diff --git a/.editorconfig b/.editorconfig index 096ff2565..5021a5b28 100644 --- a/.editorconfig +++ b/.editorconfig @@ -10,6 +10,9 @@ trim_trailing_whitespace = true insert_final_newline = true ; Not change VS generated files -[*.{sln,csroj}] +[*.{sln,csproj}] trim_trailing_whitespace = false insert_final_newline = false + +[*.{props,targets,csproj}] +indent_size = 2 \ No newline at end of file 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/libgit2sharp.yml b/.github/workflows/libgit2sharp.yml new file mode 100644 index 000000000..e1eb6ff7c --- /dev/null +++ b/.github/workflows/libgit2sharp.yml @@ -0,0 +1,80 @@ +name: .NET + +on: + push: + branches: + - dotdevelop + pull_request: + branches: + - dotdevelop + +jobs: + build: + + runs-on: ubuntu-20.04 + + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Setup .NET + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 3.1.401 + - name: Restore dependencies + run: dotnet restore LibGit2Sharp.sln + - name: Build + run: dotnet build --no-restore LibGit2Sharp.sln + - name: Pack + run: | + echo running on branch $(echo ${GITHUB_SHA} | cut -c1-10) + dotnet pack --include-symbols -c Release LibGit2Sharp.sln + ls ./bin/Packages/Release/ + + - name: Short Sha + id: short-sha + run: echo "::set-output name=short_sha::$(echo ${GITHUB_SHA} | cut -c1-10)" + + - name: Push generated package to GitHub registry + run: dotnet nuget push ./bin/Packages/Release/LibGit2Sharp.0.27.0-preview-g${{steps.short-sha.outputs.short_sha}}.nupkg --source https://nuget.pkg.github.com/dotdevelop --api-key ${{github.token}} --skip-duplicate --no-symbols true + + - name: Create Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: Release ${{ github.ref }} + draft: true + prerelease: true + - name: Upload Release Asset + id: upload-release-asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SHORT_SHA: ${{ steps.short-sha.outputs.short_sha }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps + asset_path: ./bin/Packages/Release/LibGit2Sharp.0.27.0-preview-g${{steps.short-sha.outputs.short_sha}}.nupkg + asset_name: LibGit2Sharp.0.27.0-preview-g${{steps.short-sha.outputs.short_sha}}.nupkg + asset_content_type: application/nupkg + + test: + runs-on: ubuntu-20.04 + + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Setup .NET + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 3.1.401 + - name: Restore dependencies + run: dotnet restore LibGit2Sharp.sln + - name: Build + run: dotnet build --no-restore LibGit2Sharp.sln + + - name: Test + run: dotnet test --no-build --verbosity normal LibGit2Sharp.sln diff --git a/.gitignore b/.gitignore index 6a6337edb..6ec9f6ae8 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ Thumbs.db *_p.c *.ncb *.suo +.vs/ *.sln.ide/ *.tlb *.tlh @@ -35,10 +36,9 @@ _ReSharper*/ *.userprefs *.swp *.DotSettings -#Ignore custom generated files -LibGit2Sharp/Core/UniqueIdentifier.cs -LibGit2Sharp/Core/NativeDllName.cs -!nuget.package/build/ _NCrunch_LibGit2Sharp/ packages/ +worktree.playlist + +.idea/ diff --git a/.nuget/packages.config b/.nuget/packages.config deleted file mode 100644 index 05ac50048..000000000 --- a/.nuget/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/.travis.yml b/.travis.yml index 5ab00c105..18e8b5c24 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,32 +1,7 @@ -# Travis-CI Build for libgit2sharp -# see travis-ci.org for details +language: csharp +mono: none -language: c - -os: - - osx - - linux - -before_install: - - date -u - - uname -a - - export PATH=/opt/mono/bin:$PATH - - env | sort - -# Make sure CMake and Mono are installed -install: ./CI/travis.${TRAVIS_OS_NAME}.install.deps.sh - -# Build libgit2, LibGit2Sharp and run the tests -script: - - ./build.libgit2sharp.sh 'LEAKS_IDENTIFYING' - -# Only watch the development branch +# Disable Travis-CI branches: only: - - vNext - - master - -# Notify of build changes -notifications: - email: - - emeric.fermas@gmail.com + - NOTTHISONE diff --git a/CHANGES.md b/CHANGES.md index 3fbecda78..78bd537f6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,135 @@ - Windows (x86/amd64): - Linux/Mac OS X: +## 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 diff --git a/CI/build.msbuild b/CI/build.msbuild deleted file mode 100644 index 89a3ead0e..000000000 --- a/CI/build.msbuild +++ /dev/null @@ -1,58 +0,0 @@ - - - Release - $(MSBuildProjectDirectory)\.. - $(RootDir)\LibGit2Sharp.Tests\bin\$(Configuration) - $(RootDir)\Build - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/CI/travis.linux.install.deps.sh b/CI/travis.linux.install.deps.sh deleted file mode 100755 index 365fc51a6..000000000 --- a/CI/travis.linux.install.deps.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -set -ev - -sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF - -echo "deb http://download.mono-project.com/repo/debian wheezy/snapshots/3.12.0 main" | sudo tee /etc/apt/sources.list.d/mono-xamarin.list -echo "deb http://download.mono-project.com/repo/debian wheezy-libtiff-compat main" | sudo tee -a /etc/apt/sources.list.d/mono-xamarin.list - -sudo apt-get update -sudo apt-get install mono-devel cmake diff --git a/CI/travis.osx.install.deps.sh b/CI/travis.osx.install.deps.sh deleted file mode 100755 index c6621b735..000000000 --- a/CI/travis.osx.install.deps.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -set -ev - -MONO_VER=3.6.0 - -brew update -which cmake || brew install cmake - -wget "http://download.mono-project.com/archive/${MONO_VER}/macos-10-x86/MonoFramework-MDK-${MONO_VER}.macos10.xamarin.x86.pkg" -sudo installer -pkg "MonoFramework-MDK-${MONO_VER}.macos10.xamarin.x86.pkg" -target / diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..218cb2a28 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,58 @@ +# How to Contribute + +We love Pull Requests! Your contributions help make LibGit2Sharp great. + +## Getting Started + +So you want to contribute to LibGit2Sharp. Great! Contributions take many forms from +submitting issues, writing documentation, to making code changes. We welcome it all. + +But first things first... + +* Make sure you have a [GitHub account](https://github.com/signup/free) +* Submit a ticket for your issue, assuming one does not already exist. + * Clearly describe the issue including steps to reproduce when it is a bug. + * Make sure you fill in the earliest version that you know has the issue. +* Fork the repository on GitHub, then clone it using your favorite Git client. +* Make sure the project builds and all tests pass on your machine by running + the `buildandtest.cmd` script on Windows or `buildandtest.sh` on Linux/Mac. + +## LibGit2 + +LibGit2Sharp brings all the might and speed of libgit2, a native Git implementation, to the managed world of .Net and Mono. +LibGit2 is a git submodule referencing the [libgit2 project](https://github.com/libgit2/libgit2). To learn more about +submodules read [here](http://git-scm.com/book/en/v2/Git-Tools-Submodules). +To build libgit2 see [here](https://github.com/libgit2/libgit2sharp/wiki/How-to-build-x64-libgit2-and-LibGit2Sharp). + +## Making Changes + +Make sure you have the required .NET Core SDK and runtimes installed. +The easiest way to do this is run our `tools\Install-DotNetSdk.ps1` script. +Using the `-InstallLocality Machine` switch requires elevation but ensures +that Visual Studio will be able to load the solution even when launched from a shortcut. + +Then proceed to: + +* Create a topic branch off master (don't work directly on master). +* Implement your feature or fix your bug. Please following existing coding styles and do not introduce new ones. +* Make atomic, focused commits with good commit messages. +* Make sure you have added the necessary tests for your changes. +* Run _all_ the tests to assure nothing else was accidentally broken. + +## Submitting Changes + +* Push your changes to a topic branch in your fork of the repository. +* Send a Pull Request targeting the master branch. Note what issue/issues your patch fixes. + +Some things that will increase the chance that your pull request is accepted. + +* Following existing code conventions. +* Including unit tests that would otherwise fail without the patch, but pass after applying it. +* Updating the documentation and tests that are affected by the contribution. +* If code from elsewhere is used, proper credit and a link to the source should exist in the code comments. + Then licensing issues can be checked against LibGit2Sharp's very permissive MIT based open source license. +* Having a configured git client that converts line endings to LF. [See here.](https://help.github.com/articles/dealing-with-line-endings/). +# Additional Resources + +* [General GitHub documentation](http://help.github.com/) +* [GitHub pull request documentation](https://help.github.com/articles/using-pull-requests/) diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 000000000..d98520a64 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,14 @@ + + + + true + $(MSBuildThisFileDirectory)bin\$(MSBuildProjectName)\$(Configuration)\ + $(MSBuildThisFileDirectory)obj\$(MSBuildProjectName)\ + $(MSBuildThisFileDirectory)bin\Packages\$(Configuration)\ + $(DefineConstants);$(ExtraDefine) + + + + + + diff --git a/Directory.Build.targets b/Directory.Build.targets new file mode 100644 index 000000000..0209658db --- /dev/null +++ b/Directory.Build.targets @@ -0,0 +1,14 @@ + + + cobertura + [xunit.*]* + + $(OutputPath)/ + + + + + + + diff --git a/Lib/.gitattributes b/Lib/.gitattributes deleted file mode 100644 index 2fa88711b..000000000 --- a/Lib/.gitattributes +++ /dev/null @@ -1,3 +0,0 @@ -* binary -.gitattributes text -binary -*.txt text -binary diff --git a/Lib/CustomBuildTasks/CustomBuildTasks.csproj b/Lib/CustomBuildTasks/CustomBuildTasks.csproj deleted file mode 100644 index 1c50a44aa..000000000 --- a/Lib/CustomBuildTasks/CustomBuildTasks.csproj +++ /dev/null @@ -1,43 +0,0 @@ - - - - - Debug - AnyCPU - {B6138573-A4B9-44E7-83C2-8964CAF51EDA} - Library - Properties - CustomBuildTasks - CustomBuildTasks - v3.5 - 512 - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - diff --git a/Lib/CustomBuildTasks/CustomBuildTasks.dll b/Lib/CustomBuildTasks/CustomBuildTasks.dll deleted file mode 100644 index 5bc63169d..000000000 Binary files a/Lib/CustomBuildTasks/CustomBuildTasks.dll and /dev/null differ diff --git a/Lib/CustomBuildTasks/GenerateNativeDllNameTask.cs b/Lib/CustomBuildTasks/GenerateNativeDllNameTask.cs deleted file mode 100644 index f68724978..000000000 --- a/Lib/CustomBuildTasks/GenerateNativeDllNameTask.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.IO; -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; - -namespace CustomBuildTasks -{ - public class GenerateNativeDllNameTask : Task - { - public ITaskItem InputHashFile { get; set; } - - public string OutputFile { get; set; } - - public override bool Execute() - { - var fileName = InputHashFile.GetMetadata("FullPath"); - string hash; - - using (var sr = new StreamReader(fileName)) - { - hash = sr.ReadLine(); - } - - var shortHash = hash.Substring(0, 7); - - var nativeDllName = @"namespace LibGit2Sharp.Core -{{ - internal static class NativeDllName - {{ - public const string Name = ""git2-{0}""; - }} -}} -"; - - using (var sw = new StreamWriter(OutputFile)) - { - sw.Write(nativeDllName, shortHash); - } - - return true; - } - } -} diff --git a/Lib/CustomBuildTasks/GenerateUniqueIdentifierTask.cs b/Lib/CustomBuildTasks/GenerateUniqueIdentifierTask.cs deleted file mode 100644 index 2f26ac94d..000000000 --- a/Lib/CustomBuildTasks/GenerateUniqueIdentifierTask.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.IO; -using System.Text; -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; - -namespace CustomBuildTasks -{ - public class GenerateUniqueIdentifierTask : Task - { - public override bool Execute() - { - using (FileStream fs = new FileStream(this.OutputFile, FileMode.Create, FileAccess.Write, FileShare.None)) - using (StreamWriter sw = new StreamWriter(fs, Encoding.UTF8)) - { - sw.WriteLine("using System;"); - sw.WriteLine(); - sw.WriteLine("namespace LibGit2Sharp.Core"); - sw.WriteLine("{"); - sw.WriteLine(" internal static class UniqueId"); - sw.WriteLine(" {"); - sw.WriteLine(" public const String UniqueIdentifier = \"" + Guid.NewGuid().ToString() + "\";"); - sw.WriteLine(" }"); - sw.WriteLine("}"); - } - - return true; - } - - public String OutputFile - { - get; - set; - } - } -} diff --git a/Lib/NuGet/NuGet.exe b/Lib/NuGet/NuGet.exe deleted file mode 100644 index ed2b0a221..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/LibGit2Sharp.Tests/ArchiveFixture.cs b/LibGit2Sharp.Tests/ArchiveFixture.cs index 7253e2fe8..19860ca0b 100644 --- a/LibGit2Sharp.Tests/ArchiveFixture.cs +++ b/LibGit2Sharp.Tests/ArchiveFixture.cs @@ -18,6 +18,8 @@ public void CanArchiveATree() var archiver = new MockArchiver(); + var before = DateTimeOffset.Now.TruncateMilliseconds(); + repo.ObjectDatabase.Archive(tree, archiver); var expected = new ArrayList @@ -30,7 +32,7 @@ public void CanArchiveATree() }; Assert.Equal(expected, archiver.Files); Assert.Null(archiver.ReceivedCommitSha); - Assert.InRange(archiver.ModificationTime, DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMilliseconds(100)), DateTimeOffset.UtcNow); + Assert.InRange(archiver.ModificationTime, before, DateTimeOffset.UtcNow); } } @@ -66,8 +68,10 @@ public void ArchivingANullTreeOrCommitThrows() string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { - Assert.Throws(() => repo.ObjectDatabase.Archive((Commit)null, null)); - Assert.Throws(() => repo.ObjectDatabase.Archive((Tree)null, null)); + Assert.Throws(() => repo.ObjectDatabase.Archive(default(Commit), default(ArchiverBase))); + Assert.Throws(() => repo.ObjectDatabase.Archive(default(Commit), default(string))); + Assert.Throws(() => repo.ObjectDatabase.Archive(default(Tree), default(ArchiverBase))); + Assert.Throws(() => repo.ObjectDatabase.Archive(default(Tree), default(string))); } } diff --git a/LibGit2Sharp.Tests/ArchiveTarFixture.cs b/LibGit2Sharp.Tests/ArchiveTarFixture.cs index a21847ea0..247a9a3b0 100644 --- a/LibGit2Sharp.Tests/ArchiveTarFixture.cs +++ b/LibGit2Sharp.Tests/ArchiveTarFixture.cs @@ -30,8 +30,8 @@ public void CanArchiveACommitWithDirectoryAsTar() repo.ObjectDatabase.Archive(commit, archivePath); - using (var expectedStream = new StreamReader(Path.Combine(ResourcesDirectory.FullName, "expected_archives/commit_with_directory.tar"))) - using (var actualStream = new StreamReader(archivePath)) + using (var expectedStream = new StreamReader(File.OpenRead(Path.Combine(ResourcesDirectory.FullName, "expected_archives/commit_with_directory.tar")))) + using (var actualStream = new StreamReader(File.OpenRead(archivePath))) { string expected = expectedStream.ReadToEnd(); string actual = actualStream.ReadToEnd(); diff --git a/LibGit2Sharp.Tests/AttributesFixture.cs b/LibGit2Sharp.Tests/AttributesFixture.cs index c9c4eb712..3ac8326d3 100644 --- a/LibGit2Sharp.Tests/AttributesFixture.cs +++ b/LibGit2Sharp.Tests/AttributesFixture.cs @@ -9,8 +9,7 @@ public class AttributesFixture : BaseFixture [Fact] public void StagingHonorsTheAttributesFiles() { - string path = SandboxStandardTestRepo(); - using (var repo = new Repository(path)) + using (var repo = new Repository(InitNewRepository())) { CreateAttributesFile(repo); @@ -30,7 +29,7 @@ private static void AssertNormalization(IRepository repo, string filename, bool Touch(repo.Info.WorkingDirectory, filename, sb.ToString()); - repo.Stage(filename); + Commands.Stage(repo, filename); IndexEntry entry = repo.Index[filename]; Assert.NotNull(entry); diff --git a/LibGit2Sharp.Tests/BlameFixture.cs b/LibGit2Sharp.Tests/BlameFixture.cs index 9138646c3..da63dc124 100644 --- a/LibGit2Sharp.Tests/BlameFixture.cs +++ b/LibGit2Sharp.Tests/BlameFixture.cs @@ -9,7 +9,7 @@ public class BlameFixture : BaseFixture { private static void AssertCorrectHeadBlame(BlameHunkCollection blame) { - Assert.Equal(1, blame.Count()); + Assert.Single(blame); Assert.Equal(0, blame[0].FinalStartLineNumber); Assert.Equal("schacon@gmail.com", blame[0].FinalSignature.Email); Assert.Equal("4a202b3", blame[0].FinalCommit.Id.ToString(7)); @@ -39,7 +39,7 @@ public void CanBlameFromADifferentCommit() Assert.Throws(() => repo.Blame("ancestor-only.txt")); var blame = repo.Blame("ancestor-only.txt", new BlameOptions { StartingAt = "9107b30" }); - Assert.Equal(1, blame.Count()); + Assert.Single(blame); } } @@ -79,7 +79,7 @@ public void CanStopBlame() // 9fd738e8 (Scott Chacon 2010-05-24 10:19:19 -0700 1) my new file // (be3563a comes after 9fd738e8) var blame = repo.Blame("new.txt", new BlameOptions {StoppingAt = "be3563a"}); - Assert.True(blame[0].FinalCommit.Sha.StartsWith("be3563a")); + Assert.StartsWith("be3563a", blame[0].FinalCommit.Sha); } } } diff --git a/LibGit2Sharp.Tests/BlobFixture.cs b/LibGit2Sharp.Tests/BlobFixture.cs index 4c984bd34..e6a5f3c57 100644 --- a/LibGit2Sharp.Tests/BlobFixture.cs +++ b/LibGit2Sharp.Tests/BlobFixture.cs @@ -63,7 +63,7 @@ public void CanGetBlobAsTextWithVariousEncodings(string encodingName, int expect var bomPath = Touch(repo.Info.WorkingDirectory, bomFile, content, encoding); Assert.Equal(expectedContentBytes, File.ReadAllBytes(bomPath).Length); - repo.Stage(bomFile); + Commands.Stage(repo, bomFile); var commit = repo.Commit("bom", Constants.Signature, Constants.Signature); var blob = (Blob)commit.Tree[bomFile].Target; @@ -190,7 +190,7 @@ public void CanStageAFileGeneratedFromABlobContentStream() File.AppendAllText(Path.Combine(repo.Info.WorkingDirectory, "small.txt"), sb.ToString()); } - repo.Stage("small.txt"); + Commands.Stage(repo, "small.txt"); IndexEntry entry = repo.Index["small.txt"]; Assert.Equal("baae1fb3760a73481ced1fa03dc15614142c19ef", entry.Id.Sha); @@ -202,7 +202,7 @@ public void CanStageAFileGeneratedFromABlobContentStream() CopyStream(stream, file); } - repo.Stage("small.fromblob.txt"); + Commands.Stage(repo, "small.fromblob.txt"); IndexEntry newentry = repo.Index["small.fromblob.txt"]; Assert.Equal("baae1fb3760a73481ced1fa03dc15614142c19ef", newentry.Id.Sha); @@ -216,7 +216,7 @@ public void CanTellIfTheBlobContentLooksLikeBinary() using (var repo = new Repository(path)) { var blob = repo.Lookup("a8233120f6ad708f843d861ce2b7228ec4e3dec6"); - Assert.Equal(false, blob.IsBinary); + Assert.False(blob.IsBinary); } } diff --git a/LibGit2Sharp.Tests/BranchFixture.cs b/LibGit2Sharp.Tests/BranchFixture.cs index 43bcff91d..736b0faec 100644 --- a/LibGit2Sharp.Tests/BranchFixture.cs +++ b/LibGit2Sharp.Tests/BranchFixture.cs @@ -24,6 +24,8 @@ public void CanCreateBranch(string name) const string committish = "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"; + var before = DateTimeOffset.Now.TruncateMilliseconds(); + Branch newBranch = repo.CreateBranch(name, committish); Assert.NotNull(newBranch); Assert.Equal(name, newBranch.FriendlyName); @@ -42,13 +44,26 @@ public void CanCreateBranch(string name) "branch: Created from " + committish, null, newBranch.Tip.Id, - Constants.Identity, DateTimeOffset.Now); + Constants.Identity, before); repo.Branches.Remove(newBranch.FriendlyName); Assert.Null(repo.Branches[name]); } } + [Theory] + [InlineData("32eab9cb1f450b5fe7ab663462b77d7f4b703344")] + public void CanHeadBeDetached(string commit) + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + Assert.False(repo.Info.IsHeadDetached); + Commands.Checkout(repo, commit); + Assert.True(repo.Info.IsHeadDetached); + } + } + [Fact] public void CanCreateAnUnbornBranch() { @@ -72,7 +87,7 @@ public void CanCreateAnUnbornBranch() Commit c = repo.Commit("New initial root commit", Constants.Signature, Constants.Signature); // Ensure this commit has no parent - Assert.Equal(0, c.Parents.Count()); + Assert.Empty(c.Parents); // The branch now exists... Branch orphan = repo.Branches["orphan"]; @@ -95,6 +110,8 @@ public void CanCreateBranchUsingAbbreviatedSha() const string name = "unit_test"; const string committish = "be3563a"; + var before = DateTimeOffset.Now.TruncateMilliseconds(); + Branch newBranch = repo.CreateBranch(name, committish); Assert.Equal("refs/heads/" + name, newBranch.CanonicalName); Assert.Equal("be3563ae3f795b2b4353bcce3a527ad0a4f7f644", newBranch.Tip.Sha); @@ -103,7 +120,7 @@ public void CanCreateBranchUsingAbbreviatedSha() "branch: Created from " + committish, null, newBranch.Tip.Id, - Constants.Identity, DateTimeOffset.Now); + Constants.Identity, before); } } @@ -117,9 +134,12 @@ public void CanCreateBranchFromImplicitHead(string headCommitOrBranchSpec) { EnableRefLog(repo); - repo.Checkout(headCommitOrBranchSpec); + Commands.Checkout(repo, headCommitOrBranchSpec); const string name = "unit_test"; + + var before = DateTimeOffset.Now.TruncateMilliseconds(); + Branch newBranch = repo.CreateBranch(name); Assert.NotNull(newBranch); Assert.Equal(name, newBranch.FriendlyName); @@ -133,7 +153,7 @@ public void CanCreateBranchFromImplicitHead(string headCommitOrBranchSpec) "branch: Created from " + headCommitOrBranchSpec, null, newBranch.Tip.Id, - Constants.Identity, DateTimeOffset.Now); + Constants.Identity, before); } } @@ -147,9 +167,12 @@ public void CanCreateBranchFromExplicitHead(string headCommitOrBranchSpec) { EnableRefLog(repo); - repo.Checkout(headCommitOrBranchSpec); + Commands.Checkout(repo, headCommitOrBranchSpec); const string name = "unit_test"; + + var before = DateTimeOffset.Now.TruncateMilliseconds(); + Branch newBranch = repo.CreateBranch(name, "HEAD"); Assert.NotNull(newBranch); Assert.Equal("32eab9cb1f450b5fe7ab663462b77d7f4b703344", newBranch.Tip.Sha); @@ -158,7 +181,7 @@ public void CanCreateBranchFromExplicitHead(string headCommitOrBranchSpec) "branch: Created from HEAD", null, newBranch.Tip.Id, - Constants.Identity, DateTimeOffset.Now); + Constants.Identity, before); } } @@ -172,6 +195,9 @@ public void CanCreateBranchFromCommit() 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); @@ -180,7 +206,7 @@ public void CanCreateBranchFromCommit() "branch: Created from " + newBranch.Tip.Sha, null, newBranch.Tip.Id, - Constants.Identity, DateTimeOffset.Now); + Constants.Identity, before); } } @@ -195,6 +221,8 @@ public void CanCreateBranchFromRevparseSpec() const string name = "revparse_branch"; const string committish = "master~2"; + var before = DateTimeOffset.Now.TruncateMilliseconds(); + Branch newBranch = repo.CreateBranch(name, committish); Assert.NotNull(newBranch); Assert.Equal("9fd738e8f7967c078dceed8190330fc8648ee56a", newBranch.Tip.Sha); @@ -203,7 +231,7 @@ public void CanCreateBranchFromRevparseSpec() "branch: Created from " + committish, null, newBranch.Tip.Id, - Constants.Identity, DateTimeOffset.Now); + Constants.Identity, before); } } @@ -219,6 +247,8 @@ public void CreatingABranchFromATagPeelsToTheCommit(string committish) const string name = "i-peel-tag"; + var before = DateTimeOffset.Now.TruncateMilliseconds(); + Branch newBranch = repo.CreateBranch(name, committish); Assert.NotNull(newBranch); Assert.Equal("e90810b8df3e80c413d903f631643c716887138d", newBranch.Tip.Sha); @@ -227,7 +257,7 @@ public void CreatingABranchFromATagPeelsToTheCommit(string committish) "branch: Created from " + committish, null, newBranch.Tip.Id, - Constants.Identity, DateTimeOffset.Now); + Constants.Identity, before); } } @@ -245,7 +275,7 @@ public void CreatingABranchTriggersTheCreationOfADirectReference() Reference reference = repo.Refs[newBranch.CanonicalName]; Assert.NotNull(reference); - Assert.IsType(typeof(DirectReference), reference); + Assert.IsType(reference); } } @@ -377,7 +407,7 @@ public void CanResolveRemote() using (var repo = new Repository(path)) { Branch master = repo.Branches["master"]; - Assert.Equal(repo.Network.Remotes["origin"], master.Remote); + Assert.Equal("origin", master.RemoteName); } } @@ -388,7 +418,7 @@ public void RemoteAndUpstreamBranchCanonicalNameForNonTrackingBranchIsNull() 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); } } @@ -401,7 +431,7 @@ public void QueryRemoteForLocalTrackingBranch() using (var repo = new Repository(path)) { Branch trackLocal = repo.Branches["track-local"]; - Assert.Null(trackLocal.Remote); + Assert.Null(trackLocal.RemoteName); } } @@ -423,44 +453,42 @@ public void QueryRemoteForRemoteBranch() using (var repo = new Repository(path)) { var master = repo.Branches["origin/master"]; - Assert.Equal(repo.Network.Remotes["origin"], master.Remote); + Assert.Equal("origin", master.RemoteName); } } [Fact] public void QueryUnresolvableRemoteForRemoteBranch() { - var path = SandboxStandardTestRepo(); - var fetchRefSpecs = new string[] { "+refs/heads/notfound/*:refs/remotes/origin/notfound/*" }; - using (var repo = InitIsolatedRepository(path)) + var path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { // Update the remote config such that the remote for a // remote branch cannot be resolved Remote remote = repo.Network.Remotes["origin"]; Assert.NotNull(remote); - repo.Network.Remotes.Update(remote, r => r.FetchRefSpecs = fetchRefSpecs); + repo.Network.Remotes.Update("origin", r => r.FetchRefSpecs = fetchRefSpecs); Branch branch = repo.Branches["refs/remotes/origin/master"]; Assert.NotNull(branch); Assert.True(branch.IsRemote); - Assert.Null(branch.Remote); + Assert.Null(branch.RemoteName); } } [Fact] public void QueryAmbigousRemoteForRemoteBranch() { - var path = SandboxStandardTestRepo(); - const string fetchRefSpec = "+refs/heads/*:refs/remotes/origin/*"; const string url = "http://github.com/libgit2/TestGitRepository"; - using (var repo = InitIsolatedRepository(path)) + var path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { // Add a second remote so that it is ambiguous which remote // the remote-tracking branch tracks. @@ -471,7 +499,7 @@ public void QueryAmbigousRemoteForRemoteBranch() Assert.NotNull(branch); Assert.True(branch.IsRemote); - Assert.Null(branch.Remote); + Assert.Null(branch.RemoteName); } } @@ -548,7 +576,7 @@ public void CanGetInformationFromUnbornBranch() var head = repo.Head; Assert.Equal("refs/heads/master", head.CanonicalName); - Assert.Equal(0, head.Commits.Count()); + Assert.Empty(head.Commits); Assert.True(head.IsCurrentRepositoryHead); Assert.False(head.IsRemote); Assert.Equal("master", head.FriendlyName); @@ -717,7 +745,7 @@ public void CanSetTrackedBranch() Assert.True(branch.IsTracking); Assert.Equal(trackedBranch, branch.TrackedBranch); - Assert.Equal(upstreamRemote, branch.Remote); + Assert.Equal("origin", branch.RemoteName); } } @@ -735,7 +763,7 @@ public void SetTrackedBranchForUnreasolvableRemoteThrows() // cannot be resolved. Remote remote = repo.Network.Remotes["origin"]; Assert.NotNull(remote); - repo.Network.Remotes.Update(remote, r => r.FetchRefSpecs = fetchRefSpecs); + repo.Network.Remotes.Update("origin", r => r.FetchRefSpecs = fetchRefSpecs); // Now attempt to update the tracked branch Branch branch = repo.CreateBranch(testBranchName); @@ -778,7 +806,7 @@ public void CanSetUpstreamBranch() Assert.True(updatedBranch.IsTracking); Assert.Equal(trackedBranch, updatedBranch.TrackedBranch); Assert.Equal(upstreamBranchName, updatedBranch.UpstreamBranchCanonicalName); - Assert.Equal(upstreamRemote, updatedBranch.Remote); + Assert.Equal(remoteName, updatedBranch.RemoteName); } } @@ -805,7 +833,7 @@ public void CanSetLocalTrackedBranch() // Branches that track the local remote do not have the "Remote" property set. // Verify (through the configuration entry) that the local remote is set as expected. - Assert.Null(branch.Remote); + Assert.Null(branch.RemoteName); ConfigurationEntry remoteConfigEntry = repo.Config.Get("branch", testBranchName, "remote"); Assert.NotNull(remoteConfigEntry); Assert.Equal(".", remoteConfigEntry.Value); @@ -846,7 +874,7 @@ public void CanUnsetTrackedBranch() // Verify this is no longer a tracking branch Assert.False(branch.IsTracking); - Assert.Null(branch.Remote); + Assert.Null(branch.RemoteName); Assert.Null(branch.UpstreamBranchCanonicalName); } } @@ -902,6 +930,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)] @@ -927,7 +973,8 @@ public void RemovingABranchWithBadParamsThrows() 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))); } } @@ -986,6 +1033,8 @@ public void CanRenameABranch() var br2 = repo.Branches["br2"]; Assert.NotNull(br2); + var before = DateTimeOffset.Now.TruncateMilliseconds(); + Branch newBranch = repo.Branches.Rename("br2", "br3"); Assert.Equal("br3", newBranch.FriendlyName); @@ -997,7 +1046,7 @@ public void CanRenameABranch() string.Format("branch: renamed {0} to {1}", br2.CanonicalName, newBranch.CanonicalName), br2.Tip.Id, newBranch.Tip.Id, - Constants.Identity, DateTimeOffset.Now); + Constants.Identity, before); } } @@ -1025,6 +1074,8 @@ public void CanRenameABranchWhileOverwritingAnExistingOne() Branch br2 = repo.Branches["br2"]; Assert.NotNull(br2); + var before = DateTimeOffset.Now.TruncateMilliseconds(); + Branch newBranch = repo.Branches.Rename("br2", "test", true); Assert.Equal("test", newBranch.FriendlyName); @@ -1040,7 +1091,7 @@ public void CanRenameABranchWhileOverwritingAnExistingOne() string.Format("branch: renamed {0} to {1}", br2.CanonicalName, newBranch.CanonicalName), br2.Tip.Id, newTest.Tip.Id, - Constants.Identity, DateTimeOffset.Now); + Constants.Identity, before); } } @@ -1054,7 +1105,7 @@ public void DetachedHeadIsNotATrackingBranch() 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); @@ -1075,7 +1126,7 @@ public void TrackedBranchExistsFromDefaultConfigInEmptyClone() using (var emptyRepo = new Repository(repoPath)) { - uri = new Uri(emptyRepo.Info.Path); + uri = new Uri($"file://{emptyRepo.Info.Path}"); } SelfCleaningDirectory scd2 = BuildSelfCleaningDirectory(); @@ -1085,7 +1136,7 @@ public void TrackedBranchExistsFromDefaultConfigInEmptyClone() using (var repo = new Repository(clonedRepoPath)) { Assert.Empty(Directory.GetFiles(scd2.RootedDirectoryPath)); - Assert.Equal(repo.Head.FriendlyName, "master"); + Assert.Equal("master", repo.Head.FriendlyName); Assert.Null(repo.Head.Tip); Assert.NotNull(repo.Head.TrackedBranch); @@ -1096,11 +1147,10 @@ public void TrackedBranchExistsFromDefaultConfigInEmptyClone() Assert.Null(repo.Head.TrackingDetails.BehindBy); Assert.Null(repo.Head.TrackingDetails.CommonAncestor); - Assert.NotNull(repo.Head.Remote); - Assert.Equal("origin", repo.Head.Remote.Name); + Assert.Equal("origin", repo.Head.RemoteName); Touch(repo.Info.WorkingDirectory, "a.txt", "a"); - repo.Stage("a.txt"); + Commands.Stage(repo, "a.txt"); repo.Commit("A file", Constants.Signature, Constants.Signature); Assert.NotNull(repo.Head.Tip); @@ -1125,7 +1175,7 @@ public void RemoteBranchesDoNotTrackAnything() 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); @@ -1149,19 +1199,24 @@ public void CreatingABranchIncludesTheCorrectReflogEntries() 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, DateTimeOffset.Now); + 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, DateTimeOffset.Now); + Constants.Identity, before); } } @@ -1173,15 +1228,20 @@ public void RenamingABranchIncludesTheCorrectReflogEntries() { 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, DateTimeOffset.Now); + 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, DateTimeOffset.Now); + Constants.Identity, before); } } } diff --git a/LibGit2Sharp.Tests/CheckoutFixture.cs b/LibGit2Sharp.Tests/CheckoutFixture.cs index c91f1d691..f0c2c36ed 100644 --- a/LibGit2Sharp.Tests/CheckoutFixture.cs +++ b/LibGit2Sharp.Tests/CheckoutFixture.cs @@ -34,7 +34,7 @@ public void CanCheckoutAnExistingBranch(string 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); @@ -73,7 +73,7 @@ public void CanCheckoutAnExistingBranchByName(string branchName) 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); @@ -97,7 +97,7 @@ public void CanCheckoutAnExistingBranchByName(string branchName) [Theory] [InlineData("6dcf9bf", true, "6dcf9bf")] - [InlineData("refs/tags/lw", true, "refs/tags/lw")] + [InlineData("refs/tags/lw", true, "lw")] [InlineData("HEAD~2", true, "HEAD~2")] [InlineData("6dcf9bf", false, "6dcf9bf7541ee10456529833502442f385010c3d")] [InlineData("refs/tags/lw", false, "e90810b8df3e80c413d903f631643c716887138d")] @@ -118,7 +118,7 @@ public void CanCheckoutAnArbitraryCommit(string commitPointer, bool checkoutByCo var commit = repo.Lookup(commitPointer); AssertBelongsToARepository(repo, commit); - Branch detachedHead = checkoutByCommitOrBranchSpec ? repo.Checkout(commitPointer) : repo.Checkout(commit); + Branch detachedHead = checkoutByCommitOrBranchSpec ? Commands.Checkout(repo, commitPointer) : Commands.Checkout(repo, commit); Assert.Equal(repo.Head, detachedHead); Assert.Equal(commit.Sha, detachedHead.Tip.Sha); @@ -156,13 +156,13 @@ public void CheckoutAddsMissingFilesInWorkingDirectory() // Remove the file in master branch // Verify it exists after checking out otherBranch. string fileFullPath = Path.Combine(repo.Info.WorkingDirectory, originalFilePath); - repo.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); - repo.Checkout(otherBranch); + Commands.Checkout(repo, otherBranch); // Verify working directory is updated Assert.False(repo.RetrieveStatus().IsDirty); @@ -184,13 +184,13 @@ public void CheckoutRemovesExtraFilesInWorkingDirectory() string newFileFullPath = Touch( repo.Info.WorkingDirectory, "b.txt", "hello from master branch!\n"); - repo.Stage(newFileFullPath); + Commands.Stage(repo, newFileFullPath); repo.Commit("2nd commit", Constants.Signature, Constants.Signature); // Checkout other_branch Branch otherBranch = repo.Branches[otherBranchName]; Assert.NotNull(otherBranch); - repo.Checkout(otherBranch); + Commands.Checkout(repo, otherBranch); // Verify working directory is updated Assert.False(repo.RetrieveStatus().IsDirty); @@ -212,13 +212,13 @@ public void CheckoutUpdatesModifiedFilesInWorkingDirectory() string fullPath = Touch( repo.Info.WorkingDirectory, originalFilePath, "Update : hello from master branch!\n"); - repo.Stage(fullPath); + Commands.Stage(repo, fullPath); repo.Commit("2nd commit", Constants.Signature, Constants.Signature); // Checkout other_branch Branch otherBranch = repo.Branches[otherBranchName]; Assert.NotNull(otherBranch); - repo.Checkout(otherBranch); + Commands.Checkout(repo, otherBranch); // Verify working directory is updated Assert.False(repo.RetrieveStatus().IsDirty); @@ -254,22 +254,22 @@ public void CanForcefullyCheckoutWithConflictingStagedChanges() // Add change to master. Touch(repo.Info.WorkingDirectory, originalFilePath, originalFileContent); - repo.Stage(originalFilePath); + Commands.Stage(repo, originalFilePath); repo.Commit("change in master", Constants.Signature, Constants.Signature); // Checkout otherBranch. - repo.Checkout(otherBranchName); + Commands.Checkout(repo, otherBranchName); // Add change to otherBranch. Touch(repo.Info.WorkingDirectory, originalFilePath, alternateFileContent); - repo.Stage(originalFilePath); + Commands.Stage(repo, originalFilePath); // Assert that normal checkout throws exception // for the conflict. - Assert.Throws(() => repo.Checkout(master.CanonicalName)); + Assert.Throws(() => Commands.Checkout(repo, master.CanonicalName)); // Checkout with force option should succeed. - repo.Checkout(master.CanonicalName, new CheckoutOptions() { CheckoutModifiers = CheckoutModifiers.Force}); + Commands.Checkout(repo, master.CanonicalName, new CheckoutOptions() { CheckoutModifiers = CheckoutModifiers.Force}); // Assert that master branch is checked out. Assert.True(repo.Branches["master"].IsCurrentRepositoryHead); @@ -287,7 +287,7 @@ public void CheckingOutWithMergeConflictsThrows() using (var repo = new Repository(repoPath)) { Touch(repo.Info.WorkingDirectory, originalFilePath, "Hello\n"); - repo.Stage(originalFilePath); + Commands.Stage(repo, originalFilePath); repo.Commit("Initial commit", Constants.Signature, Constants.Signature); // Create 2nd branch @@ -295,20 +295,20 @@ public void CheckingOutWithMergeConflictsThrows() // Update file in main Touch(repo.Info.WorkingDirectory, originalFilePath, "Hello from master!\n"); - repo.Stage(originalFilePath); + Commands.Stage(repo, originalFilePath); repo.Commit("2nd commit", Constants.Signature, Constants.Signature); // Checkout branch2 - repo.Checkout("branch2"); + Commands.Checkout(repo, "branch2"); Touch(repo.Info.WorkingDirectory, originalFilePath, "Hello From branch2!\n"); // Assert that checking out master throws // when there are unstaged commits - Assert.Throws(() => repo.Checkout("master")); + Assert.Throws(() => Commands.Checkout(repo, "master")); // And when there are staged commits - repo.Stage(originalFilePath); - Assert.Throws(() => repo.Checkout("master")); + Commands.Stage(repo, originalFilePath); + Assert.Throws(() => Commands.Checkout(repo, "master")); } } @@ -322,7 +322,7 @@ public void CanCancelCheckoutThroughNotifyCallback() const string relativePath = "a.txt"; Touch(repo.Info.WorkingDirectory, relativePath, "Hello\n"); - repo.Stage(relativePath); + Commands.Stage(repo, relativePath); repo.Commit("Initial commit", Constants.Signature, Constants.Signature); // Create 2nd branch @@ -330,11 +330,11 @@ public void CanCancelCheckoutThroughNotifyCallback() // Update file in main Touch(repo.Info.WorkingDirectory, relativePath, "Hello from master!\n"); - repo.Stage(relativePath); + Commands.Stage(repo, relativePath); repo.Commit("2nd commit", Constants.Signature, Constants.Signature); // Checkout branch2 - repo.Checkout("branch2"); + Commands.Checkout(repo, "branch2"); // Update the context of a.txt - a.txt will then conflict between branch2 and master. Touch(repo.Info.WorkingDirectory, relativePath, "Hello From branch2!\n"); @@ -348,7 +348,7 @@ public void CanCancelCheckoutThroughNotifyCallback() CheckoutNotifyFlags = CheckoutNotifyFlags.Conflict, }; - Assert.Throws(() => repo.Checkout("master", options)); + Assert.Throws(() => Commands.Checkout(repo, "master", options)); Assert.Equal(relativePath, conflictPath); } } @@ -359,8 +359,8 @@ public void CheckingOutInABareRepoThrows() 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")); } } @@ -373,7 +373,7 @@ public void CheckingOutAgainstAnUnbornBranchThrows() { Assert.True(repo.Info.IsHeadUnborn); - Assert.Throws(() => repo.Checkout(repo.Head)); + Assert.Throws(() => Commands.Checkout(repo, repo.Head)); } } @@ -383,7 +383,7 @@ public void CheckingOutANonExistingBranchThrows() 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")); } } @@ -393,9 +393,9 @@ public void CheckingOutABranchWithBadParamsThrows() 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))); } } @@ -410,7 +410,7 @@ public void CheckingOutThroughBranchCallsCheckoutProgress() bool wasCalled = false; Branch branch = repo.Branches[otherBranchName]; - repo.Checkout(branch, + Commands.Checkout(repo, branch, new CheckoutOptions { OnCheckoutProgress = (path, completed, total) => wasCalled = true}); Assert.True(wasCalled); @@ -427,7 +427,7 @@ public void CheckingOutThroughRepositoryCallsCheckoutProgress() PopulateBasicRepository(repo); bool wasCalled = false; - repo.Checkout(otherBranchName, new CheckoutOptions() { OnCheckoutProgress = (path, completed, total) => wasCalled = true}); + Commands.Checkout(repo, otherBranchName, new CheckoutOptions() { OnCheckoutProgress = (path, completed, total) => wasCalled = true}); Assert.True(wasCalled); } @@ -453,13 +453,13 @@ public void CheckingOutCallsCheckoutNotify(CheckoutNotifyFlags notifyFlags, stri const string relativePathUpdated = "updated.txt"; Touch(repo.Info.WorkingDirectory, relativePathUpdated, "updated file text A"); - repo.Stage(relativePathUpdated); + 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"); - repo.Stage(relativePathConflict); + Commands.Stage(repo, relativePathConflict); repo.Commit("Initial commit of conflict.txt and update.txt", Constants.Signature, Constants.Signature); // Create another branch @@ -467,25 +467,25 @@ public void CheckingOutCallsCheckoutNotify(CheckoutNotifyFlags notifyFlags, stri // Make an edit to conflict.txt and update.txt Touch(repo.Info.WorkingDirectory, relativePathUpdated, "updated file text BB"); - repo.Stage(relativePathUpdated); + Commands.Stage(repo, relativePathUpdated); Touch(repo.Info.WorkingDirectory, relativePathConflict, "conflict file text BB"); - repo.Stage(relativePathConflict); + Commands.Stage(repo, relativePathConflict); repo.Commit("2nd commit of conflict.txt and update.txt on master branch", Constants.Signature, Constants.Signature); // Checkout other branch - repo.Checkout("newbranch"); + Commands.Checkout(repo, "newbranch"); // Make alternate edits to conflict.txt and update.txt Touch(repo.Info.WorkingDirectory, relativePathUpdated, "updated file text CCC"); - repo.Stage(relativePathUpdated); + Commands.Stage(repo, relativePathUpdated); Touch(repo.Info.WorkingDirectory, relativePathConflict, "conflict file text CCC"); - repo.Stage(relativePathConflict); + Commands.Stage(repo, relativePathConflict); repo.Commit("2nd commit of conflict.txt and update.txt on newbranch", Constants.Signature, Constants.Signature); // make conflicting change to conflict.txt Touch(repo.Info.WorkingDirectory, relativePathConflict, "conflict file text DDDD"); - repo.Stage(relativePathConflict); + Commands.Stage(repo, relativePathConflict); // Create ignored change string relativePathIgnore = Path.Combine("bin", "ignored.txt"); @@ -505,7 +505,7 @@ public void CheckingOutCallsCheckoutNotify(CheckoutNotifyFlags notifyFlags, stri CheckoutNotifyFlags = notifyFlags, }; - Assert.Throws(() => repo.Checkout("master", options)); + Assert.Throws(() => Commands.Checkout(repo, "master", options)); Assert.True(wasCalled); Assert.Equal(expectedNotificationPath, actualNotificationPath); @@ -526,13 +526,13 @@ public void CheckoutRetainsUntrackedChanges() string fullPathFileB = Touch(repo.Info.WorkingDirectory, "b.txt", alternateFileContent); // Verify that there is an untracked entry. - Assert.Equal(1, repo.RetrieveStatus().Untracked.Count()); + 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.RetrieveStatus().Untracked.Count()); + Assert.Single(repo.RetrieveStatus().Untracked); Assert.Equal(FileStatus.NewInWorkdir, repo.RetrieveStatus(fullPathFileB)); } } @@ -550,13 +550,13 @@ public void ForceCheckoutRetainsUntrackedChanges() string fullPathFileB = Touch(repo.Info.WorkingDirectory, "b.txt", alternateFileContent); // Verify that there is an untracked entry. - Assert.Equal(1, repo.RetrieveStatus().Untracked.Count()); + Assert.Single(repo.RetrieveStatus().Untracked); Assert.Equal(FileStatus.NewInWorkdir, repo.RetrieveStatus(fullPathFileB)); - repo.Checkout(otherBranchName, new CheckoutOptions() { CheckoutModifiers = CheckoutModifiers.Force }); + Commands.Checkout(repo, otherBranchName, new CheckoutOptions() { CheckoutModifiers = CheckoutModifiers.Force }); // Verify untracked entry still exists. - Assert.Equal(1, repo.RetrieveStatus().Untracked.Count()); + Assert.Single(repo.RetrieveStatus().Untracked); Assert.Equal(FileStatus.NewInWorkdir, repo.RetrieveStatus(fullPathFileB)); } } @@ -574,13 +574,13 @@ public void CheckoutRetainsUnstagedChanges() string fullPathFileA = Touch(repo.Info.WorkingDirectory, originalFilePath, alternateFileContent); // Verify that there is a modified entry. - Assert.Equal(1, repo.RetrieveStatus().Modified.Count()); + 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.RetrieveStatus().Modified.Count()); + Assert.Single(repo.RetrieveStatus().Modified); Assert.Equal(FileStatus.ModifiedInWorkdir, repo.RetrieveStatus(fullPathFileA)); } } @@ -596,16 +596,16 @@ public void CheckoutRetainsStagedChanges() // Generate a staged change. string fullPathFileA = Touch(repo.Info.WorkingDirectory, originalFilePath, alternateFileContent); - repo.Stage(fullPathFileA); + Commands.Stage(repo, fullPathFileA); // Verify that there is a staged entry. - Assert.Equal(1, repo.RetrieveStatus().Staged.Count()); + 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.RetrieveStatus().Staged.Count()); + Assert.Single(repo.RetrieveStatus().Staged); Assert.Equal(FileStatus.ModifiedInIndex, repo.RetrieveStatus(fullPathFileA)); } } @@ -625,11 +625,11 @@ public void CheckoutRetainsIgnoredChanges() "bin/some_ignored_file.txt", "hello from this ignored file."); - Assert.Equal(1, repo.RetrieveStatus().Ignored.Count()); + Assert.Single(repo.RetrieveStatus(new StatusOptions { IncludeIgnored = true }).Ignored); 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.RetrieveStatus(ignoredFilePath)); @@ -652,11 +652,11 @@ public void ForceCheckoutRetainsIgnoredChanges() "bin/some_ignored_file.txt", "hello from this ignored file."); - Assert.Equal(1, repo.RetrieveStatus().Ignored.Count()); + Assert.Single(repo.RetrieveStatus(new StatusOptions { IncludeIgnored = true }).Ignored); Assert.Equal(FileStatus.Ignored, repo.RetrieveStatus(ignoredFilePath)); - repo.Checkout(otherBranchName, new CheckoutOptions() { CheckoutModifiers = CheckoutModifiers.Force }); + Commands.Checkout(repo, otherBranchName, new CheckoutOptions() { CheckoutModifiers = CheckoutModifiers.Force }); // Verify that the ignored file still exists. Assert.Equal(FileStatus.Ignored, repo.RetrieveStatus(ignoredFilePath)); @@ -680,12 +680,12 @@ public void CheckoutBranchSnapshot() // Add commit to master string fullPath = Touch(repo.Info.WorkingDirectory, originalFilePath, "Update : hello from master branch!\n"); - repo.Stage(fullPath); + Commands.Stage(repo, fullPath); repo.Commit("2nd commit", Constants.Signature, Constants.Signature); Assert.False(repo.Info.IsHeadDetached); - repo.Checkout(initial); + Commands.Checkout(repo, initial); // Head should point at initial commit. Assert.Equal(repo.Head.Tip, initialCommit); @@ -712,7 +712,7 @@ public void CheckingOutRemoteBranchResultsInDetachedHead(string remoteBranchSpec // Set the working directory to the current head ResetAndCleanWorkingDirectory(repo); - repo.Checkout(remoteBranchSpec); + Commands.Checkout(repo, remoteBranchSpec); // Verify that HEAD is detached. Assert.Equal(repo.Refs["HEAD"].TargetIdentifier, repo.Branches["origin/master"].Tip.Sha); @@ -733,7 +733,7 @@ public void CheckingOutABranchDoesNotAlterBinaryFiles() // The blob actually exists in the object database with the correct Sha Assert.Equal(expectedSha, repo.Lookup(expectedSha).Sha); - repo.Checkout("refs/heads/logo", new CheckoutOptions() { CheckoutModifiers = CheckoutModifiers.Force }); + Commands.Checkout(repo, "refs/heads/logo", new CheckoutOptions() { CheckoutModifiers = CheckoutModifiers.Force }); // The Index has been updated as well with the blob Assert.Equal(expectedSha, repo.Index["square-logo.png"].Id.Sha); @@ -748,7 +748,7 @@ public void CheckingOutABranchDoesNotAlterBinaryFiles() [Theory] [InlineData("a447ba2ca8")] - [InlineData("refs/tags/lw")] + [InlineData("lw")] [InlineData("e90810^{}")] public void CheckoutFromDetachedHead(string commitPointer) { @@ -761,9 +761,9 @@ public void CheckoutFromDetachedHead(string commitPointer) var commitSha = repo.Lookup(commitPointer).Sha; - Branch initialHead = repo.Checkout("6dcf9bf"); + Branch initialHead = Commands.Checkout(repo, "6dcf9bf"); - repo.Checkout(commitPointer); + Commands.Checkout(repo, commitPointer); // Assert reflog entry is created var reflogEntry = repo.Refs.Log(repo.Refs.Head).First(); @@ -785,16 +785,18 @@ public void CheckoutBranchFromDetachedHead() ResetAndCleanWorkingDirectory(repo); Assert.False(repo.RetrieveStatus().IsDirty); - Branch initialHead = repo.Checkout("6dcf9bf"); + Branch initialHead = Commands.Checkout(repo, "6dcf9bf"); Assert.True(repo.Info.IsHeadDetached); - Branch newHead = repo.Checkout(repo.Branches["master"]); + 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, DateTimeOffset.Now); + initialHead.Tip.Id, newHead.Tip.Id, Constants.Identity, before); } } @@ -810,10 +812,10 @@ public void CheckoutBranchByShortNameAttachesTheHead(string shortBranchName, str ResetAndCleanWorkingDirectory(repo); Assert.False(repo.RetrieveStatus().IsDirty); - repo.Checkout("6dcf9bf"); + Commands.Checkout(repo, "6dcf9bf"); Assert.True(repo.Info.IsHeadDetached); - var branch = repo.Checkout(shortBranchName); + var branch = Commands.Checkout(repo, shortBranchName); Assert.False(repo.Info.IsHeadDetached); Assert.Equal(referenceName, repo.Head.CanonicalName); @@ -831,11 +833,11 @@ public void CheckoutPreviousCheckedOutBranch() ResetAndCleanWorkingDirectory(repo); Assert.False(repo.RetrieveStatus().IsDirty); - Branch previousHead = repo.Checkout("i-do-numbers"); - repo.Checkout("diff-test-cases"); + Branch previousHead = Commands.Checkout(repo, "i-do-numbers"); + Commands.Checkout(repo, "diff-test-cases"); //Go back to previous branch checked out - var branch = repo.Checkout(@"@{-1}"); + var branch = Commands.Checkout(repo, @"@{-1}"); Assert.False(repo.Info.IsHeadDetached); Assert.Equal(previousHead.CanonicalName, repo.Head.CanonicalName); @@ -858,33 +860,36 @@ public void CheckoutCurrentReference() var reflogEntriesCount = repo.Refs.Log(repo.Refs.Head).Count(); // Checkout branch - repo.Checkout(master); + Commands.Checkout(repo, master); Assert.Equal(reflogEntriesCount, repo.Refs.Log(repo.Refs.Head).Count()); + var before = DateTimeOffset.Now.TruncateMilliseconds(); + // Checkout in detached mode - repo.Checkout(master.Tip.Sha); + Commands.Checkout(repo, master.Tip.Sha); Assert.True(repo.Info.IsHeadDetached); AssertRefLogEntry(repo, "HEAD", - string.Format("checkout: moving from master to {0}", master.Tip.Sha), master.Tip.Id, master.Tip.Id, Constants.Identity, DateTimeOffset.Now); + string.Format("checkout: moving from master to {0}", master.Tip.Sha), + master.Tip.Id, master.Tip.Id, Constants.Identity, before); // Checkout detached "HEAD" => nothing should happen reflogEntriesCount = repo.Refs.Log(repo.Refs.Head).Count(); - repo.Checkout(repo.Head); + Commands.Checkout(repo, repo.Head); Assert.Equal(reflogEntriesCount, repo.Refs.Log(repo.Refs.Head).Count()); // Checkout attached "HEAD" => nothing should happen - repo.Checkout("master"); + Commands.Checkout(repo, "master"); reflogEntriesCount = repo.Refs.Log(repo.Refs.Head).Count(); - repo.Checkout(repo.Head); + Commands.Checkout(repo, repo.Head); Assert.Equal(reflogEntriesCount, repo.Refs.Log(repo.Refs.Head).Count()); - repo.Checkout("HEAD"); + Commands.Checkout(repo, "HEAD"); Assert.Equal(reflogEntriesCount, repo.Refs.Log(repo.Refs.Head).Count()); } @@ -896,7 +901,7 @@ public void CheckoutLowerCasedHeadThrows() string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { - Assert.Throws(() => repo.Checkout("head")); + Assert.Throws(() => Commands.Checkout(repo, "head")); } } @@ -908,10 +913,10 @@ public void CanCheckoutAttachedHead() { Assert.False(repo.Info.IsHeadDetached); - repo.Checkout(repo.Head); + Commands.Checkout(repo, repo.Head); Assert.False(repo.Info.IsHeadDetached); - repo.Checkout("HEAD"); + Commands.Checkout(repo, "HEAD"); Assert.False(repo.Info.IsHeadDetached); } } @@ -922,14 +927,14 @@ public void CanCheckoutDetachedHead() string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { - repo.Checkout(repo.Head.Tip.Sha); + Commands.Checkout(repo, repo.Head.Tip.Sha); Assert.True(repo.Info.IsHeadDetached); - repo.Checkout(repo.Head); + Commands.Checkout(repo, repo.Head); Assert.True(repo.Info.IsHeadDetached); - repo.Checkout("HEAD"); + Commands.Checkout(repo, "HEAD"); Assert.True(repo.Info.IsHeadDetached); } } @@ -947,13 +952,13 @@ public void CanCheckoutPath(string originalBranch, string checkoutFrom, string p // Set the working directory to the current head ResetAndCleanWorkingDirectory(repo); - repo.Checkout(originalBranch); + Commands.Checkout(repo, originalBranch); Assert.False(repo.RetrieveStatus().IsDirty); repo.CheckoutPaths(checkoutFrom, new[] { path }); Assert.Equal(expectedStatus, repo.RetrieveStatus(path)); - Assert.Equal(1, repo.RetrieveStatus().Count()); + Assert.Single(repo.RetrieveStatus()); } } @@ -990,8 +995,7 @@ public void CannotCheckoutPathsWithEmptyOrNullPathArgument() Assert.False(repo.RetrieveStatus().IsDirty); // Passing null 'paths' parameter should throw - Assert.Throws(typeof(ArgumentNullException), - () => repo.CheckoutPaths("i-do-numbers", null)); + Assert.Throws(() => repo.CheckoutPaths("i-do-numbers", null)); // Passing empty list should do nothing repo.CheckoutPaths("i-do-numbers", Enumerable.Empty()); @@ -1033,10 +1037,10 @@ private void PopulateBasicRepository(IRepository repo) { // Generate a .gitignore file. string gitIgnoreFilePath = Touch(repo.Info.WorkingDirectory, ".gitignore", "bin"); - repo.Stage(gitIgnoreFilePath); + Commands.Stage(repo, gitIgnoreFilePath); string fullPathFileA = Touch(repo.Info.WorkingDirectory, originalFilePath, originalFileContent); - repo.Stage(fullPathFileA); + Commands.Stage(repo, fullPathFileA); repo.Commit("Initial commit", Constants.Signature, Constants.Signature); diff --git a/LibGit2Sharp.Tests/CherryPickFixture.cs b/LibGit2Sharp.Tests/CherryPickFixture.cs index d9828e266..f4a383fef 100644 --- a/LibGit2Sharp.Tests/CherryPickFixture.cs +++ b/LibGit2Sharp.Tests/CherryPickFixture.cs @@ -19,7 +19,7 @@ public void CanCherryPick(bool fromDetachedHead) { if (fromDetachedHead) { - repo.Checkout(repo.Head.Tip.Id.Sha); + Commands.Checkout(repo, repo.Head.Tip.Id.Sha); } Commit commitToMerge = repo.Branches["fast_forward"].Tip; @@ -46,7 +46,7 @@ public void CherryPickWithConflictDoesNotCommit() using (var repo = new Repository(path)) { var firstBranch = repo.CreateBranch("FirstBranch"); - repo.Checkout(firstBranch); + Commands.Checkout(repo, firstBranch); // Commit with ONE new file to both first & second branch (SecondBranch is created on this commit). AddFileCommitToRepo(repo, sharedBranchFileName); @@ -56,7 +56,7 @@ public void CherryPickWithConflictDoesNotCommit() AddFileCommitToRepo(repo, firstBranchFileName); AddFileCommitToRepo(repo, sharedBranchFileName, "The first branches comment"); // Change file in first branch - repo.Checkout(secondBranch); + Commands.Checkout(repo, secondBranch); // Commit with ONE new file to second branch (FirstBranch and SecondBranch now point to separate commits that both have the same parent commit). AddFileCommitToRepo(repo, secondBranchFileName); AddFileCommitToRepo(repo, sharedBranchFileName, "The second branches comment"); // Change file in second branch @@ -66,7 +66,7 @@ public void CherryPickWithConflictDoesNotCommit() Assert.Equal(CherryPickStatus.Conflicts, cherryPickResult.Status); Assert.Null(cherryPickResult.Commit); - Assert.Equal(1, repo.Index.Conflicts.Count()); + Assert.Single(repo.Index.Conflicts); var conflict = repo.Index.Conflicts.First(); var changes = repo.Diff.Compare(repo.Lookup(conflict.Theirs.Id), repo.Lookup(conflict.Ours.Id)); @@ -126,11 +126,97 @@ public void CanSpecifyConflictFileStrategy(CheckoutFileConflictStrategy conflict } } + [Fact] + public void CanCherryPickCommit() + { + string path = SandboxMergeTestRepo(); + using (var repo = new Repository(path)) + { + var ours = repo.Head.Tip; + + Commit commitToMerge = repo.Branches["fast_forward"].Tip; + + var result = repo.ObjectDatabase.CherryPickCommit(commitToMerge, ours, 0, null); + + Assert.Equal(MergeTreeStatus.Succeeded, result.Status); + Assert.Empty(result.Conflicts); + } + } + + [Fact] + public void CherryPickWithConflictsReturnsConflicts() + { + const string conflictBranchName = "conflicts"; + + string path = SandboxMergeTestRepo(); + using (var repo = new Repository(path)) + { + Branch branch = repo.Branches[conflictBranchName]; + Assert.NotNull(branch); + + var result = repo.ObjectDatabase.CherryPickCommit(branch.Tip, repo.Head.Tip, 0, null); + + Assert.Equal(MergeTreeStatus.Conflicts, result.Status); + Assert.NotEmpty(result.Conflicts); + + } + } + + [Fact] + public void CanCherryPickCommitIntoIndex() + { + string path = SandboxMergeTestRepo(); + using (var repo = new Repository(path)) + { + var ours = repo.Head.Tip; + + Commit commitToMerge = repo.Branches["fast_forward"].Tip; + + using (TransientIndex index = repo.ObjectDatabase.CherryPickCommitIntoIndex(commitToMerge, ours, 0, null)) + { + var tree = index.WriteToTree(); + Assert.Equal(commitToMerge.Tree.Id, tree.Id); + } + } + } + + [Fact] + public void CanCherryPickIntoIndexWithConflicts() + { + const string conflictBranchName = "conflicts"; + + string path = SandboxMergeTestRepo(); + using (var repo = new Repository(path)) + { + Branch branch = repo.Branches[conflictBranchName]; + Assert.NotNull(branch); + + using (TransientIndex index = repo.ObjectDatabase.CherryPickCommitIntoIndex(branch.Tip, repo.Head.Tip, 0, null)) + { + Assert.False(index.IsFullyMerged); + + var conflict = index.Conflicts.First(); + + //Resolve the conflict by taking the blob from branch + var blob = repo.Lookup(conflict.Theirs.Id); + //Add() does not remove conflict entries for the same path, so they must be explicitly removed first. + index.Remove(conflict.Ours.Path); + index.Add(blob, conflict.Ours.Path, Mode.NonExecutableFile); + + Assert.True(index.IsFullyMerged); + var tree = index.WriteToTree(); + + //Since we took the conflicted blob from the branch, the merged result should be the same as the branch. + Assert.Equal(branch.Tip.Tree.Id, tree.Id); + } + } + } + private Commit AddFileCommitToRepo(IRepository repository, string filename, string content = null) { Touch(repository.Info.WorkingDirectory, filename, content); - repository.Stage(filename); + Commands.Stage(repository, filename); return repository.Commit("New commit", Constants.Signature, Constants.Signature); } diff --git a/LibGit2Sharp.Tests/CleanFixture.cs b/LibGit2Sharp.Tests/CleanFixture.cs index f674285c6..39c7a6152 100644 --- a/LibGit2Sharp.Tests/CleanFixture.cs +++ b/LibGit2Sharp.Tests/CleanFixture.cs @@ -14,13 +14,13 @@ public void CanCleanWorkingDirectory() { // Verify that there are the expected number of entries and untracked files Assert.Equal(6, repo.RetrieveStatus().Count()); - Assert.Equal(1, repo.RetrieveStatus().Untracked.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.RetrieveStatus().Count()); - Assert.Equal(0, repo.RetrieveStatus().Untracked.Count()); + Assert.Empty(repo.RetrieveStatus().Untracked); } } diff --git a/LibGit2Sharp.Tests/CloneFixture.cs b/LibGit2Sharp.Tests/CloneFixture.cs index 7fb05048f..09af475fd 100644 --- a/LibGit2Sharp.Tests/CloneFixture.cs +++ b/LibGit2Sharp.Tests/CloneFixture.cs @@ -33,8 +33,8 @@ public void CanClone(string url) Assert.False(repo.Info.IsBare); Assert.True(File.Exists(Path.Combine(scd.RootedDirectoryPath, "master.txt"))); - Assert.Equal(repo.Head.FriendlyName, "master"); - Assert.Equal(repo.Head.Tip.Id.ToString(), "49322bb17d3acc9146f98c97d078513228bbf3c0"); + Assert.Equal("master", repo.Head.FriendlyName); + Assert.Equal("49322bb17d3acc9146f98c97d078513228bbf3c0", repo.Head.Tip.Id.ToString()); } } @@ -74,14 +74,14 @@ private void AssertLocalClone(string url, string path = null, bool isCloningAnEm 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(Path.GetFullPath(BareTestRepoPath)); + var uri = new Uri($"file://{Path.GetFullPath(BareTestRepoPath)}"); AssertLocalClone(uri.AbsoluteUri, BareTestRepoPath); } @@ -195,19 +195,34 @@ public void CanCloneWithCredentials() } } + static Credentials CreateUsernamePasswordCredentials (string user, string pass, bool secure) + { + if (secure) + { + return new SecureUsernamePasswordCredentials + { + Username = user, + Password = Constants.StringToSecureString(pass), + }; + } + + return new UsernamePasswordCredentials + { + Username = user, + Password = pass, + }; + } + [Theory] - [InlineData("https://libgit2@bitbucket.org/libgit2/testgitrepository.git", "libgit3", "libgit3")] - public void CanCloneFromBBWithCredentials(string url, string user, string pass) + [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) => new UsernamePasswordCredentials - { - Username = user, - Password = pass, - } + CredentialsProvider = (_url, _user, _cred) => CreateUsernamePasswordCredentials (user, pass, secure) }); using (var repo = new Repository(clonedRepoPath)) @@ -222,12 +237,64 @@ public void CanCloneFromBBWithCredentials(string url, string user, string pass) } } - [Fact] - public void CloningAnUrlWithoutPathThrows() + [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(); - Assert.Throws(() => Repository.Clone("http://github.com", scd.DirectoryPath)); + InconclusiveIf( + () => + certType == typeof (CertificateSsh) && !GlobalSettings.Version.Features.HasFlag(BuiltInFeatures.Ssh), + "SSH not supported"); + + bool wasCalled = false; + bool checksHappy = false; + + var options = new CloneOptions { + 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] @@ -286,7 +353,7 @@ private class CloneCallbackInfo [Fact] public void CanRecursivelyCloneSubmodules() { - var uri = new Uri(Path.GetFullPath(SandboxSubmoduleSmallTestRepo())); + var uri = new Uri($"file://{Path.GetFullPath(SandboxSubmoduleSmallTestRepo())}"); var scd = BuildSelfCleaningDirectory(); string relativeSubmodulePath = "submodule_target_wd"; @@ -437,7 +504,7 @@ public void CanRecursivelyCloneSubmodules() [Fact] public void CanCancelRecursiveClone() { - var uri = new Uri(Path.GetFullPath(SandboxSubmoduleSmallTestRepo())); + var uri = new Uri($"file://{Path.GetFullPath(SandboxSubmoduleSmallTestRepo())}"); var scd = BuildSelfCleaningDirectory(); string relativeSubmodulePath = "submodule_target_wd"; @@ -482,5 +549,54 @@ public void CanCancelRecursiveClone() } } + + [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() + { + FetchOptions = new 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() + { + FetchOptions = new 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() + { + FetchOptions = new FetchOptions { CustomHeaders = new String[] { knownHeader } } + }; + + var clonedRepoPath = Repository.Clone(url, scd.DirectoryPath, cloneOptions); + Assert.True(Directory.Exists(clonedRepoPath)); + } } } diff --git a/LibGit2Sharp.Tests/CommitFixture.cs b/LibGit2Sharp.Tests/CommitFixture.cs index 18fec45ba..f555e7874 100644 --- a/LibGit2Sharp.Tests/CommitFixture.cs +++ b/LibGit2Sharp.Tests/CommitFixture.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text; using LibGit2Sharp.Core; using LibGit2Sharp.Tests.TestHelpers; using Xunit; @@ -34,11 +35,11 @@ public void CanCorrectlyCountCommitsWhenSwitchingToAnotherBranch() 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); } @@ -69,7 +70,7 @@ public void CanEnumerateCommitsInDetachedHeadState() 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()); } @@ -92,7 +93,7 @@ public void CanEnumerateCommitsFromSha() string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { - foreach (Commit commit in repo.Commits.QueryBy(new CommitFilter { Since = "a4a7dce85cf63874e984719f4fdd239f5145052f" })) + foreach (Commit commit in repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = "a4a7dce85cf63874e984719f4fdd239f5145052f" })) { Assert.NotNull(commit); count++; @@ -107,9 +108,9 @@ public void QueryingTheCommitHistoryWithUnknownShaOrInvalidEntryPointThrows() string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { - Assert.Throws(() => repo.Commits.QueryBy(new CommitFilter { Since = Constants.UnknownSha }).Count()); - Assert.Throws(() => repo.Commits.QueryBy(new CommitFilter { Since = "refs/heads/deadbeef" }).Count()); - Assert.Throws(() => repo.Commits.QueryBy(new CommitFilter { Since = null }).Count()); + Assert.Throws(() => repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = Constants.UnknownSha }).Count()); + Assert.Throws(() => repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = "refs/heads/deadbeef" }).Count()); + Assert.Throws(() => repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = null }).Count()); } } @@ -121,8 +122,8 @@ public void QueryingTheCommitHistoryFromACorruptedReferenceThrows() { CreateCorruptedDeadBeefHead(repo.Info.Path); - Assert.Throws(() => repo.Commits.QueryBy(new CommitFilter { Since = repo.Branches["deadbeef"] }).Count()); - Assert.Throws(() => repo.Commits.QueryBy(new CommitFilter { Since = repo.Refs["refs/heads/deadbeef"] }).Count()); + Assert.Throws(() => repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = repo.Branches["deadbeef"] }).Count()); + Assert.Throws(() => repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = repo.Refs["refs/heads/deadbeef"] }).Count()); } } @@ -132,8 +133,8 @@ public void QueryingTheCommitHistoryWithBadParamsThrows() string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { - Assert.Throws(() => repo.Commits.QueryBy(new CommitFilter { Since = string.Empty })); - Assert.Throws(() => repo.Commits.QueryBy(new CommitFilter { Since = null })); + Assert.Throws(() => repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = string.Empty })); + Assert.Throws(() => repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = null })); Assert.Throws(() => repo.Commits.QueryBy(default(CommitFilter))); } } @@ -150,12 +151,12 @@ public void CanEnumerateCommitsWithReverseTimeSorting() { foreach (Commit commit in repo.Commits.QueryBy(new CommitFilter { - Since = "a4a7dce85cf63874e984719f4fdd239f5145052f", + IncludeReachableFrom = "a4a7dce85cf63874e984719f4fdd239f5145052f", SortBy = CommitSortStrategies.Time | CommitSortStrategies.Reverse })) { Assert.NotNull(commit); - Assert.True(commit.Sha.StartsWith(reversedShas[count])); + Assert.StartsWith(reversedShas[count], commit.Sha); count++; } } @@ -170,7 +171,7 @@ public void CanEnumerateCommitsWithReverseTopoSorting() { List commits = repo.Commits.QueryBy(new CommitFilter { - Since = "a4a7dce85cf63874e984719f4fdd239f5145052f", + IncludeReachableFrom = "a4a7dce85cf63874e984719f4fdd239f5145052f", SortBy = CommitSortStrategies.Time | CommitSortStrategies.Reverse }).ToList(); foreach (Commit commit in commits) @@ -189,7 +190,7 @@ public void CanEnumerateCommitsWithReverseTopoSorting() public void CanSimplifyByFirstParent() { AssertEnumerationOfCommits( - repo => new CommitFilter { Since = repo.Head, FirstParentOnly = true }, + repo => new CommitFilter { IncludeReachableFrom = repo.Head, FirstParentOnly = true }, new[] { "4c062a6", "be3563a", "9fd738e", @@ -203,7 +204,7 @@ public void CanGetParentsCount() string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { - Assert.Equal(1, repo.Commits.First().Parents.Count()); + Assert.Single(repo.Commits.First().Parents); } } @@ -216,12 +217,12 @@ public void CanEnumerateCommitsWithTimeSorting() { foreach (Commit commit in repo.Commits.QueryBy(new CommitFilter { - Since = "a4a7dce85cf63874e984719f4fdd239f5145052f", + IncludeReachableFrom = "a4a7dce85cf63874e984719f4fdd239f5145052f", SortBy = CommitSortStrategies.Time })) { Assert.NotNull(commit); - Assert.True(commit.Sha.StartsWith(expectedShas[count])); + Assert.StartsWith(expectedShas[count], commit.Sha); count++; } } @@ -236,7 +237,7 @@ public void CanEnumerateCommitsWithTopoSorting() { List commits = repo.Commits.QueryBy(new CommitFilter { - Since = "a4a7dce85cf63874e984719f4fdd239f5145052f", + IncludeReachableFrom = "a4a7dce85cf63874e984719f4fdd239f5145052f", SortBy = CommitSortStrategies.Topological }).ToList(); foreach (Commit commit in commits) @@ -255,7 +256,7 @@ public void CanEnumerateCommitsWithTopoSorting() public void CanEnumerateFromHead() { AssertEnumerationOfCommits( - repo => new CommitFilter { Since = repo.Head }, + repo => new CommitFilter { IncludeReachableFrom = repo.Head }, new[] { "4c062a6", "be3563a", "c47800c", "9fd738e", @@ -274,10 +275,10 @@ public void CanEnumerateFromDetachedHead() repoClone.RemoveUntrackedFiles(); string headSha = repoClone.Head.Tip.Sha; - repoClone.Checkout(headSha); + Commands.Checkout(repoClone, headSha); AssertEnumerationOfCommitsInRepo(repoClone, - repo => new CommitFilter { Since = repo.Head }, + repo => new CommitFilter { IncludeReachableFrom = repo.Head }, new[] { "32eab9c", "592d3c8", "4c062a6", @@ -291,7 +292,7 @@ public void CanEnumerateFromDetachedHead() public void CanEnumerateUsingTwoHeadsAsBoundaries() { AssertEnumerationOfCommits( - repo => new CommitFilter { Since = "HEAD", Until = "refs/heads/br2" }, + repo => new CommitFilter { IncludeReachableFrom = "HEAD", ExcludeReachableFrom = "refs/heads/br2" }, new[] { "4c062a6", "be3563a" } ); } @@ -300,7 +301,7 @@ public void CanEnumerateUsingTwoHeadsAsBoundaries() public void CanEnumerateUsingImplicitHeadAsSinceBoundary() { AssertEnumerationOfCommits( - repo => new CommitFilter { Until = "refs/heads/br2" }, + repo => new CommitFilter { ExcludeReachableFrom = "refs/heads/br2" }, new[] { "4c062a6", "be3563a" } ); } @@ -309,7 +310,7 @@ public void CanEnumerateUsingImplicitHeadAsSinceBoundary() public void CanEnumerateUsingTwoAbbreviatedShasAsBoundaries() { AssertEnumerationOfCommits( - repo => new CommitFilter { Since = "a4a7dce", Until = "4a202b3" }, + repo => new CommitFilter { IncludeReachableFrom = "a4a7dce", ExcludeReachableFrom = "4a202b3" }, new[] { "a4a7dce", "c47800c", "9fd738e" } ); } @@ -318,7 +319,7 @@ public void CanEnumerateUsingTwoAbbreviatedShasAsBoundaries() public void CanEnumerateCommitsFromTwoHeads() { AssertEnumerationOfCommits( - repo => new CommitFilter { Since = new[] { "refs/heads/br2", "refs/heads/master" } }, + repo => new CommitFilter { IncludeReachableFrom = new[] { "refs/heads/br2", "refs/heads/master" } }, new[] { "4c062a6", "a4a7dce", "be3563a", "c47800c", @@ -330,7 +331,7 @@ public void CanEnumerateCommitsFromTwoHeads() public void CanEnumerateCommitsFromMixedStartingPoints() { AssertEnumerationOfCommits( - repo => new CommitFilter { Since = new object[] { repo.Branches["br2"], + repo => new CommitFilter { IncludeReachableFrom = new object[] { repo.Branches["br2"], "refs/heads/master", new ObjectId("e90810b8df3e80c413d903f631643c716887138d") } }, new[] @@ -345,7 +346,7 @@ public void CanEnumerateCommitsFromMixedStartingPoints() public void CanEnumerateCommitsUsingGlob() { AssertEnumerationOfCommits( - repo => new CommitFilter { Since = repo.Refs.FromGlob("refs/heads/*") }, + repo => new CommitFilter { IncludeReachableFrom = repo.Refs.FromGlob("refs/heads/*") }, new[] { "4c062a6", "e90810b", "6dcf9bf", "a4a7dce", "be3563a", "c47800c", "9fd738e", "4a202b3", "41bc8c6", "5001298", "5b5b025", "8496071" @@ -356,7 +357,7 @@ public void CanEnumerateCommitsUsingGlob() public void CanHideCommitsUsingGlob() { AssertEnumerationOfCommits( - repo => new CommitFilter { Since = "refs/heads/packed-test", Until = repo.Refs.FromGlob("*/packed") }, + repo => new CommitFilter { IncludeReachableFrom = "refs/heads/packed-test", ExcludeReachableFrom = repo.Refs.FromGlob("*/packed") }, new[] { "4a202b3", "5b5b025", "8496071" @@ -378,7 +379,7 @@ public void CanEnumerateCommitsFromATagAnnotation() private void CanEnumerateCommitsFromATag(Func transformer) { AssertEnumerationOfCommits( - repo => new CommitFilter { Since = transformer(repo.Tags["test"]) }, + repo => new CommitFilter { IncludeReachableFrom = transformer(repo.Tags["test"]) }, new[] { "e90810b", "6dcf9bf", } ); } @@ -389,7 +390,7 @@ public void CanEnumerateAllCommits() AssertEnumerationOfCommits( repo => new CommitFilter { - Since = repo.Refs.OrderBy(r => r.CanonicalName, StringComparer.Ordinal), + IncludeReachableFrom = repo.Refs.OrderBy(r => r.CanonicalName, StringComparer.Ordinal), }, new[] { @@ -404,7 +405,7 @@ public void CanEnumerateAllCommits() public void CanEnumerateCommitsFromATagWhichPointsToABlob() { AssertEnumerationOfCommits( - repo => new CommitFilter { Since = repo.Tags["point_to_blob"] }, + repo => new CommitFilter { IncludeReachableFrom = repo.Tags["point_to_blob"] }, new string[] { }); } @@ -419,7 +420,7 @@ public void CanEnumerateCommitsFromATagWhichPointsToATree() Tag tag = repo.ApplyTag("point_to_tree", headTreeSha); AssertEnumerationOfCommitsInRepo(repo, - r => new CommitFilter { Since = tag }, + r => new CommitFilter { IncludeReachableFrom = tag }, new string[] { }); } } @@ -474,16 +475,16 @@ 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); } } @@ -538,55 +539,35 @@ public void DirectlyAccessingAnUnknownTreeEntryOfTheCommitReturnsNull() } } - [Theory] - [InlineData(null, "x@example.com")] - [InlineData("", "x@example.com")] - [InlineData("X", null)] - [InlineData("X", "")] - public void CommitWithInvalidSignatureConfigThrows(string name, string email) - { - string repoPath = InitNewRepository(); - string configPath = CreateConfigurationWithDummyUser(name, email); - var options = new RepositoryOptions { GlobalConfigurationLocation = configPath }; - - using (var repo = new Repository(repoPath, options)) - { - Assert.Equal(name, repo.Config.GetValueOrDefault("user.name")); - Assert.Equal(email, repo.Config.GetValueOrDefault("user.email")); - - Assert.Throws( - () => repo.Commit("Initial egotistic commit", new CommitOptions { AllowEmptyCommit = true })); - } - } - [Fact] public void CanCommitWithSignatureFromConfig() { string repoPath = InitNewRepository(); - string configPath = CreateConfigurationWithDummyUser(Constants.Signature); - var options = new RepositoryOptions { GlobalConfigurationLocation = configPath }; - using (var repo = new Repository(repoPath, options)) + using (var repo = new Repository(repoPath)) { + CreateConfigurationWithDummyUser(repo, Constants.Identity); string dir = repo.Info.Path; Assert.True(Path.IsPathRooted(dir)); Assert.True(Directory.Exists(dir)); const string relativeFilepath = "new.txt"; string filePath = Touch(repo.Info.WorkingDirectory, relativeFilepath, "null"); - repo.Stage(relativeFilepath); + Commands.Stage(repo, relativeFilepath); File.AppendAllText(filePath, "token\n"); - repo.Stage(relativeFilepath); + Commands.Stage(repo, relativeFilepath); Assert.Null(repo.Head[relativeFilepath]); - Commit commit = repo.Commit("Initial egotistic commit"); + Signature signature = repo.Config.BuildSignature(DateTimeOffset.Now); + + Commit commit = repo.Commit("Initial egotistic commit", signature, signature); AssertBlobContent(repo.Head[relativeFilepath], "nulltoken\n"); AssertBlobContent(commit[relativeFilepath], "nulltoken\n"); - AssertCommitSignaturesAre(commit, Constants.Signature); + AssertCommitIdentitiesAre(commit, Constants.Identity); } } @@ -609,8 +590,8 @@ public void CommitParentsAreMergeHeads() Assert.Equal(CurrentOperation.None, repo.Info.CurrentOperation); Assert.Equal(2, newMergedCommit.Parents.Count()); - Assert.Equal(newMergedCommit.Parents.First().Sha, "c47800c7266a2be04c571c04d5a6614691ea99bd"); - Assert.Equal(newMergedCommit.Parents.Skip(1).First().Sha, "9fd738e8f7967c078dceed8190330fc8648ee56a"); + Assert.Equal("c47800c7266a2be04c571c04d5a6614691ea99bd", newMergedCommit.Parents.First().Sha); + Assert.Equal("9fd738e8f7967c078dceed8190330fc8648ee56a", newMergedCommit.Parents.Skip(1).First().Sha); // Assert reflog entry is created var reflogEntry = repo.Refs.Log(repo.Refs.Head).First(); @@ -634,7 +615,7 @@ public void CommitCleansUpMergeMetadata() const string relativeFilepath = "new.txt"; Touch(repo.Info.WorkingDirectory, relativeFilepath, "this is a new file"); - repo.Stage(relativeFilepath); + Commands.Stage(repo, relativeFilepath); string mergeHeadPath = Touch(repo.Info.Path, "MERGE_HEAD", "abcdefabcdefabcdefabcdefabcdefabcdefabcd"); string mergeMsgPath = Touch(repo.Info.Path, "MERGE_MSG", "This is a dummy merge.\n"); @@ -671,9 +652,9 @@ public void CanCommitALittleBit() const string relativeFilepath = "new.txt"; string filePath = Touch(repo.Info.WorkingDirectory, relativeFilepath, "null"); - repo.Stage(relativeFilepath); + Commands.Stage(repo, relativeFilepath); File.AppendAllText(filePath, "token\n"); - repo.Stage(relativeFilepath); + Commands.Stage(repo, relativeFilepath); Assert.Null(repo.Head[relativeFilepath]); @@ -682,23 +663,25 @@ public void CanCommitALittleBit() const string shortMessage = "Initial egotistic commit"; const string commitMessage = shortMessage + "\n\nOnly the coolest commits from us"; + var before = DateTimeOffset.Now.TruncateMilliseconds(); + Commit commit = repo.Commit(commitMessage, author, author); AssertBlobContent(repo.Head[relativeFilepath], "nulltoken\n"); AssertBlobContent(commit[relativeFilepath], "nulltoken\n"); - Assert.Equal(0, commit.Parents.Count()); + Assert.Empty(commit.Parents); Assert.False(repo.Info.IsHeadUnborn); // Assert a reflog entry is created on HEAD - Assert.Equal(1, repo.Refs.Log("HEAD").Count()); + Assert.Single(repo.Refs.Log("HEAD")); var reflogEntry = repo.Refs.Log("HEAD").First(); Assert.Equal(identity.Name, reflogEntry.Committer.Name); Assert.Equal(identity.Email, reflogEntry.Committer.Email); var now = DateTimeOffset.Now; - Assert.InRange(reflogEntry.Committer.When, now - TimeSpan.FromSeconds(1), now); + Assert.InRange(reflogEntry.Committer.When, before, now); Assert.Equal(commit.Id, reflogEntry.To); Assert.Equal(ObjectId.Zero, reflogEntry.From); @@ -706,11 +689,11 @@ public void CanCommitALittleBit() // Assert a reflog entry is created on HEAD target var targetCanonicalName = repo.Refs.Head.TargetIdentifier; - Assert.Equal(1, repo.Refs.Log(targetCanonicalName).Count()); + Assert.Single(repo.Refs.Log(targetCanonicalName)); Assert.Equal(commit.Id, repo.Refs.Log(targetCanonicalName).First().To); File.WriteAllText(filePath, "nulltoken commits!\n"); - repo.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); @@ -718,7 +701,7 @@ public void CanCommitALittleBit() AssertBlobContent(repo.Head[relativeFilepath], "nulltoken commits!\n"); AssertBlobContent(commit2[relativeFilepath], "nulltoken commits!\n"); - Assert.Equal(1, commit2.Parents.Count()); + Assert.Single(commit2.Parents); Assert.Equal(commit.Id, commit2.Parents.First().Id); // Assert the reflog is shifted @@ -726,19 +709,19 @@ public void CanCommitALittleBit() Assert.Equal(reflogEntry.To, repo.Refs.Log("HEAD").First().From); Branch firstCommitBranch = repo.CreateBranch("davidfowl-rules", commit); - repo.Checkout(firstCommitBranch); + Commands.Checkout(repo, firstCommitBranch); File.WriteAllText(filePath, "davidfowl commits!\n"); var author3 = new Signature("David Fowler", "david.fowler@microsoft.com", author.When.AddSeconds(2)); - repo.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"); @@ -757,7 +740,7 @@ private static void AddCommitToRepo(string path) { const string relativeFilepath = "test.txt"; Touch(repo.Info.WorkingDirectory, relativeFilepath, "test\n"); - repo.Stage(relativeFilepath); + Commands.Stage(repo, relativeFilepath); var author = new Signature("nulltoken", "emeric.fermas@gmail.com", DateTimeOffset.Parse("Wed, Dec 14 2011 08:29:03 +0100")); repo.Commit("Initial commit", author, author); @@ -793,17 +776,17 @@ public void CanAmendARootCommit() using (var repo = new Repository(repoPath)) { - Assert.Equal(1, repo.Head.Commits.Count()); + Assert.Single(repo.Head.Commits); Commit originalCommit = repo.Head.Tip; - Assert.Equal(0, originalCommit.Parents.Count()); + Assert.Empty(originalCommit.Parents); CreateAndStageANewFile(repo); Commit amendedCommit = repo.Commit("I'm rewriting the history!", Constants.Signature, Constants.Signature, new CommitOptions { AmendPreviousCommit = true }); - Assert.Equal(1, repo.Head.Commits.Count()); + Assert.Single(repo.Head.Commits); AssertCommitHasBeenAmended(repo, amendedCommit, originalCommit); } @@ -824,6 +807,8 @@ public void CanAmendACommitWithMoreThanOneParent() CreateAndStageANewFile(repo); const string commitMessage = "I'm rewriting the history!"; + var before = DateTimeOffset.Now.TruncateMilliseconds(); + Commit amendedCommit = repo.Commit(commitMessage, Constants.Signature, Constants.Signature, new CommitOptions { AmendPreviousCommit = true }); @@ -833,7 +818,7 @@ public void CanAmendACommitWithMoreThanOneParent() string.Format("commit (amend): {0}", commitMessage), mergedCommit.Id, amendedCommit.Id, - Constants.Identity, DateTimeOffset.Now); + Constants.Identity, before); } } @@ -841,7 +826,7 @@ private static void CreateAndStageANewFile(IRepository repo) { string relativeFilepath = string.Format("new-file-{0}.txt", Path.GetRandomFileName()); Touch(repo.Info.WorkingDirectory, relativeFilepath, "brand new content\n"); - repo.Stage(relativeFilepath); + Commands.Stage(repo, relativeFilepath); } private static void AssertCommitHasBeenAmended(IRepository repo, Commit amendedCommit, Commit originalCommit) @@ -876,10 +861,10 @@ public void CanRetrieveChildrenOfASpecificCommit() var filter = new CommitFilter { /* Revwalk from all the refs (git log --all) ... */ - Since = repo.Refs, + IncludeReachableFrom = repo.Refs, /* ... and stop when the parent is reached */ - Until = parentSha + ExcludeReachableFrom = parentSha }; var commits = repo.Commits.QueryBy(filter); @@ -904,9 +889,9 @@ public void CanCorrectlyDistinguishAuthorFromCommitter() 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); @@ -930,10 +915,10 @@ public void CanCommitOnOrphanedBranch() const string relativeFilepath = "test.txt"; Touch(repo.Info.WorkingDirectory, relativeFilepath, "test\n"); - repo.Stage(relativeFilepath); + Commands.Stage(repo, relativeFilepath); repo.Commit("Initial commit", Constants.Signature, Constants.Signature); - Assert.Equal(1, repo.Head.Commits.Count()); + Assert.Single(repo.Head.Commits); } } @@ -1015,8 +1000,8 @@ public void CanCommitAnEmptyCommitWhenMerging() Commit newMergedCommit = repo.Commit("Merge commit", Constants.Signature, Constants.Signature); Assert.Equal(2, newMergedCommit.Parents.Count()); - Assert.Equal(newMergedCommit.Parents.First().Sha, "32eab9cb1f450b5fe7ab663462b77d7f4b703344"); - Assert.Equal(newMergedCommit.Parents.Skip(1).First().Sha, "f705abffe7015f2beacf2abe7a36583ebee3487e"); + Assert.Equal("32eab9cb1f450b5fe7ab663462b77d7f4b703344", newMergedCommit.Parents.First().Sha); + Assert.Equal("f705abffe7015f2beacf2abe7a36583ebee3487e", newMergedCommit.Parents.Skip(1).First().Sha); } } @@ -1046,20 +1031,143 @@ public void CanNotAmendACommitInAWayThatWouldLeadTheNewCommitToBecomeEmpty() using (var repo = new Repository(repoPath)) { Touch(repo.Info.WorkingDirectory, "test.txt", "test\n"); - repo.Stage("test.txt"); + Commands.Stage(repo, "test.txt"); repo.Commit("Initial commit", Constants.Signature, Constants.Signature); Touch(repo.Info.WorkingDirectory, "new.txt", "content\n"); - repo.Stage("new.txt"); + Commands.Stage(repo, "new.txt"); repo.Commit("One commit", Constants.Signature, Constants.Signature); - repo.Remove("new.txt"); + Commands.Remove(repo, "new.txt"); Assert.Throws(() => repo.Commit("Oops", Constants.Signature, Constants.Signature, new CommitOptions { AmendPreviousCommit = true })); } } + + [Fact] + public void CanPrettifyAMessage() + { + string input = "# Comment\nA line that will remain\n# And another character\n\n\n"; + string expected = "A line that will remain\n"; + + Assert.Equal(expected, Commit.PrettifyMessage(input, '#')); + Assert.Equal(expected, Commit.PrettifyMessage(input.Replace('#', ';'), ';')); + } + + private readonly string signedCommit = + "tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904\n" + + "parent 8496071c1b46c854b31185ea97743be6a8774479\n" + + "author Ben Burkert 1358451456 -0800\n" + + "committer Ben Burkert 1358451456 -0800\n" + + "gpgsig -----BEGIN PGP SIGNATURE-----\n" + + " Version: GnuPG v1.4.12 (Darwin)\n" + + " \n" + + " iQIcBAABAgAGBQJQ+FMIAAoJEH+LfPdZDSs1e3EQAJMjhqjWF+WkGLHju7pTw2al\n" + + " o6IoMAhv0Z/LHlWhzBd9e7JeCnanRt12bAU7yvYp9+Z+z+dbwqLwDoFp8LVuigl8\n" + + " JGLcnwiUW3rSvhjdCp9irdb4+bhKUnKUzSdsR2CK4/hC0N2i/HOvMYX+BRsvqweq\n" + + " AsAkA6dAWh+gAfedrBUkCTGhlNYoetjdakWqlGL1TiKAefEZrtA1TpPkGn92vbLq\n" + + " SphFRUY9hVn1ZBWrT3hEpvAIcZag3rTOiRVT1X1flj8B2vGCEr3RrcwOIZikpdaW\n" + + " who/X3xh/DGbI2RbuxmmJpxxP/8dsVchRJJzBwG+yhwU/iN3MlV2c5D69tls/Dok\n" + + " 6VbyU4lm/ae0y3yR83D9dUlkycOnmmlBAHKIZ9qUts9X7mWJf0+yy2QxJVpjaTGG\n" + + " cmnQKKPeNIhGJk2ENnnnzjEve7L7YJQF6itbx5VCOcsGh3Ocb3YR7DMdWjt7f8pu\n" + + " c6j+q1rP7EpE2afUN/geSlp5i3x8aXZPDj67jImbVCE/Q1X9voCtyzGJH7MXR0N9\n" + + " ZpRF8yzveRfMH8bwAJjSOGAFF5XkcR/RNY95o+J+QcgBLdX48h+ZdNmUf6jqlu3J\n" + + " 7KmTXXQcOVpN6dD3CmRFsbjq+x6RHwa8u1iGn+oIkX908r97ckfB/kHKH7ZdXIJc\n" + + " cpxtDQQMGYFpXK/71stq\n" + + " =ozeK\n" + + " -----END PGP SIGNATURE-----\n" + + "\n" + + "a simple commit which works\n"; + + private readonly string signatureData = + "-----BEGIN PGP SIGNATURE-----\n" + + "Version: GnuPG v1.4.12 (Darwin)\n" + + "\n" + + "iQIcBAABAgAGBQJQ+FMIAAoJEH+LfPdZDSs1e3EQAJMjhqjWF+WkGLHju7pTw2al\n" + + "o6IoMAhv0Z/LHlWhzBd9e7JeCnanRt12bAU7yvYp9+Z+z+dbwqLwDoFp8LVuigl8\n" + + "JGLcnwiUW3rSvhjdCp9irdb4+bhKUnKUzSdsR2CK4/hC0N2i/HOvMYX+BRsvqweq\n" + + "AsAkA6dAWh+gAfedrBUkCTGhlNYoetjdakWqlGL1TiKAefEZrtA1TpPkGn92vbLq\n" + + "SphFRUY9hVn1ZBWrT3hEpvAIcZag3rTOiRVT1X1flj8B2vGCEr3RrcwOIZikpdaW\n" + + "who/X3xh/DGbI2RbuxmmJpxxP/8dsVchRJJzBwG+yhwU/iN3MlV2c5D69tls/Dok\n" + + "6VbyU4lm/ae0y3yR83D9dUlkycOnmmlBAHKIZ9qUts9X7mWJf0+yy2QxJVpjaTGG\n" + + "cmnQKKPeNIhGJk2ENnnnzjEve7L7YJQF6itbx5VCOcsGh3Ocb3YR7DMdWjt7f8pu\n" + + "c6j+q1rP7EpE2afUN/geSlp5i3x8aXZPDj67jImbVCE/Q1X9voCtyzGJH7MXR0N9\n" + + "ZpRF8yzveRfMH8bwAJjSOGAFF5XkcR/RNY95o+J+QcgBLdX48h+ZdNmUf6jqlu3J\n" + + "7KmTXXQcOVpN6dD3CmRFsbjq+x6RHwa8u1iGn+oIkX908r97ckfB/kHKH7ZdXIJc\n" + + "cpxtDQQMGYFpXK/71stq\n" + + "=ozeK\n" + + "-----END PGP SIGNATURE-----"; + + private readonly string signedData = + "tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904\n" + + "parent 8496071c1b46c854b31185ea97743be6a8774479\n" + + "author Ben Burkert 1358451456 -0800\n" + + "committer Ben Burkert 1358451456 -0800\n" + + "\n" + + "a simple commit which works\n"; + + [Fact] + public void CanExtractSignatureFromCommit() + { + string repoPath = InitNewRepository(); + using (var repo = new Repository(repoPath)) + { + var odb = repo.ObjectDatabase; + var signedId = odb.Write(Encoding.UTF8.GetBytes(signedCommit)); + + // Look up the commit to make sure we wrote something valid + var commit = repo.Lookup(signedId); + Assert.Equal("a simple commit which works\n", commit.Message); + + var signatureInfo = Commit.ExtractSignature(repo, signedId, "gpgsig"); + Assert.Equal(signedData, signatureInfo.SignedData); + Assert.Equal(signatureData, signatureInfo.Signature); + + signatureInfo = Commit.ExtractSignature(repo, signedId); + Assert.Equal(signedData, signatureInfo.SignedData); + Assert.Equal(signatureData, signatureInfo.Signature); + } + } + + [Fact] + public void CanCreateACommitString() + { + string repoPath = SandboxStandardTestRepo(); + using (var repo = new Repository(repoPath)) + { + var tipCommit = repo.Head.Tip; + var recreatedCommit = Commit.CreateBuffer( + tipCommit.Author, + tipCommit.Committer, + tipCommit.Message, + tipCommit.Tree, + tipCommit.Parents, + false, null); + + var recreatedId = repo.ObjectDatabase.Write(Encoding.UTF8.GetBytes(recreatedCommit)); + Assert.Equal(tipCommit.Id, recreatedId); + } + } + + [Fact] + public void CanCreateASignedCommit() + { + string repoPath = SandboxStandardTestRepo(); + using (var repo = new Repository(repoPath)) + { + var odb = repo.ObjectDatabase; + var signedId = odb.Write(Encoding.UTF8.GetBytes(signedCommit)); + var signedId2 = odb.CreateCommitWithSignature(signedData, signatureData); + + Assert.Equal(signedId, signedId2); + + var signatureInfo = Commit.ExtractSignature(repo, signedId2); + Assert.Equal(signedData, signatureInfo.SignedData); + Assert.Equal(signatureData, signatureInfo.Signature); + } + } } } diff --git a/LibGit2Sharp.Tests/ConfigurationFixture.cs b/LibGit2Sharp.Tests/ConfigurationFixture.cs index 78cb71c0b..999aa0336 100644 --- a/LibGit2Sharp.Tests/ConfigurationFixture.cs +++ b/LibGit2Sharp.Tests/ConfigurationFixture.cs @@ -1,8 +1,10 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using LibGit2Sharp.Tests.TestHelpers; using Xunit; +using Xunit.Extensions; namespace LibGit2Sharp.Tests { @@ -14,35 +16,6 @@ private static void AssertValueInLocalConfigFile(string repoPath, string regex) AssertValueInConfigFile(configFilePath, regex); } - private static string RetrieveGlobalConfigLocation() - { - string[] variables = { "HOME", "USERPROFILE", }; - - foreach (string variable in variables) - { - string potentialLocation = Environment.GetEnvironmentVariable(variable); - if (string.IsNullOrEmpty(potentialLocation)) - { - continue; - } - - string potentialPath = Path.Combine(potentialLocation, ".gitconfig"); - - if (File.Exists(potentialPath)) - { - return potentialPath; - } - } - - throw new InvalidOperationException("Unable to determine the location of '.gitconfig' file."); - } - - private static void AssertValueInGlobalConfigFile(string regex) - { - string configFilePath = RetrieveGlobalConfigLocation(); - AssertValueInConfigFile(configFilePath, regex); - } - [Fact] public void CanUnsetAnEntryFromTheLocalConfiguration() { @@ -63,12 +36,8 @@ public void CanUnsetAnEntryFromTheLocalConfiguration() [Fact] public void CanUnsetAnEntryFromTheGlobalConfiguration() { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); - - var options = BuildFakeConfigs(scd); - string path = SandboxBareTestRepo(); - using (var repo = new Repository(path, options)) + 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); @@ -81,6 +50,119 @@ public void CanUnsetAnEntryFromTheGlobalConfiguration() } } + [Fact] + public void CanAddAndReadMultivarFromTheLocalConfiguration() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + Assert.Empty(repo.Config + .OfType>() + .Where(x => x.Key == "unittests.plugin")); + + repo.Config.Add("unittests.plugin", "value1", ConfigurationLevel.Local); + repo.Config.Add("unittests.plugin", "value2", ConfigurationLevel.Local); + + Assert.Equal(new[] { "value1", "value2" }, repo.Config + .OfType>() + .Where(x => x.Key == "unittests.plugin" && x.Level == ConfigurationLevel.Local) + .Select(x => x.Value) + .ToArray()); + } + } + + [Fact] + public void CanAddAndReadMultivarFromTheGlobalConfiguration() + { + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) + { + Assert.True(repo.Config.HasConfig(ConfigurationLevel.Global)); + Assert.Empty(repo.Config + .OfType>() + .Where(x => x.Key == "unittests.plugin")); + + repo.Config.Add("unittests.plugin", "value1", ConfigurationLevel.Global); + repo.Config.Add("unittests.plugin", "value2", ConfigurationLevel.Global); + + Assert.Equal(new[] { "value1", "value2" }, repo.Config + .OfType>() + .Where(x => x.Key == "unittests.plugin") + .Select(x => x.Value) + .ToArray()); + } + } + + [Fact] + public void CanUnsetAllFromTheGlobalConfiguration() + { + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) + { + Assert.True(repo.Config.HasConfig(ConfigurationLevel.Global)); + Assert.Empty(repo.Config + .OfType>() + .Where(x => x.Key == "unittests.plugin") + .Select(x => x.Value) + .ToArray()); + + repo.Config.Add("unittests.plugin", "value1", ConfigurationLevel.Global); + repo.Config.Add("unittests.plugin", "value2", ConfigurationLevel.Global); + + Assert.Equal(2, repo.Config + .OfType>() + .Where(x => x.Key == "unittests.plugin" && x.Level == ConfigurationLevel.Global) + .Select(x => x.Value) + .Count()); + + repo.Config.UnsetAll("unittests.plugin"); + + Assert.Equal(2, repo.Config + .OfType>() + .Where(x => x.Key == "unittests.plugin" && x.Level == ConfigurationLevel.Global) + .Select(x => x.Value) + .Count()); + + repo.Config.UnsetAll("unittests.plugin", ConfigurationLevel.Global); + + Assert.Empty(repo.Config + .OfType>() + .Where(x => x.Key == "unittests.plugin") + .Select(x => x.Value) + .ToArray()); + } + } + + [Fact] + public void CanUnsetAllFromTheLocalConfiguration() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + Assert.True(repo.Config.HasConfig(ConfigurationLevel.Global)); + Assert.Empty(repo.Config + .OfType>() + .Where(x => x.Key == "unittests.plugin") + .Select(x => x.Value) + .ToArray()); + + repo.Config.Add("unittests.plugin", "value1"); + repo.Config.Add("unittests.plugin", "value2"); + + Assert.Equal(2, repo.Config + .OfType>() + .Where(x => x.Key == "unittests.plugin" && x.Level == ConfigurationLevel.Local) + .Select(x => x.Value) + .Count()); + + repo.Config.UnsetAll("unittests.plugin"); + + Assert.Empty(repo.Config + .OfType>() + .Where(x => x.Key == "unittests.plugin")); + } + } + [Fact] public void CanReadBooleanValue() { @@ -90,9 +172,9 @@ public void CanReadBooleanValue() Assert.True(repo.Config.Get("core.ignorecase").Value); Assert.True(repo.Config.GetValueOrDefault("core.ignorecase")); - Assert.Equal(false, repo.Config.GetValueOrDefault("missing.key")); - Assert.Equal(true, repo.Config.GetValueOrDefault("missing.key", true)); - Assert.Equal(true, repo.Config.GetValueOrDefault("missing.key", () => true)); + Assert.False(repo.Config.GetValueOrDefault("missing.key")); + Assert.True(repo.Config.GetValueOrDefault("missing.key", true)); + Assert.True(repo.Config.GetValueOrDefault("missing.key", () => true)); } } @@ -141,26 +223,26 @@ public void CanReadStringValue() Assert.Equal("+refs/heads/*:refs/remotes/origin/*", repo.Config.GetValueOrDefault("remote", "origin", "fetch")); Assert.Equal("+refs/heads/*:refs/remotes/origin/*", repo.Config.GetValueOrDefault(new[] { "remote", "origin", "fetch" })); - Assert.Equal(null, repo.Config.GetValueOrDefault("missing.key")); - Assert.Equal(null, repo.Config.GetValueOrDefault("missing.key", default(string))); + Assert.Null(repo.Config.GetValueOrDefault("missing.key")); + Assert.Null(repo.Config.GetValueOrDefault("missing.key", default(string))); Assert.Throws(() => repo.Config.GetValueOrDefault("missing.key", default(Func))); Assert.Equal("value", repo.Config.GetValueOrDefault("missing.key", "value")); Assert.Equal("value", repo.Config.GetValueOrDefault("missing.key", () => "value")); - Assert.Equal(null, repo.Config.GetValueOrDefault("missing.key", ConfigurationLevel.Local)); - Assert.Equal(null, repo.Config.GetValueOrDefault("missing.key", ConfigurationLevel.Local, default(string))); + Assert.Null(repo.Config.GetValueOrDefault("missing.key", ConfigurationLevel.Local)); + Assert.Null(repo.Config.GetValueOrDefault("missing.key", ConfigurationLevel.Local, default(string))); Assert.Throws(() => repo.Config.GetValueOrDefault("missing.key", ConfigurationLevel.Local, default(Func))); Assert.Equal("value", repo.Config.GetValueOrDefault("missing.key", ConfigurationLevel.Local, "value")); Assert.Equal("value", repo.Config.GetValueOrDefault("missing.key", ConfigurationLevel.Local, () => "value")); - Assert.Equal(null, repo.Config.GetValueOrDefault("missing", "config", "key")); - Assert.Equal(null, repo.Config.GetValueOrDefault("missing", "config", "key", default(string))); + Assert.Null(repo.Config.GetValueOrDefault("missing", "config", "key")); + Assert.Null(repo.Config.GetValueOrDefault("missing", "config", "key", default(string))); Assert.Throws(() => repo.Config.GetValueOrDefault("missing", "config", "key", default(Func))); Assert.Equal("value", repo.Config.GetValueOrDefault("missing", "config", "key", "value")); Assert.Equal("value", repo.Config.GetValueOrDefault("missing", "config", "key", () => "value")); - Assert.Equal(null, repo.Config.GetValueOrDefault(new[] { "missing", "key" })); - Assert.Equal(null, repo.Config.GetValueOrDefault(new[] { "missing", "key" }, default(string))); + Assert.Null(repo.Config.GetValueOrDefault(new[] { "missing", "key" })); + Assert.Null(repo.Config.GetValueOrDefault(new[] { "missing", "key" }, default(string))); Assert.Throws(() => repo.Config.GetValueOrDefault(new[] { "missing", "key" }, default(Func))); Assert.Equal("value", repo.Config.GetValueOrDefault(new[] { "missing", "key" }, "value")); Assert.Equal("value", repo.Config.GetValueOrDefault(new[] { "missing", "key" }, () => "value")); @@ -170,12 +252,10 @@ public void CanReadStringValue() [Fact] public void CanEnumerateGlobalConfig() { - string configPath = CreateConfigurationWithDummyUser(Constants.Signature); - var options = new RepositoryOptions { GlobalConfigurationLocation = configPath }; - var path = SandboxStandardTestRepoGitDir(); - using (var repo = new Repository(path, options)) + using (var repo = new Repository(path)) { + CreateConfigurationWithDummyUser(repo, Constants.Identity); var entry = repo.Config.FirstOrDefault>(e => e.Key == "user.name"); Assert.NotNull(entry); Assert.Equal(Constants.Signature.Name, entry.Value); @@ -227,16 +307,14 @@ public void CanFindInLocalConfig() [Fact] public void CanFindInGlobalConfig() { - string configPath = CreateConfigurationWithDummyUser(Constants.Signature); - var options = new RepositoryOptions { GlobalConfigurationLocation = configPath }; var path = SandboxStandardTestRepoGitDir(); - using (var repo = new Repository(path, options)) + using (var repo = new Repository(path)) { - var matches = repo.Config.Find(@"\.name", ConfigurationLevel.Global); + var matches = repo.Config.Find("-rocks", ConfigurationLevel.Global); Assert.NotNull(matches); - Assert.Equal(new[] { "user.name" }, + Assert.Equal(new[] { "woot.this-rocks" }, matches.Select(m => m.Key).ToArray()); } } @@ -256,7 +334,7 @@ public void CanSetBooleanValue() [Fact] public void SettingLocalConfigurationOutsideAReposThrows() { - using (var config = new Configuration(null, null, null)) + using (var config = Configuration.BuildFrom(null, null, null, null)) { Assert.Throws(() => config.Set("unittests.intsetting", 3)); } @@ -358,12 +436,8 @@ public void SettingUnsupportedTypeThrows() [Fact] public void CanGetAnEntryFromASpecificStore() { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); - - var options = BuildFakeConfigs(scd); - string path = SandboxStandardTestRepo(); - using (var repo = new Repository(path, options)) + using (var repo = new Repository(path)) { Assert.True(repo.Config.HasConfig(ConfigurationLevel.Local)); Assert.True(repo.Config.HasConfig(ConfigurationLevel.Global)); @@ -383,17 +457,180 @@ public void CanGetAnEntryFromASpecificStore() [Fact] public void CanTellIfASpecificStoreContainsAKey() { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); - - var options = BuildFakeConfigs(scd); - string path = SandboxBareTestRepo(); - using (var repo = new Repository(path, options)) + 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 65a98659e..b28270b7e 100644 --- a/LibGit2Sharp.Tests/ConflictFixture.cs +++ b/LibGit2Sharp.Tests/ConflictFixture.cs @@ -75,16 +75,16 @@ public void CanResolveConflictsByRemovingFromTheIndex( Assert.Equal(existsBeforeRemove, File.Exists(fullpath)); Assert.NotNull(repo.Index.Conflicts[filename]); - Assert.Equal(0, repo.Index.Conflicts.ResolvedConflicts.Count()); + Assert.Empty(repo.Index.Conflicts.ResolvedConflicts); - repo.Remove(filename, removeFromWorkdir); + Commands.Remove(repo, filename, removeFromWorkdir); Assert.Null(repo.Index.Conflicts[filename]); Assert.Equal(count - removedIndexEntries, repo.Index.Count); Assert.Equal(existsAfterRemove, File.Exists(fullpath)); Assert.Equal(lastStatus, repo.RetrieveStatus(filename)); - Assert.Equal(1, repo.Index.Conflicts.ResolvedConflicts.Count()); + Assert.Single(repo.Index.Conflicts.ResolvedConflicts); Assert.NotNull(repo.Index.Conflicts.ResolvedConflicts[filename]); } } @@ -112,7 +112,7 @@ public void CanGetOriginalNamesOfRenameConflicts() } } - [Theory, PropertyData("ConflictData")] + [Theory, MemberData(nameof(ConflictData))] public void CanRetrieveSingleConflictByPath(string filepath, string ancestorId, string ourId, string theirId) { var path = SandboxMergedTestRepo(); diff --git a/LibGit2Sharp.Tests/DescribeFixture.cs b/LibGit2Sharp.Tests/DescribeFixture.cs index 52e0ac8fc..ca859b9cd 100644 --- a/LibGit2Sharp.Tests/DescribeFixture.cs +++ b/LibGit2Sharp.Tests/DescribeFixture.cs @@ -1,6 +1,7 @@ using System.Linq; using LibGit2Sharp.Tests.TestHelpers; using Xunit; +using System; namespace LibGit2Sharp.Tests { @@ -49,5 +50,35 @@ public void CanDescribeACommit() 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 00ef0ab2b..fea0bbb74 100644 --- a/LibGit2Sharp.Tests/DiffBlobToBlobFixture.cs +++ b/LibGit2Sharp.Tests/DiffBlobToBlobFixture.cs @@ -1,4 +1,5 @@ using System.IO; +using System.Linq; using System.Text; using LibGit2Sharp.Tests.TestHelpers; using Xunit; @@ -126,5 +127,85 @@ 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); + } + } + + 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 da48dbc68..2fb359f24 100644 --- a/LibGit2Sharp.Tests/DiffTreeToTargetFixture.cs +++ b/LibGit2Sharp.Tests/DiffTreeToTargetFixture.cs @@ -12,12 +12,12 @@ private static void SetUpSimpleDiffContext(IRepository repo) { var fullpath = Touch(repo.Info.WorkingDirectory, "file.txt", "hello\n"); - repo.Stage(fullpath); + Commands.Stage(repo, fullpath); repo.Commit("Initial commit", Constants.Signature, Constants.Signature); File.AppendAllText(fullpath, "world\n"); - repo.Stage(fullpath); + Commands.Stage(repo,fullpath); File.AppendAllText(fullpath, "!!!\n"); } @@ -43,23 +43,27 @@ public void CanCompareASimpleTreeAgainstTheWorkDir() { SetUpSimpleDiffContext(repo); - var changes = repo.Diff.Compare(repo.Head.Tip.Tree, - DiffTargets.WorkingDirectory); - Assert.Equal(1, changes.Modified.Count()); - - var patch = repo.Diff.Compare(repo.Head.Tip.Tree, - DiffTargets.WorkingDirectory); - var expected = new StringBuilder() - .Append("diff --git a/file.txt b/file.txt\n") - .Append("index ce01362..4f125e3 100644\n") - .Append("--- a/file.txt\n") - .Append("+++ b/file.txt\n") - .Append("@@ -1 +1,3 @@\n") - .Append(" hello\n") - .Append("+world\n") - .Append("+!!!\n"); - - Assert.Equal(expected.ToString(), patch); + using (var changes = repo.Diff.Compare(repo.Head.Tip.Tree, + DiffTargets.WorkingDirectory)) + { + Assert.Single(changes.Modified); + } + + using (var patch = repo.Diff.Compare(repo.Head.Tip.Tree, + DiffTargets.WorkingDirectory)) + { + var expected = new StringBuilder() + .Append("diff --git a/file.txt b/file.txt\n") + .Append("index ce01362..4f125e3 100644\n") + .Append("--- a/file.txt\n") + .Append("+++ b/file.txt\n") + .Append("@@ -1 +1,3 @@\n") + .Append(" hello\n") + .Append("+world\n") + .Append("+!!!\n"); + + Assert.Equal(expected.ToString(), patch); + } } } @@ -71,19 +75,21 @@ public void CanCompareAMoreComplexTreeAgainstTheWorkdir() { Tree tree = repo.Head.Tip.Tree; - var changes = repo.Diff.Compare(tree, DiffTargets.WorkingDirectory); - Assert.NotNull(changes); + using (var changes = repo.Diff.Compare(tree, DiffTargets.WorkingDirectory)) + { + Assert.NotNull(changes); - Assert.Equal(6, changes.Count()); + Assert.Equal(6, changes.Count()); - Assert.Equal(new[] { "deleted_staged_file.txt", "deleted_unstaged_file.txt" }, - changes.Deleted.Select(tec => tec.Path)); + Assert.Equal(new[] { "deleted_staged_file.txt", "deleted_unstaged_file.txt" }, + changes.Deleted.Select(tec => tec.Path)); - Assert.Equal(new[] { "new_tracked_file.txt", "new_untracked_file.txt" }, - changes.Added.Select(tec => tec.Path)); + Assert.Equal(new[] { "new_tracked_file.txt", "new_untracked_file.txt" }, + changes.Added.Select(tec => tec.Path)); - Assert.Equal(new[] { "modified_staged_file.txt", "modified_unstaged_file.txt" }, - changes.Modified.Select(tec => tec.Path)); + Assert.Equal(new[] { "modified_staged_file.txt", "modified_unstaged_file.txt" }, + changes.Modified.Select(tec => tec.Path)); + } } } @@ -107,23 +113,27 @@ public void CanCompareASimpleTreeAgainstTheWorkDirAndTheIndex() { SetUpSimpleDiffContext(repo); - var changes = repo.Diff.Compare(repo.Head.Tip.Tree, - DiffTargets.Index | DiffTargets.WorkingDirectory); - Assert.Equal(1, changes.Modified.Count()); - - var patch = repo.Diff.Compare(repo.Head.Tip.Tree, - DiffTargets.Index | DiffTargets.WorkingDirectory); - var expected = new StringBuilder() - .Append("diff --git a/file.txt b/file.txt\n") - .Append("index ce01362..4f125e3 100644\n") - .Append("--- a/file.txt\n") - .Append("+++ b/file.txt\n") - .Append("@@ -1 +1,3 @@\n") - .Append(" hello\n") - .Append("+world\n") - .Append("+!!!\n"); - - Assert.Equal(expected.ToString(), patch); + using (var changes = repo.Diff.Compare(repo.Head.Tip.Tree, + DiffTargets.Index | DiffTargets.WorkingDirectory)) + { + Assert.Single(changes.Modified); + } + + using (var patch = repo.Diff.Compare(repo.Head.Tip.Tree, + DiffTargets.Index | DiffTargets.WorkingDirectory)) + { + var expected = new StringBuilder() + .Append("diff --git a/file.txt b/file.txt\n") + .Append("index ce01362..4f125e3 100644\n") + .Append("--- a/file.txt\n") + .Append("+++ b/file.txt\n") + .Append("@@ -1 +1,3 @@\n") + .Append(" hello\n") + .Append("+world\n") + .Append("+!!!\n"); + + Assert.Equal(expected.ToString(), patch); + } } } @@ -159,50 +169,56 @@ public void ShowcaseTheDifferenceBetweenTheTwoKindOfComparison() var fullpath = Path.Combine(repo.Info.WorkingDirectory, "file.txt"); File.Move(fullpath, fullpath + ".bak"); - repo.Stage(fullpath); + Commands.Stage(repo, fullpath); File.Move(fullpath + ".bak", fullpath); FileStatus state = repo.RetrieveStatus("file.txt"); Assert.Equal(FileStatus.DeletedFromIndex | FileStatus.NewInWorkdir, state); - var wrkDirToIdxToTree = repo.Diff.Compare(repo.Head.Tip.Tree, - DiffTargets.Index | DiffTargets.WorkingDirectory); - - Assert.Equal(1, wrkDirToIdxToTree.Deleted.Count()); - Assert.Equal(0, wrkDirToIdxToTree.Modified.Count()); - - var patch = repo.Diff.Compare(repo.Head.Tip.Tree, - DiffTargets.Index | DiffTargets.WorkingDirectory); - var expected = new StringBuilder() - .Append("diff --git a/file.txt b/file.txt\n") - .Append("deleted file mode 100644\n") - .Append("index ce01362..0000000\n") - .Append("--- a/file.txt\n") - .Append("+++ /dev/null\n") - .Append("@@ -1 +0,0 @@\n") - .Append("-hello\n"); - - Assert.Equal(expected.ToString(), patch); - - var wrkDirToTree = repo.Diff.Compare(repo.Head.Tip.Tree, - DiffTargets.WorkingDirectory); - - Assert.Equal(0, wrkDirToTree.Deleted.Count()); - Assert.Equal(1, wrkDirToTree.Modified.Count()); - - patch = repo.Diff.Compare(repo.Head.Tip.Tree, - DiffTargets.WorkingDirectory); - expected = new StringBuilder() - .Append("diff --git a/file.txt b/file.txt\n") - .Append("index ce01362..4f125e3 100644\n") - .Append("--- a/file.txt\n") - .Append("+++ b/file.txt\n") - .Append("@@ -1 +1,3 @@\n") - .Append(" hello\n") - .Append("+world\n") - .Append("+!!!\n"); - - Assert.Equal(expected.ToString(), patch); + 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); + } } } @@ -225,22 +241,26 @@ public void CanCompareASimpleTreeAgainstTheIndex() { SetUpSimpleDiffContext(repo); - var changes = repo.Diff.Compare(repo.Head.Tip.Tree, - DiffTargets.Index); - Assert.Equal(1, changes.Modified.Count()); - - var patch = repo.Diff.Compare(repo.Head.Tip.Tree, - DiffTargets.Index); - var expected = new StringBuilder() - .Append("diff --git a/file.txt b/file.txt\n") - .Append("index ce01362..94954ab 100644\n") - .Append("--- a/file.txt\n") - .Append("+++ b/file.txt\n") - .Append("@@ -1 +1,2 @@\n") - .Append(" hello\n") - .Append("+world\n"); - - Assert.Equal(expected.ToString(), patch); + using (var changes = repo.Diff.Compare(repo.Head.Tip.Tree, + DiffTargets.Index)) + { + Assert.Single(changes.Modified); + } + + using (var patch = repo.Diff.Compare(repo.Head.Tip.Tree, + DiffTargets.Index)) + { + var expected = new StringBuilder() + .Append("diff --git a/file.txt b/file.txt\n") + .Append("index ce01362..94954ab 100644\n") + .Append("--- a/file.txt\n") + .Append("+++ b/file.txt\n") + .Append("@@ -1 +1,2 @@\n") + .Append(" hello\n") + .Append("+world\n"); + + Assert.Equal(expected.ToString(), patch); + } } } @@ -276,13 +296,15 @@ public void CanCompareAMoreComplexTreeAgainstTheIndex() { Tree tree = repo.Head.Tip.Tree; - var changes = repo.Diff.Compare(tree, DiffTargets.Index); - Assert.NotNull(changes); + using (var changes = repo.Diff.Compare(tree, DiffTargets.Index)) + { + Assert.NotNull(changes); - Assert.Equal(3, changes.Count()); - Assert.Equal("deleted_staged_file.txt", changes.Deleted.Single().Path); - Assert.Equal("new_tracked_file.txt", changes.Added.Single().Path); - Assert.Equal("modified_staged_file.txt", changes.Modified.Single().Path); + Assert.Equal(3, changes.Count()); + Assert.Equal("deleted_staged_file.txt", changes.Deleted.Single().Path); + Assert.Equal("new_tracked_file.txt", changes.Added.Single().Path); + Assert.Equal("modified_staged_file.txt", changes.Modified.Single().Path); + } } } @@ -304,20 +326,21 @@ public void CanCompareASubsetofTheTreeAgainstTheIndex() { Tree tree = repo.Head.Tip.Tree; - var changes = repo.Diff.Compare(tree, DiffTargets.Index, - new[] { "deleted_staged_file.txt", "1/branch_file.txt" }); + using (var changes = repo.Diff.Compare(tree, DiffTargets.Index, + new[] { "deleted_staged_file.txt", "1/branch_file.txt" })) + { + Assert.NotNull(changes); - Assert.NotNull(changes); - - Assert.Equal(1, changes.Count()); - Assert.Equal("deleted_staged_file.txt", changes.Deleted.Single().Path); + Assert.Single(changes); + Assert.Equal("deleted_staged_file.txt", changes.Deleted.Single().Path); + } } } private static void AssertCanCompareASubsetOfTheTreeAgainstTheIndex(TreeChanges changes) { Assert.NotNull(changes); - Assert.Equal(1, changes.Count()); + Assert.Single(changes); Assert.Equal("deleted_staged_file.txt", changes.Deleted.Single().Path); } @@ -329,13 +352,17 @@ public void CanCompareASubsetofTheTreeAgainstTheIndexWithLaxExplicitPathsValidat { Tree tree = repo.Head.Tip.Tree; - var changes = repo.Diff.Compare(tree, DiffTargets.Index, - new[] { "deleted_staged_file.txt", "1/branch_file.txt", "I-do/not-exist" }, new ExplicitPathsOptions { ShouldFailOnUnmatchedPath = false }); - AssertCanCompareASubsetOfTheTreeAgainstTheIndex(changes); - - changes = repo.Diff.Compare(tree, DiffTargets.Index, - new[] { "deleted_staged_file.txt", "1/branch_file.txt", "I-do/not-exist" }); - AssertCanCompareASubsetOfTheTreeAgainstTheIndex(changes); + using (var changes = repo.Diff.Compare(tree, DiffTargets.Index, + new[] { "deleted_staged_file.txt", "1/branch_file.txt", "I-do/not-exist" }, new ExplicitPathsOptions { ShouldFailOnUnmatchedPath = false })) + { + AssertCanCompareASubsetOfTheTreeAgainstTheIndex(changes); + } + + using (var changes = repo.Diff.Compare(tree, DiffTargets.Index, + new[] { "deleted_staged_file.txt", "1/branch_file.txt", "I-do/not-exist" })) + { + AssertCanCompareASubsetOfTheTreeAgainstTheIndex(changes); + } } } @@ -378,29 +405,33 @@ public void CanCopeWithEndOfFileNewlineChanges() { var fullpath = Touch(repo.Info.WorkingDirectory, "file.txt", "a"); - repo.Stage("file.txt"); + Commands.Stage(repo, "file.txt"); repo.Commit("Add file without line ending", Constants.Signature, Constants.Signature); File.AppendAllText(fullpath, "\n"); - repo.Stage("file.txt"); - - var changes = repo.Diff.Compare(repo.Head.Tip.Tree, DiffTargets.Index); - Assert.Equal(1, changes.Modified.Count()); - - var patch = repo.Diff.Compare(repo.Head.Tip.Tree, DiffTargets.Index); - var expected = new StringBuilder() - .Append("diff --git a/file.txt b/file.txt\n") - .Append("index 2e65efe..7898192 100644\n") - .Append("--- a/file.txt\n") - .Append("+++ b/file.txt\n") - .Append("@@ -1 +1 @@\n") - .Append("-a\n") - .Append("\\ No newline at end of file\n") - .Append("+a\n"); - - Assert.Equal(expected.ToString(), patch); - Assert.Equal(1, patch.LinesAdded); - Assert.Equal(1, patch.LinesDeleted); + Commands.Stage(repo, "file.txt"); + + using (var changes = repo.Diff.Compare(repo.Head.Tip.Tree, DiffTargets.Index)) + { + Assert.Single(changes.Modified); + } + + using (var patch = repo.Diff.Compare(repo.Head.Tip.Tree, DiffTargets.Index)) + { + var expected = new StringBuilder() + .Append("diff --git a/file.txt b/file.txt\n") + .Append("index 2e65efe..7898192 100644\n") + .Append("--- a/file.txt\n") + .Append("+++ b/file.txt\n") + .Append("@@ -1 +1 @@\n") + .Append("-a\n") + .Append("\\ No newline at end of file\n") + .Append("+a\n"); + + Assert.Equal(expected.ToString(), patch); + Assert.Equal(1, patch.LinesAdded); + Assert.Equal(1, patch.LinesDeleted); + } } } @@ -428,13 +459,14 @@ public void CanCompareANullTreeAgainstTheIndex() { SetUpSimpleDiffContext(repo); - var changes = repo.Diff.Compare(null, - DiffTargets.Index); - - Assert.Equal(1, changes.Count()); - Assert.Equal(1, changes.Added.Count()); + using (var changes = repo.Diff.Compare(null, + DiffTargets.Index)) + { + Assert.Single(changes); + Assert.Single(changes.Added); - Assert.Equal("file.txt", changes.Added.Single().Path); + Assert.Equal("file.txt", changes.Added.Single().Path); + } } } @@ -447,13 +479,14 @@ public void CanCompareANullTreeAgainstTheWorkdir() { SetUpSimpleDiffContext(repo); - var changes = repo.Diff.Compare(null, - DiffTargets.WorkingDirectory); + using (var changes = repo.Diff.Compare(null, + DiffTargets.WorkingDirectory)) + { + Assert.Single(changes); + Assert.Single(changes.Added); - Assert.Equal(1, changes.Count()); - Assert.Equal(1, changes.Added.Count()); - - Assert.Equal("file.txt", changes.Added.Single().Path); + Assert.Equal("file.txt", changes.Added.Single().Path); + } } } @@ -466,13 +499,14 @@ public void CanCompareANullTreeAgainstTheWorkdirAndTheIndex() { SetUpSimpleDiffContext(repo); - var changes = repo.Diff.Compare(null, - DiffTargets.WorkingDirectory | DiffTargets.Index); - - Assert.Equal(1, changes.Count()); - Assert.Equal(1, changes.Added.Count()); + using (var changes = repo.Diff.Compare(null, + DiffTargets.WorkingDirectory | DiffTargets.Index)) + { + Assert.Single(changes); + Assert.Single(changes.Added); - Assert.Equal("file.txt", changes.Added.Single().Path); + Assert.Equal("file.txt", changes.Added.Single().Path); + } } } } diff --git a/LibGit2Sharp.Tests/DiffTreeToTreeFixture.cs b/LibGit2Sharp.Tests/DiffTreeToTreeFixture.cs index 0e8ef905e..dba762bfe 100644 --- a/LibGit2Sharp.Tests/DiffTreeToTreeFixture.cs +++ b/LibGit2Sharp.Tests/DiffTreeToTreeFixture.cs @@ -10,7 +10,7 @@ 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() @@ -20,12 +20,16 @@ public void ComparingATreeAgainstItselfReturnsNoDifference() { Tree tree = repo.Head.Tip.Tree; - var changes = repo.Diff.Compare(tree, tree); - var patch = repo.Diff.Compare(tree, tree); + using(var changes = repo.Diff.Compare(tree, tree)) + { + Assert.Empty(changes); + } - Assert.Empty(changes); - Assert.Empty(patch); - Assert.Equal(String.Empty, patch); + using (var patch = repo.Diff.Compare(tree, tree)) + { + Assert.Empty(patch); + Assert.Equal(String.Empty, patch); + } } } @@ -37,9 +41,10 @@ public void RetrievingANonExistentFileChangeReturnsNull() { Tree tree = repo.Head.Tip.Tree; - var changes = repo.Diff.Compare(tree, tree); - - Assert.Equal(0, changes.Count(c => c.Path == "batman")); + using (var changes = repo.Diff.Compare(tree, tree)) + { + Assert.Equal(0, changes.Count(c => c.Path == "batman")); + } } } @@ -57,22 +62,25 @@ public void CanCompareACommitTreeAgainstItsParent() Tree commitTree = repo.Head.Tip.Tree; Tree parentCommitTree = repo.Head.Tip.Parents.Single().Tree; - var changes = repo.Diff.Compare(parentCommitTree, commitTree); - - Assert.Equal(1, changes.Count()); - Assert.Equal(1, changes.Added.Count()); + using (var changes = repo.Diff.Compare(parentCommitTree, commitTree)) + { + Assert.Single(changes); + Assert.Single(changes.Added); - TreeEntryChanges treeEntryChanges = changes.Single(c => c.Path == "1.txt"); + TreeEntryChanges treeEntryChanges = changes.Single(c => c.Path == "1.txt"); - var patch = repo.Diff.Compare(parentCommitTree, commitTree); - Assert.False(patch["1.txt"].IsBinaryComparison); + Assert.Equal("1.txt", treeEntryChanges.Path); + Assert.Equal(ChangeKind.Added, treeEntryChanges.Status); - Assert.Equal("1.txt", treeEntryChanges.Path); - Assert.Equal(ChangeKind.Added, treeEntryChanges.Status); + Assert.Equal(treeEntryChanges.Path, changes.Added.Single().Path); - Assert.Equal(treeEntryChanges, changes.Added.Single()); + Assert.Equal(Mode.Nonexistent, treeEntryChanges.OldMode); + } - Assert.Equal(Mode.Nonexistent, treeEntryChanges.OldMode); + using (var patch = repo.Diff.Compare(parentCommitTree, commitTree)) + { + Assert.False(patch["1.txt"].IsBinaryComparison); + } } } @@ -99,19 +107,19 @@ public void CanDetectABinaryChange() CreateBinaryFile(filepath); - repo.Stage(filename); + Commands.Stage(repo, filename); var commit = repo.Commit("Add binary file", Constants.Signature, Constants.Signature); File.AppendAllText(filepath, "abcdef"); - var patch = repo.Diff.Compare(commit.Tree, DiffTargets.WorkingDirectory, new[] { filename }); - Assert.True(patch[filename].IsBinaryComparison); + using(var patch = repo.Diff.Compare(commit.Tree, DiffTargets.WorkingDirectory, new[] { filename })) + Assert.True(patch[filename].IsBinaryComparison); - repo.Stage(filename); + Commands.Stage(repo, filename); var commit2 = repo.Commit("Update binary file", Constants.Signature, Constants.Signature); - var patch2 = repo.Diff.Compare(commit.Tree, commit2.Tree, new[] { filename }); - Assert.True(patch2[filename].IsBinaryComparison); + using(var patch2 = repo.Diff.Compare(commit.Tree, commit2.Tree, new[] { filename })) + Assert.True(patch2[filename].IsBinaryComparison); } } @@ -125,19 +133,19 @@ public void CanDetectABinaryDeletion() CreateBinaryFile(filepath); - repo.Stage(filename); + Commands.Stage(repo, filename); var commit = repo.Commit("Add binary file", Constants.Signature, Constants.Signature); File.Delete(filepath); - var patch = repo.Diff.Compare(commit.Tree, DiffTargets.WorkingDirectory, new [] {filename}); - Assert.True(patch[filename].IsBinaryComparison); + using(var patch = repo.Diff.Compare(commit.Tree, DiffTargets.WorkingDirectory, new [] {filename})) + Assert.True(patch[filename].IsBinaryComparison); - repo.Remove(filename); + Commands.Remove(repo, filename); var commit2 = repo.Commit("Delete binary file", Constants.Signature, Constants.Signature); - var patch2 = repo.Diff.Compare(commit.Tree, commit2.Tree, new[] { filename }); - Assert.True(patch2[filename].IsBinaryComparison); + using(var patch2 = repo.Diff.Compare(commit.Tree, commit2.Tree, new[] { filename })) + Assert.True(patch2[filename].IsBinaryComparison); } } @@ -160,11 +168,13 @@ public void CanCompareASubsetofTheTreeAgainstOneOfItsAncestor() Tree tree = repo.Head.Tip.Tree; Tree ancestor = repo.Lookup("9fd738e").Tree; - var changes = repo.Diff.Compare(ancestor, tree, new[] { "1" }); - Assert.NotNull(changes); + using (var changes = repo.Diff.Compare(ancestor, tree, new[] { "1" })) + { + Assert.NotNull(changes); - Assert.Equal(1, changes.Count()); - Assert.Equal(subBranchFilePath, changes.Added.Single().Path); + Assert.Single(changes); + Assert.Equal(subBranchFilePath, changes.Added.Single().Path); + } } } @@ -191,20 +201,23 @@ public void CanCompareACommitTreeAgainstATreeWithNoCommonAncestor() Tree commitTree = repo.Head.Tip.Tree; Tree commitTreeWithDifferentAncestor = repo.Branches["refs/remotes/origin/test"].Tip.Tree; - var changes = repo.Diff.Compare(commitTreeWithDifferentAncestor, commitTree); - - Assert.Equal(10, changes.Count()); - Assert.Equal(9, changes.Added.Count()); - Assert.Equal(1, changes.Deleted.Count()); + using (var changes = repo.Diff.Compare(commitTreeWithDifferentAncestor, commitTree)) + { + Assert.Equal(10, changes.Count()); + Assert.Equal(9, changes.Added.Count()); + Assert.Single(changes.Deleted); - Assert.Equal("readme.txt", changes.Deleted.Single().Path); - Assert.Equal(new[] { "1.txt", subBranchFilePath, "README", "branch_file.txt", "deleted_staged_file.txt", "deleted_unstaged_file.txt", "modified_staged_file.txt", "modified_unstaged_file.txt", "new.txt" }, - changes.Added.Select(x => x.Path).OrderBy(p => p, StringComparer.Ordinal).ToArray()); + Assert.Equal("readme.txt", changes.Deleted.Single().Path); + Assert.Equal(new[] { "1.txt", subBranchFilePath, "README", "branch_file.txt", "deleted_staged_file.txt", "deleted_unstaged_file.txt", "modified_staged_file.txt", "modified_unstaged_file.txt", "new.txt" }, + changes.Added.Select(x => x.Path).OrderBy(p => p, StringComparer.Ordinal).ToArray()); + } - var patch = repo.Diff.Compare(commitTreeWithDifferentAncestor, commitTree); - Assert.Equal(9, patch.LinesAdded); - Assert.Equal(2, patch.LinesDeleted); - Assert.Equal(2, patch["readme.txt"].LinesDeleted); + using (var patch = repo.Diff.Compare(commitTreeWithDifferentAncestor, commitTree)) + { + Assert.Equal(9, patch.LinesAdded); + Assert.Equal(2, patch.LinesDeleted); + Assert.Equal(2, patch["readme.txt"].LinesDeleted); + } } } @@ -217,13 +230,17 @@ public void CanCompareATreeAgainstAnotherTreeWithLaxExplicitPathsValidationAndNo Tree commitTree = repo.Head.Tip.Tree; Tree commitTreeWithDifferentAncestor = repo.Branches["refs/remotes/origin/test"].Tip.Tree; - var changes = repo.Diff.Compare(commitTreeWithDifferentAncestor, commitTree, - new[] { "if-I-exist-this-test-is-really-unlucky.txt" }, new ExplicitPathsOptions { ShouldFailOnUnmatchedPath = false }); - Assert.Equal(0, changes.Count()); + using (var changes = repo.Diff.Compare(commitTreeWithDifferentAncestor, commitTree, + new[] { "if-I-exist-this-test-is-really-unlucky.txt" }, new ExplicitPathsOptions { ShouldFailOnUnmatchedPath = false })) + { + Assert.Empty(changes); + } - changes = repo.Diff.Compare(commitTreeWithDifferentAncestor, commitTree, - new[] { "if-I-exist-this-test-is-really-unlucky.txt" }); - Assert.Equal(0, changes.Count()); + using (var changes = repo.Diff.Compare(commitTreeWithDifferentAncestor, commitTree, + new[] { "if-I-exist-this-test-is-really-unlucky.txt" })) + { + Assert.Empty(changes); + } } } @@ -269,11 +286,12 @@ public void DetectsTheRenamingOfAModifiedFileByDefault() Tree rootCommitTree = repo.Lookup("f8d44d7").Tree; Tree commitTreeWithRenamedFile = repo.Lookup("4be51d6").Tree; - var changes = repo.Diff.Compare(rootCommitTree, commitTreeWithRenamedFile); - - Assert.Equal(1, changes.Count()); - Assert.Equal("my-name-does-not-feel-right.txt", changes.Single(c => c.Path == "super-file.txt").OldPath); - Assert.Equal(1, changes.Renamed.Count()); + using (var changes = repo.Diff.Compare(rootCommitTree, commitTreeWithRenamedFile)) + { + Assert.Single(changes); + Assert.Equal("my-name-does-not-feel-right.txt", changes.Single(c => c.Path == "super-file.txt").OldPath); + Assert.Single(changes.Renamed); + } } } @@ -289,20 +307,21 @@ public void DetectsTheExactRenamingOfFilesByDefault() Touch(repo.Info.WorkingDirectory, originalPath, "a\nb\nc\nd\n"); - repo.Stage(originalPath); + Commands.Stage(repo, originalPath); Commit old = repo.Commit("Initial", Constants.Signature, Constants.Signature); - repo.Move(originalPath, renamedPath); + Commands.Move(repo, originalPath, renamedPath); Commit @new = repo.Commit("Updated", Constants.Signature, Constants.Signature); - var changes = repo.Diff.Compare(old.Tree, @new.Tree); - - Assert.Equal(1, changes.Count()); - Assert.Equal(1, changes.Renamed.Count()); - Assert.Equal(originalPath, changes.Renamed.Single().OldPath); - Assert.Equal(renamedPath, changes.Renamed.Single().Path); + using (var changes = repo.Diff.Compare(old.Tree, @new.Tree)) + { + Assert.Single(changes); + Assert.Single(changes.Renamed); + Assert.Equal(originalPath, changes.Renamed.Single().OldPath); + Assert.Equal(renamedPath, changes.Renamed.Single().Path); + } } } @@ -324,14 +343,14 @@ public void RenameThresholdsAreObeyed() // 4 lines Touch(repo.Info.WorkingDirectory, originalPath, "a\nb\nc\nd\n"); - repo.Stage(originalPath); + Commands.Stage(repo, originalPath); Commit old = repo.Commit("Initial", Constants.Signature, Constants.Signature); // 8 lines, 50% are from original file Touch(repo.Info.WorkingDirectory, originalPath, "a\nb\nc\nd\ne\nf\ng\nh\n"); - repo.Stage(originalPath); - repo.Move(originalPath, renamedPath); + Commands.Stage(repo, originalPath); + Commands.Move(repo, originalPath, renamedPath); Commit @new = repo.Commit("Updated", Constants.Signature, Constants.Signature); @@ -344,12 +363,14 @@ public void RenameThresholdsAreObeyed() }; compareOptions.Similarity.RenameThreshold = 30; - var changes = repo.Diff.Compare(old.Tree, @new.Tree, compareOptions: compareOptions); - Assert.True(changes.All(x => x.Status == ChangeKind.Renamed)); + + using (var changes = repo.Diff.Compare(old.Tree, @new.Tree, compareOptions: compareOptions)) + Assert.True(changes.All(x => x.Status == ChangeKind.Renamed)); compareOptions.Similarity.RenameThreshold = 90; - changes = repo.Diff.Compare(old.Tree, @new.Tree, compareOptions: compareOptions); - Assert.False(changes.Any(x => x.Status == ChangeKind.Renamed)); + + using (var changes = repo.Diff.Compare(old.Tree, @new.Tree, compareOptions: compareOptions)) + Assert.DoesNotContain(changes, x => x.Status == ChangeKind.Renamed); } } @@ -365,24 +386,25 @@ public void ExactModeDetectsExactRenames() Touch(repo.Info.WorkingDirectory, originalPath, "a\nb\nc\nd\n"); - repo.Stage(originalPath); + Commands.Stage(repo, originalPath); Commit old = repo.Commit("Initial", Constants.Signature, Constants.Signature); - repo.Move(originalPath, renamedPath); + Commands.Move(repo, originalPath, renamedPath); Commit @new = repo.Commit("Updated", Constants.Signature, Constants.Signature); - var changes = repo.Diff.Compare(old.Tree, @new.Tree, + using (var changes = repo.Diff.Compare(old.Tree, @new.Tree, compareOptions: new CompareOptions { Similarity = SimilarityOptions.Exact, - }); - - Assert.Equal(1, changes.Count()); - Assert.Equal(1, changes.Renamed.Count()); - Assert.Equal(originalPath, changes.Renamed.Single().OldPath); - Assert.Equal(renamedPath, changes.Renamed.Single().Path); + })) + { + Assert.Single(changes); + Assert.Single(changes.Renamed); + Assert.Equal(originalPath, changes.Renamed.Single().OldPath); + Assert.Equal(renamedPath, changes.Renamed.Single().Path); + } } } @@ -399,22 +421,23 @@ public void ExactModeDetectsExactCopies() var copiedFullPath = Path.Combine(repo.Info.WorkingDirectory, copiedPath); Touch(repo.Info.WorkingDirectory, originalPath, "a\nb\nc\nd\n"); - repo.Stage(originalPath); + Commands.Stage(repo, originalPath); Commit old = repo.Commit("Initial", Constants.Signature, Constants.Signature); File.Copy(originalFullPath, copiedFullPath); - repo.Stage(copiedPath); + Commands.Stage(repo, copiedPath); Commit @new = repo.Commit("Updated", Constants.Signature, Constants.Signature); - var changes = repo.Diff.Compare(old.Tree, @new.Tree, + using (var changes = repo.Diff.Compare(old.Tree, @new.Tree, compareOptions: new CompareOptions { Similarity = SimilarityOptions.Exact, - }); - - Assert.Equal(1, changes.Count()); - Assert.Equal(1, changes.Copied.Count()); + })) + { + Assert.Single(changes); + Assert.Single(changes.Copied); + } } } @@ -430,26 +453,27 @@ public void ExactModeDoesntDetectRenamesWithEdits() Touch(repo.Info.WorkingDirectory, originalPath, "a\nb\nc\nd\n"); - repo.Stage(originalPath); + Commands.Stage(repo, originalPath); Commit old = repo.Commit("Initial", Constants.Signature, Constants.Signature); - repo.Move(originalPath, renamedPath); + Commands.Move(repo, originalPath, renamedPath); File.AppendAllText(Path.Combine(repo.Info.WorkingDirectory, renamedPath), "e\nf\n"); - repo.Stage(renamedPath); + Commands.Stage(repo, renamedPath); Commit @new = repo.Commit("Updated", Constants.Signature, Constants.Signature); - var changes = repo.Diff.Compare(old.Tree, @new.Tree, + using (var changes = repo.Diff.Compare(old.Tree, @new.Tree, compareOptions: new CompareOptions { Similarity = SimilarityOptions.Exact, - }); - - Assert.Equal(2, changes.Count()); - Assert.Equal(0, changes.Renamed.Count()); - Assert.Equal(1, changes.Added.Count()); - Assert.Equal(1, changes.Deleted.Count()); + })) + { + Assert.Equal(2, changes.Count()); + Assert.Empty(changes.Renamed); + Assert.Single(changes.Added); + Assert.Single(changes.Deleted); + } } } @@ -467,28 +491,29 @@ public void CanIncludeUnmodifiedEntriesWhenDetectingTheExactRenamingOfFilesWhenE Touch(repo.Info.WorkingDirectory, originalPath, "a\nb\nc\nd\n"); - repo.Stage(originalPath); + Commands.Stage(repo, originalPath); Commit old = repo.Commit("Initial", Constants.Signature, Constants.Signature); File.Copy(originalFullPath, copiedFullPath); - repo.Stage(copiedPath); + Commands.Stage(repo, copiedPath); Commit @new = repo.Commit("Updated", Constants.Signature, Constants.Signature); - var changes = repo.Diff.Compare(old.Tree, @new.Tree, + using (var changes = repo.Diff.Compare(old.Tree, @new.Tree, compareOptions: new CompareOptions { Similarity = SimilarityOptions.CopiesHarder, IncludeUnmodified = true, - }); - - Assert.Equal(2, changes.Count()); - Assert.Equal(1, changes.Unmodified.Count()); - Assert.Equal(1, changes.Copied.Count()); - Assert.Equal(originalPath, changes.Copied.Single().OldPath); - Assert.Equal(copiedPath, changes.Copied.Single().Path); + })) + { + Assert.Equal(2, changes.Count()); + Assert.Single(changes.Unmodified); + Assert.Single(changes.Copied); + Assert.Equal(originalPath, changes.Copied.Single().OldPath); + Assert.Equal(copiedPath, changes.Copied.Single().Path); + } } } @@ -504,23 +529,24 @@ public void CanNotDetectTheExactRenamingFilesWhenNotEnabled() Touch(repo.Info.WorkingDirectory, originalPath, "a\nb\nc\nd\n"); - repo.Stage(originalPath); + Commands.Stage(repo, originalPath); Commit old = repo.Commit("Initial", Constants.Signature, Constants.Signature); - repo.Move(originalPath, renamedPath); + Commands.Move(repo, originalPath, renamedPath); Commit @new = repo.Commit("Updated", Constants.Signature, Constants.Signature); - var changes = repo.Diff.Compare(old.Tree, @new.Tree, + using (var changes = repo.Diff.Compare(old.Tree, @new.Tree, compareOptions: new CompareOptions { Similarity = SimilarityOptions.None, - }); - - Assert.Equal(2, changes.Count()); - Assert.Equal(0, changes.Renamed.Count()); + })) + { + Assert.Equal(2, changes.Count()); + Assert.Empty(changes.Renamed); + } } } @@ -538,26 +564,27 @@ public void CanDetectTheExactCopyingOfNonModifiedFilesWhenEnabled() Touch(repo.Info.WorkingDirectory, originalPath, "a\nb\nc\nd\n"); - repo.Stage(originalPath); + Commands.Stage(repo, originalPath); Commit old = repo.Commit("Initial", Constants.Signature, Constants.Signature); File.Copy(originalFullPath, copiedFullPath); - repo.Stage(copiedPath); + Commands.Stage(repo, copiedPath); Commit @new = repo.Commit("Updated", Constants.Signature, Constants.Signature); - var changes = repo.Diff.Compare(old.Tree, @new.Tree, + using (var changes = repo.Diff.Compare(old.Tree, @new.Tree, compareOptions: new CompareOptions { Similarity = SimilarityOptions.CopiesHarder, - }); - - Assert.Equal(1, changes.Count()); - Assert.Equal(1, changes.Copied.Count()); - Assert.Equal(originalPath, changes.Copied.Single().OldPath); - Assert.Equal(copiedPath, changes.Copied.Single().Path); + })) + { + Assert.Single(changes); + Assert.Single(changes.Copied); + Assert.Equal(originalPath, changes.Copied.Single().OldPath); + Assert.Equal(copiedPath, changes.Copied.Single().Path); + } } } @@ -575,19 +602,20 @@ public void CanNotDetectTheExactCopyingOfNonModifiedFilesWhenNotEnabled() Touch(repo.Info.WorkingDirectory, originalPath, "a\nb\nc\nd\n"); - repo.Stage(originalPath); + Commands.Stage(repo, originalPath); Commit old = repo.Commit("Initial", Constants.Signature, Constants.Signature); File.Copy(originalFullPath, copiedFullPath); - repo.Stage(copiedPath); + Commands.Stage(repo, copiedPath); Commit @new = repo.Commit("Updated", Constants.Signature, Constants.Signature); - var changes = repo.Diff.Compare(old.Tree, @new.Tree); - - Assert.Equal(1, changes.Count()); - Assert.Equal(0, changes.Copied.Count()); + using (var changes = repo.Diff.Compare(old.Tree, @new.Tree)) + { + Assert.Single(changes); + Assert.Empty(changes.Copied); + } } } @@ -605,29 +633,30 @@ public void CanDetectTheExactCopyingOfModifiedFilesWhenEnabled() Touch(repo.Info.WorkingDirectory, originalPath, "a\nb\nc\nd\n"); - repo.Stage(originalPath); + Commands.Stage(repo, originalPath); Commit old = repo.Commit("Initial", Constants.Signature, Constants.Signature); File.Copy(originalFullPath, copiedFullPath); Touch(repo.Info.WorkingDirectory, originalPath, "e\n"); - repo.Stage(originalPath); - repo.Stage(copiedPath); + Commands.Stage(repo, originalPath); + Commands.Stage(repo, copiedPath); Commit @new = repo.Commit("Updated", Constants.Signature, Constants.Signature); - var changes = repo.Diff.Compare(old.Tree, @new.Tree, + using (var changes = repo.Diff.Compare(old.Tree, @new.Tree, compareOptions: new CompareOptions { Similarity = SimilarityOptions.Copies, - }); - - Assert.Equal(2, changes.Count()); - Assert.Equal(1, changes.Copied.Count()); - Assert.Equal(originalPath, changes.Copied.Single().OldPath); - Assert.Equal(copiedPath, changes.Copied.Single().Path); + })) + { + Assert.Equal(2, changes.Count()); + Assert.Single(changes.Copied); + Assert.Equal(originalPath, changes.Copied.Single().OldPath); + Assert.Equal(copiedPath, changes.Copied.Single().Path); + } } } @@ -645,22 +674,23 @@ public void CanNotDetectTheExactCopyingOfModifiedFilesWhenNotEnabled() Touch(repo.Info.WorkingDirectory, originalPath, "a\nb\nc\nd\n"); - repo.Stage(originalPath); + Commands.Stage(repo, originalPath); Commit old = repo.Commit("Initial", Constants.Signature, Constants.Signature); File.Copy(originalFullPath, copiedFullPath); File.AppendAllText(originalFullPath, "e\n"); - repo.Stage(originalPath); - repo.Stage(copiedPath); + Commands.Stage(repo, originalPath); + Commands.Stage(repo, copiedPath); Commit @new = repo.Commit("Updated", Constants.Signature, Constants.Signature); - var changes = repo.Diff.Compare(old.Tree, @new.Tree); - - Assert.Equal(2, changes.Count()); - Assert.Equal(0, changes.Copied.Count()); + using (var changes = repo.Diff.Compare(old.Tree, @new.Tree)) + { + Assert.Equal(2, changes.Count()); + Assert.Empty(changes.Copied); + } } } @@ -674,19 +704,20 @@ public void CanIncludeUnmodifiedEntriesWhenEnabled() Touch(repo.Info.WorkingDirectory, "a.txt", "abc\ndef\n"); Touch(repo.Info.WorkingDirectory, "b.txt", "abc\ndef\n"); - repo.Stage(new[] {"a.txt", "b.txt"}); + Commands.Stage(repo, new[] {"a.txt", "b.txt"}); Commit old = repo.Commit("Initial", Constants.Signature, Constants.Signature); File.AppendAllText(Path.Combine(repo.Info.WorkingDirectory, "b.txt"), "ghi\njkl\n"); - repo.Stage("b.txt"); + Commands.Stage(repo, "b.txt"); Commit @new = repo.Commit("Updated", Constants.Signature, Constants.Signature); - var changes = repo.Diff.Compare(old.Tree, @new.Tree, - compareOptions: new CompareOptions {IncludeUnmodified = true}); - - Assert.Equal(2, changes.Count()); - Assert.Equal(1, changes.Unmodified.Count()); - Assert.Equal(1, changes.Modified.Count()); + using (var changes = repo.Diff.Compare(old.Tree, @new.Tree, + compareOptions: new CompareOptions { IncludeUnmodified = true })) + { + Assert.Equal(2, changes.Count()); + Assert.Single(changes.Unmodified); + Assert.Single(changes.Modified); + } } } @@ -708,9 +739,9 @@ public void CanDetectTheExactRenamingExactCopyingOfNonModifiedAndModifiedFilesWh Touch(repo.Info.WorkingDirectory, originalPath2, "1\n2\n3\n4\n"); Touch(repo.Info.WorkingDirectory, originalPath3, "5\n6\n7\n8\n"); - repo.Stage(originalPath); - repo.Stage(originalPath2); - repo.Stage(originalPath3); + Commands.Stage(repo, originalPath); + Commands.Stage(repo, originalPath2); + Commands.Stage(repo, originalPath3); Commit old = repo.Commit("Initial", Constants.Signature, Constants.Signature); @@ -722,30 +753,31 @@ public void CanDetectTheExactRenamingExactCopyingOfNonModifiedAndModifiedFilesWh File.Copy(originalFullPath3, copiedFullPath2); File.AppendAllText(originalFullPath3, "9\n"); - repo.Stage(originalPath3); - repo.Stage(copiedPath1); - repo.Stage(copiedPath2); - repo.Move(originalPath, renamedPath); + Commands.Stage(repo, originalPath3); + Commands.Stage(repo, copiedPath1); + Commands.Stage(repo, copiedPath2); + Commands.Move(repo, originalPath, renamedPath); Commit @new = repo.Commit("Updated", Constants.Signature, Constants.Signature); - var changes = repo.Diff.Compare(old.Tree, @new.Tree, + using (var changes = repo.Diff.Compare(old.Tree, @new.Tree, compareOptions: new CompareOptions { Similarity = SimilarityOptions.CopiesHarder, - }); - - Assert.Equal(4, changes.Count()); - Assert.Equal(1, changes.Modified.Count()); - Assert.Equal(1, changes.Renamed.Count()); - Assert.Equal(originalPath, changes.Renamed.Single().OldPath); - Assert.Equal(renamedPath, changes.Renamed.Single().Path); - Assert.Equal(2, changes.Copied.Count()); - Assert.Equal(originalPath2, changes.Copied.ElementAt(0).OldPath); - Assert.Equal(copiedPath1, changes.Copied.ElementAt(0).Path); - Assert.Equal(originalPath3, changes.Copied.ElementAt(1).OldPath); - Assert.Equal(copiedPath2, changes.Copied.ElementAt(1).Path); + })) + { + Assert.Equal(4, changes.Count()); + Assert.Single(changes.Modified); + Assert.Single(changes.Renamed); + Assert.Equal(originalPath, changes.Renamed.Single().OldPath); + Assert.Equal(renamedPath, changes.Renamed.Single().Path); + Assert.Equal(2, changes.Copied.Count()); + Assert.Equal(originalPath2, changes.Copied.ElementAt(0).OldPath); + Assert.Equal(copiedPath1, changes.Copied.ElementAt(0).Path); + Assert.Equal(originalPath3, changes.Copied.ElementAt(1).OldPath); + Assert.Equal(copiedPath2, changes.Copied.ElementAt(1).Path); + } } } /* @@ -784,22 +816,24 @@ public void CanCompareTwoVersionsOfAFileWithATrailingNewlineDeletion(int context Tree rootCommitTree = repo.Lookup("f8d44d7").Tree; Tree commitTreeWithUpdatedFile = repo.Lookup("ec9e401").Tree; - var changes = repo.Diff.Compare(rootCommitTree, commitTreeWithUpdatedFile); - - Assert.Equal(1, changes.Count()); - Assert.Equal(1, changes.Modified.Count()); - - var patch = repo.Diff.Compare(rootCommitTree, commitTreeWithUpdatedFile, - compareOptions: new CompareOptions { ContextLines = contextLines }); + using (var changes = repo.Diff.Compare(rootCommitTree, commitTreeWithUpdatedFile)) + { + Assert.Single(changes); + Assert.Single(changes.Modified); + } - Assert.Equal(expectedPatchLength, patch.Content.Length); + using (var patch = repo.Diff.Compare(rootCommitTree, commitTreeWithUpdatedFile, + compareOptions: new CompareOptions { ContextLines = contextLines })) + { + Assert.Equal(expectedPatchLength, patch.Content.Length); - PatchEntryChanges entryChanges = patch["numbers.txt"]; + PatchEntryChanges entryChanges = patch["numbers.txt"]; - Assert.Equal(2, entryChanges.LinesAdded); - Assert.Equal(1, entryChanges.LinesDeleted); - Assert.Equal(expectedPatchLength, entryChanges.Patch.Length); - Assert.Equal("numbers.txt", entryChanges.Path); + Assert.Equal(2, entryChanges.LinesAdded); + Assert.Equal(1, entryChanges.LinesDeleted); + Assert.Equal(expectedPatchLength, entryChanges.Patch.Length); + Assert.Equal("numbers.txt", entryChanges.Path); + } } } @@ -880,26 +914,28 @@ public void CanCompareTwoVersionsOfAFileWithADiffOfTwoHunks(int contextLines, in Tree rootCommitTree = repo.Lookup("f8d44d7").Tree; Tree mergedCommitTree = repo.Lookup("7252fe2").Tree; - var changes = repo.Diff.Compare(rootCommitTree, mergedCommitTree, compareOptions: compareOptions); - - Assert.Equal(3, changes.Count()); - Assert.Equal(1, changes.Modified.Count()); - Assert.Equal(1, changes.Deleted.Count()); - Assert.Equal(1, changes.Added.Count()); - - Assert.Equal(Mode.Nonexistent, changes.Single(c => c.Path =="my-name-does-not-feel-right.txt").Mode); - - var patch = repo.Diff.Compare(rootCommitTree, mergedCommitTree, compareOptions: compareOptions); + using (var changes = repo.Diff.Compare(rootCommitTree, mergedCommitTree, compareOptions: compareOptions)) + { + Assert.Equal(3, changes.Count()); + Assert.Single(changes.Modified); + Assert.Single(changes.Deleted); + Assert.Single(changes.Added); - PatchEntryChanges entryChanges = patch["numbers.txt"]; + Assert.Equal(Mode.Nonexistent, changes.Single(c => c.Path == "my-name-does-not-feel-right.txt").Mode); + } - Assert.Equal(3, entryChanges.LinesAdded); - Assert.Equal(1, entryChanges.LinesDeleted); - Assert.Equal(Expected("f8d44d7...7252fe2/numbers.txt-{0}-{1}.diff", contextLines, interhunkLines), - entryChanges.Patch); - Assert.Equal(Expected("f8d44d7...7252fe2/full-{0}-{1}.diff", contextLines, interhunkLines), - patch); - Assert.Equal("numbers.txt", entryChanges.Path); + using (var patch = repo.Diff.Compare(rootCommitTree, mergedCommitTree, compareOptions: compareOptions)) + { + PatchEntryChanges entryChanges = patch["numbers.txt"]; + + Assert.Equal(3, entryChanges.LinesAdded); + Assert.Equal(1, entryChanges.LinesDeleted); + Assert.Equal(Expected("f8d44d7...7252fe2/numbers.txt-{0}-{1}.diff", contextLines, interhunkLines), + entryChanges.Patch); + Assert.Equal(Expected("f8d44d7...7252fe2/full-{0}-{1}.diff", contextLines, interhunkLines), + patch); + Assert.Equal("numbers.txt", entryChanges.Path); + } } } @@ -925,13 +961,14 @@ private void CanHandleTwoTreeEntryChangesWithTheSamePath(SimilarityOptions simil Tree treeNew = repo.ObjectDatabase.CreateTree(tdNew); - var changes = repo.Diff.Compare(treeOld, treeNew, + using (var changes = repo.Diff.Compare(treeOld, treeNew, compareOptions: new CompareOptions { Similarity = similarity, - }); - - verifier(path, changes); + })) + { + verifier(path, changes); + } } } @@ -946,8 +983,8 @@ public void CanHandleTwoTreeEntryChangesWithTheSamePathUsingSimilarityNone() (path, changes) => { Assert.Equal(2, changes.Count()); - Assert.Equal(1, changes.Deleted.Count()); - Assert.Equal(1, changes.TypeChanged.Count()); + Assert.Single(changes.Deleted); + Assert.Single(changes.TypeChanged); TreeEntryChanges change = changes.Single(c => c.Path== path); Assert.Equal(Mode.SymbolicLink, change.OldMode); @@ -968,8 +1005,8 @@ public void CanHandleTwoTreeEntryChangesWithTheSamePathUsingSimilarityDefault() (path, changes) => { Assert.Equal(2, changes.Count()); - Assert.Equal(1, changes.Deleted.Count()); - Assert.Equal(1, changes.Renamed.Count()); + Assert.Single(changes.Deleted); + Assert.Single(changes.Renamed); TreeEntryChanges renamed = changes.Renamed.Single(); Assert.Equal(Mode.NonExecutableFile, renamed.OldMode); @@ -993,19 +1030,21 @@ public void CanCompareATreeAgainstANullTree() { Tree tree = repo.Branches["refs/remotes/origin/test"].Tip.Tree; - var changes = repo.Diff.Compare(tree, null); - - Assert.Equal(1, changes.Count()); - Assert.Equal(1, changes.Deleted.Count()); - - Assert.Equal("readme.txt", changes.Deleted.Single().Path); + using (var changes = repo.Diff.Compare(tree, null)) + { + Assert.Single(changes); + Assert.Single(changes.Deleted); - changes = repo.Diff.Compare(null, tree); + Assert.Equal("readme.txt", changes.Deleted.Single().Path); + } - Assert.Equal(1, changes.Count()); - Assert.Equal(1, changes.Added.Count()); + using (var changes = repo.Diff.Compare(null, tree)) + { + Assert.Single(changes); + Assert.Single(changes.Added); - Assert.Equal("readme.txt", changes.Added.Single().Path); + Assert.Equal("readme.txt", changes.Added.Single().Path); + } } } @@ -1015,9 +1054,10 @@ public void ComparingTwoNullTreesReturnsAnEmptyTreeChanges() var path = SandboxStandardTestRepoGitDir(); using (var repo = new Repository(path)) { - var changes = repo.Diff.Compare(default(Tree), default(Tree)); - - Assert.Equal(0, changes.Count()); + using (var changes = repo.Diff.Compare(default(Tree), default(Tree))) + { + Assert.Empty(changes); + } } } @@ -1044,49 +1084,32 @@ public void ComparingReliesOnProvidedConfigEntriesIfAny() repo.Config.Unset("core.filemode"); } - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); - - var options = BuildFakeSystemConfigFilemodeOption(scd, true); - - using (var repo = new Repository(path, options)) + using (var repo = new Repository(path)) { - var changes = repo.Diff.Compare(new[] { file }); - - Assert.Equal(1, changes.Count()); + SetFilemode(repo, true); + using(var changes = repo.Diff.Compare(new[] { file })) + { + Assert.Single(changes); - var change = changes.Modified.Single(); - Assert.Equal(Mode.ExecutableFile, change.OldMode); - Assert.Equal(Mode.NonExecutableFile, change.Mode); + var change = changes.Modified.Single(); + Assert.Equal(Mode.ExecutableFile, change.OldMode); + Assert.Equal(Mode.NonExecutableFile, change.Mode); + } } - options = BuildFakeSystemConfigFilemodeOption(scd, false); - - using (var repo = new Repository(path, options)) + using (var repo = new Repository(path)) { - var changes = repo.Diff.Compare(new[] { file }); - - Assert.Equal(0, changes.Count()); + SetFilemode(repo, false); + using (var changes = repo.Diff.Compare(new[] { file })) + { + Assert.Empty(changes); + } } } - private RepositoryOptions BuildFakeSystemConfigFilemodeOption( - SelfCleaningDirectory scd, - bool value) + void SetFilemode(Repository repo, bool value) { - Directory.CreateDirectory(scd.DirectoryPath); - - var options = new RepositoryOptions - { - SystemConfigurationLocation = Path.Combine( - scd.RootedDirectoryPath, "fake-system.config") - }; - - StringBuilder sb = new StringBuilder() - .AppendFormat("[core]{0}", Environment.NewLine) - .AppendFormat("filemode = {1}{0}", Environment.NewLine, value); - Touch("", options.SystemConfigurationLocation, sb.ToString()); - - return options; + repo.Config.Set("core.filemode", value); } [Fact] @@ -1116,21 +1139,11 @@ public void RetrievingDiffChangesMustAlwaysBeCaseSensitive() using (var repo = new Repository(repoPath)) { - var changes = repo.Diff.Compare(repo.Lookup(treeOldOid), repo.Lookup(treeNewOid)); - - Assert.Equal(ChangeKind.Modified, changes.Single(c => c.Path == "a.txt").Status); - Assert.Equal(ChangeKind.Modified, changes.Single(c => c.Path == "A.TXT").Status); - } - } - - [Fact] - public void CallingCompareWithAnUnsupportedGenericParamThrows() - { - var path = SandboxStandardTestRepoGitDir(); - using (var repo = new Repository(path)) - { - Assert.Throws(() => repo.Diff.Compare(default(Tree), default(Tree))); - Assert.Throws(() => repo.Diff.Compare()); + 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); + } } } @@ -1198,9 +1211,12 @@ public void UsingPatienceAlgorithmCompareOptionProducesPatienceDiff() .Append("+cccccc\n") .Append("+cccccc\n").ToString(); - Assert.Equal(diffDefault, repo.Diff.Compare(treeOld, treeNew)); - Assert.Equal(diffPatience, repo.Diff.Compare(treeOld, treeNew, - compareOptions: new CompareOptions { Algorithm = DiffAlgorithm.Patience })); + 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); } } } diff --git a/LibGit2Sharp.Tests/DiffWorkdirToIndexFixture.cs b/LibGit2Sharp.Tests/DiffWorkdirToIndexFixture.cs index 65f075826..430859577 100644 --- a/LibGit2Sharp.Tests/DiffWorkdirToIndexFixture.cs +++ b/LibGit2Sharp.Tests/DiffWorkdirToIndexFixture.cs @@ -33,11 +33,12 @@ public void CanCompareTheWorkDirAgainstTheIndex() var path = SandboxStandardTestRepoGitDir(); using (var repo = new Repository(path)) { - var changes = repo.Diff.Compare(); - - Assert.Equal(2, changes.Count()); - Assert.Equal("deleted_unstaged_file.txt", changes.Deleted.Single().Path); - Assert.Equal("modified_unstaged_file.txt", changes.Modified.Single().Path); + using (var changes = repo.Diff.Compare()) + { + Assert.Equal(2, changes.Count()); + Assert.Equal("deleted_unstaged_file.txt", changes.Deleted.Single().Path); + Assert.Equal("modified_unstaged_file.txt", changes.Modified.Single().Path); + } } } @@ -51,11 +52,15 @@ public void CanCompareTheWorkDirAgainstTheIndexWithLaxUnmatchedExplicitPathsVali { Assert.Equal(currentStatus, repo.RetrieveStatus(relativePath)); - var changes = repo.Diff.Compare(new[] { relativePath }, false, new ExplicitPathsOptions { ShouldFailOnUnmatchedPath = false }); - Assert.Equal(0, changes.Count()); + using (var changes = repo.Diff.Compare(new[] { relativePath }, false, new ExplicitPathsOptions { ShouldFailOnUnmatchedPath = false })) + { + Assert.Empty(changes); + } - changes = repo.Diff.Compare(new[] { relativePath }); - Assert.Equal(0, changes.Count()); + using (var changes = repo.Diff.Compare(new[] { relativePath })) + { + Assert.Empty(changes); + } } } @@ -85,12 +90,14 @@ public void CallbackForUnmatchedExplicitPathsIsCalledWhenSet(string relativePath { Assert.Equal(currentStatus, repo.RetrieveStatus(relativePath)); - repo.Diff.Compare(new[] { relativePath }, false, new ExplicitPathsOptions + using (var changes = repo.Diff.Compare(new[] { relativePath }, false, new ExplicitPathsOptions { ShouldFailOnUnmatchedPath = false, - OnUnmatchedPath = callback.OnUnmatchedPath }); - - Assert.True(callback.WasCalled); + OnUnmatchedPath = callback.OnUnmatchedPath + })) + { + Assert.True(callback.WasCalled); + } } } @@ -127,49 +134,32 @@ public void ComparingReliesOnProvidedConfigEntriesIfAny() repo.Config.Unset("core.filemode", ConfigurationLevel.Local); } - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); - - var options = BuildFakeSystemConfigFilemodeOption(scd, true); - - using (var repo = new Repository(path, options)) + using (var repo = new Repository(path)) { - var changes = repo.Diff.Compare(new[] { file }); - - Assert.Equal(1, changes.Count()); + SetFilemode(repo, true); + using(var changes = repo.Diff.Compare(new[] { file })) + { + Assert.Single(changes); - var change = changes.Modified.Single(); - Assert.Equal(Mode.ExecutableFile, change.OldMode); - Assert.Equal(Mode.NonExecutableFile, change.Mode); + var change = changes.Modified.Single(); + Assert.Equal(Mode.ExecutableFile, change.OldMode); + Assert.Equal(Mode.NonExecutableFile, change.Mode); + } } - options = BuildFakeSystemConfigFilemodeOption(scd, false); - - using (var repo = new Repository(path, options)) + using (var repo = new Repository(path)) { - var changes = repo.Diff.Compare(new[] { file }); - - Assert.Equal(0, changes.Count()); + SetFilemode(repo, false); + using(var changes = repo.Diff.Compare(new[] { file })) + { + Assert.Empty(changes); + } } } - private RepositoryOptions BuildFakeSystemConfigFilemodeOption( - SelfCleaningDirectory scd, - bool value) + void SetFilemode(Repository repo, bool value) { - Directory.CreateDirectory(scd.DirectoryPath); - - var options = new RepositoryOptions - { - SystemConfigurationLocation = Path.Combine( - scd.RootedDirectoryPath, "fake-system.config") - }; - - StringBuilder sb = new StringBuilder() - .AppendFormat("[core]{0}", Environment.NewLine) - .AppendFormat("filemode = {1}{0}", Environment.NewLine, value); - File.WriteAllText(options.SystemConfigurationLocation, sb.ToString()); - - return options; + repo.Config.Set("core.filemode", value); } [Fact] @@ -178,12 +168,13 @@ public void CanCompareTheWorkDirAgainstTheIndexWithUntrackedFiles() var path = SandboxStandardTestRepoGitDir(); using (var repo = new Repository(path)) { - var changes = repo.Diff.Compare(null, true); - - Assert.Equal(3, changes.Count()); - Assert.Equal("deleted_unstaged_file.txt", changes.Deleted.Single().Path); - Assert.Equal("modified_unstaged_file.txt", changes.Modified.Single().Path); - Assert.Equal("new_untracked_file.txt", changes.Added.Single().Path); + using (var changes = repo.Diff.Compare(null, true)) + { + Assert.Equal(3, changes.Count()); + Assert.Equal("deleted_unstaged_file.txt", changes.Deleted.Single().Path); + Assert.Equal("modified_unstaged_file.txt", changes.Modified.Single().Path); + Assert.Equal("new_untracked_file.txt", changes.Added.Single().Path); + } } } } diff --git a/LibGit2Sharp.Tests/EpochFixture.cs b/LibGit2Sharp.Tests/EpochFixture.cs deleted file mode 100644 index 773f4c846..000000000 --- a/LibGit2Sharp.Tests/EpochFixture.cs +++ /dev/null @@ -1,87 +0,0 @@ -using System; -using LibGit2Sharp.Core; -using Xunit; -using Xunit.Extensions; - -namespace LibGit2Sharp.Tests -{ - public class EpochFixture - { - [Theory] - [InlineData(0)] - [InlineData(17)] - public void UnixTimestampShouldBeCastIntoAUtcBasedDateTimeOffset(long secondsSinceEpoch) - { - DateTimeOffset date = Epoch.ToDateTimeOffset(secondsSinceEpoch, 0); - Assert.Equal(0, date.Offset.TotalMinutes); - - Assert.Equal(TimeSpan.Zero, date.Offset); - Assert.Equal(DateTimeKind.Utc, date.UtcDateTime.Kind); - } - - [Theory] - [InlineData(0, 0)] - [InlineData(17, -120)] - [InlineData(31, 60)] - public void AreEqual(long secondsSinceEpoch, int timezoneOffset) - { - DateTimeOffset one = Epoch.ToDateTimeOffset(secondsSinceEpoch, timezoneOffset); - DateTimeOffset another = Epoch.ToDateTimeOffset(secondsSinceEpoch, timezoneOffset); - - Assert.Equal(one, another); - Assert.Equal(another, one); - - Assert.True(one == another); - Assert.True(another == one); - - Assert.False(one != another); - Assert.False(another != one); - - Assert.Equal(one.GetHashCode(), another.GetHashCode()); - } - - [Theory] - [InlineData(1291801952, "Wed, 08 Dec 2010 09:52:32 +0000")] - [InlineData(1234567890, "Fri, 13 Feb 2009 23:31:30 +0000")] - [InlineData(1288114383, "Tue, 26 Oct 2010 17:33:03 +0000")] - public void UnixTimestampShouldShouldBeCastIntoAPlainUtcDate(long secondsSinceEpoch, string expected) - { - DateTimeOffset expectedDate = DateTimeOffset.Parse(expected); - - DateTimeOffset date = Epoch.ToDateTimeOffset(secondsSinceEpoch, 0); - - Assert.Equal(secondsSinceEpoch, date.ToSecondsSinceEpoch()); - Assert.Equal(expectedDate, date); - Assert.Equal(TimeSpan.Zero, date.Offset); - } - - [Theory] - [InlineData(1250379778, -210, "Sat, 15 Aug 2009 20:12:58 -0330")] - public void UnixTimestampAndTimezoneOffsetShouldBeCastIntoAUtcDateBearingAnOffset(long secondsSinceEpoch, Int32 offset, string expected) - { - DateTimeOffset expectedDate = DateTimeOffset.Parse(expected); - - DateTimeOffset date = Epoch.ToDateTimeOffset(secondsSinceEpoch, offset); - Assert.Equal(offset, date.Offset.TotalMinutes); - Assert.Equal(secondsSinceEpoch, date.ToSecondsSinceEpoch()); - - Assert.Equal(expectedDate, date); - Assert.Equal(expectedDate.Offset, date.Offset); - } - - [Theory] - [InlineData("Wed, 08 Dec 2010 09:52:32 +0000", 1291801952, 0)] - [InlineData("Fri, 13 Feb 2009 23:31:30 +0000", 1234567890, 0)] - [InlineData("Tue, 26 Oct 2010 17:33:03 +0000", 1288114383, 0)] - [InlineData("Sat, 14 Feb 2009 00:31:30 +0100", 1234567890, 60)] - [InlineData("Sat, 15 Aug 2009 20:12:58 -0330", 1250379778, -210)] - [InlineData("Sat, 15 Aug 2009 23:42:58 +0000", 1250379778, 0)] - [InlineData("Sun, 16 Aug 2009 00:42:58 +0100", 1250379778, 60)] - public void DateTimeOffsetShoudlBeCastIntoAUnixTimestampAndATimezoneOffset(string formattedDate, long expectedSeconds, Int32 expectedOffset) - { - DateTimeOffset when = DateTimeOffset.Parse(formattedDate); - DateTimeOffset date = Epoch.ToDateTimeOffset(expectedSeconds, expectedOffset); - Assert.Equal(when, date); - } - } -} diff --git a/LibGit2Sharp.Tests/FetchFixture.cs b/LibGit2Sharp.Tests/FetchFixture.cs index 89a7e8a62..170b64d61 100644 --- a/LibGit2Sharp.Tests/FetchFixture.cs +++ b/LibGit2Sharp.Tests/FetchFixture.cs @@ -22,7 +22,7 @@ public void CanFetchIntoAnEmptyRepository(string url) using (var repo = new Repository(path)) { - Remote remote = repo.Network.Remotes.Add(remoteName, url); + repo.Network.Remotes.Add(remoteName, url); // Set up structures for the expected results // and verifying the RemoteUpdateTips callback. @@ -44,7 +44,7 @@ public void CanFetchIntoAnEmptyRepository(string url) } // Perform the actual fetch - repo.Network.Fetch(remote, new FetchOptions { OnUpdateTips = expectedFetchState.RemoteUpdateTipsHandler }); + Commands.Fetch(repo, remoteName, new string[0], new FetchOptions { OnUpdateTips = expectedFetchState.RemoteUpdateTipsHandler }, null); // Verify the expected expectedFetchState.CheckUpdatedReferences(repo); @@ -61,13 +61,13 @@ public void CanFetchIntoAnEmptyRepositoryWithCredentials() using (var repo = new Repository(path)) { - Remote remote = repo.Network.Remotes.Add(remoteName, Constants.PrivateRepoUrl); + repo.Network.Remotes.Add(remoteName, Constants.PrivateRepoUrl); // Perform the actual fetch - repo.Network.Fetch(remote, new FetchOptions + Commands.Fetch(repo, remoteName, new string[0], new FetchOptions { CredentialsProvider = Constants.PrivateRepoCredentials - }); + }, null); } } @@ -81,7 +81,7 @@ public void CanFetchAllTagsIntoAnEmptyRepository(string url) using (var repo = new Repository(path)) { - Remote remote = repo.Network.Remotes.Add(remoteName, url); + repo.Network.Remotes.Add(remoteName, url); // Set up structures for the expected results // and verifying the RemoteUpdateTips callback. @@ -101,16 +101,16 @@ public void CanFetchAllTagsIntoAnEmptyRepository(string url) } // Perform the actual fetch - repo.Network.Fetch(remote, new FetchOptions { + Commands.Fetch(repo, remoteName, new string[0], new FetchOptions { TagFetchMode = TagFetchMode.All, OnUpdateTips = expectedFetchState.RemoteUpdateTipsHandler - }); + }, null); // Verify the expected expectedFetchState.CheckUpdatedReferences(repo); // Verify the reflog entries - Assert.Equal(1, repo.Refs.Log(string.Format("refs/remotes/{0}/master", remoteName)).Count()); // Branches are also retrieved + Assert.Single(repo.Refs.Log(string.Format("refs/remotes/{0}/master", remoteName))); // Branches are also retrieved } } @@ -124,7 +124,7 @@ public void CanFetchCustomRefSpecsIntoAnEmptyRepository(string url, string local using (var repo = new Repository(path)) { - Remote remote = repo.Network.Remotes.Add(remoteName, url); + repo.Network.Remotes.Add(remoteName, url); string refSpec = string.Format("refs/heads/{2}:refs/remotes/{0}/{1}", remoteName, localBranchName, remoteBranchName); @@ -147,17 +147,17 @@ public void CanFetchCustomRefSpecsIntoAnEmptyRepository(string url, string local } // Perform the actual fetch - repo.Network.Fetch(remote, new string[] { refSpec }, new FetchOptions { + Commands.Fetch(repo, remoteName, new string[] { refSpec }, new FetchOptions { TagFetchMode = TagFetchMode.None, OnUpdateTips = expectedFetchState.RemoteUpdateTipsHandler - }); + }, null); // Verify the expected expectedFetchState.CheckUpdatedReferences(repo); // Verify the reflog entries var reflogEntry = repo.Refs.Log(string.Format("refs/remotes/{0}/{1}", remoteName, localBranchName)).Single(); - Assert.True(reflogEntry.Message.StartsWith("fetch ")); + Assert.StartsWith("fetch ", reflogEntry.Message); } } @@ -177,11 +177,11 @@ public void FetchRespectsConfiguredAutoTagSetting(TagFetchMode tagFetchMode, int Assert.NotNull(remote); // Update the configured autotag setting. - repo.Network.Remotes.Update(remote, + repo.Network.Remotes.Update(remoteName, r => r.TagFetchMode = tagFetchMode); // Perform the actual fetch. - repo.Network.Fetch(remote); + Commands.Fetch(repo, remoteName, new string[0], null, null); // Verify the number of fetched tags. Assert.Equal(expectedTagCount, repo.Tags.Count()); @@ -199,7 +199,7 @@ public void CanFetchAllTagsAfterAnInitialClone() using (var repo = new Repository(clonedRepoPath)) { - repo.Fetch("origin", new FetchOptions { TagFetchMode = TagFetchMode.All }); + Commands.Fetch(repo, "origin", new string[0], new FetchOptions { TagFetchMode = TagFetchMode.All }, null); } } @@ -207,15 +207,13 @@ public void CanFetchAllTagsAfterAnInitialClone() public void FetchHonorsTheFetchPruneConfigurationEntry() { var source = SandboxBareTestRepo(); - var url = new Uri(Path.GetFullPath(source)).AbsoluteUri; + var url = new Uri($"file://{Path.GetFullPath(source)}").AbsoluteUri; var scd = BuildSelfCleaningDirectory(); string clonedRepoPath = Repository.Clone(url, scd.DirectoryPath); - var options = BuildFakeConfigs(BuildSelfCleaningDirectory()); - - using (var clonedRepo = new Repository(clonedRepoPath, options)) + using (var clonedRepo = new Repository(clonedRepoPath)) { Assert.Equal(5, clonedRepo.Branches.Count(b => b.IsRemote)); @@ -227,19 +225,71 @@ public void FetchHonorsTheFetchPruneConfigurationEntry() // No pruning when the configuration entry isn't defined Assert.Null(clonedRepo.Config.Get("fetch.prune")); - clonedRepo.Fetch("origin"); + Commands.Fetch(clonedRepo, "origin", new string[0], null, null); Assert.Equal(5, clonedRepo.Branches.Count(b => b.IsRemote)); // No pruning when the configuration entry is set to false clonedRepo.Config.Set("fetch.prune", false); - clonedRepo.Fetch("origin"); + Commands.Fetch(clonedRepo, "origin", new string[0], null, null); Assert.Equal(5, clonedRepo.Branches.Count(b => b.IsRemote)); // Auto pruning when the configuration entry is set to true clonedRepo.Config.Set("fetch.prune", true); - clonedRepo.Fetch("origin"); + Commands.Fetch(clonedRepo, "origin", new string[0], null, null); Assert.Equal(4, clonedRepo.Branches.Count(b => b.IsRemote)); } } + + [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", new string[0], 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", new string[0], 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", new string[0], options, null)); + } + } + } } diff --git a/LibGit2Sharp.Tests/FileHistoryFixture.cs b/LibGit2Sharp.Tests/FileHistoryFixture.cs index 3d09858b3..e6465d1ac 100644 --- a/LibGit2Sharp.Tests/FileHistoryFixture.cs +++ b/LibGit2Sharp.Tests/FileHistoryFixture.cs @@ -10,54 +10,56 @@ namespace LibGit2Sharp.Tests { public class FileHistoryFixture : BaseFixture { - [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.Equal(1, fileHistoryEntries.Count()); - 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); - } - } + //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)] @@ -94,11 +96,11 @@ public void CanFollowBranches(string specificRepoPath) dummy, master9); repo.CreateBranch("master", master10); - repo.Checkout("master", new CheckoutOptions { CheckoutModifiers = CheckoutModifiers.Force }); + Commands.Checkout(repo, "master", new CheckoutOptions { CheckoutModifiers = CheckoutModifiers.Force }); // Test --date-order. var timeHistory = repo.Commits.QueryBy(path, - new FollowFilter { SortBy = CommitSortStrategies.Time }); + new CommitFilter { SortBy = CommitSortStrategies.Time }); var timeCommits = new List { master10, // master @@ -117,7 +119,7 @@ public void CanFollowBranches(string specificRepoPath) // Test --topo-order. var topoHistory = repo.Commits.QueryBy(path, - new FollowFilter { SortBy = CommitSortStrategies.Topological }); + new CommitFilter { SortBy = CommitSortStrategies.Topological }); var topoCommits = new List { master10, // master @@ -151,8 +153,8 @@ public void CanTellComplexCommitHistory() var commit2 = MakeAndCommitChange(repo, repoPath, path1, "Hello World again"); // Move the first file to a new directory. - var newPath1 = Path.Combine(SubFolderPath1, path1); - repo.Move(path1, newPath1); + var newPath1 = Path.Combine(SubFolderPath1, path1).Replace(@"\", "/"); + Commands.Move(repo, path1, newPath1); var commit3 = repo.Commit("Moved " + path1 + " to " + newPath1, Constants.Signature, Constants.Signature); @@ -161,7 +163,8 @@ public void CanTellComplexCommitHistory() var commit4 = MakeAndCommitChange(repo, repoPath, newPath1, "I have done it again!"); // Perform tests. - var fileHistoryEntries = repo.Commits.QueryBy(newPath1).ToList(); + 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()); @@ -222,8 +225,8 @@ public void CanTellSingleCommitHistory() IEnumerable history = repo.Commits.QueryBy(path).ToList(); var changedBlobs = history.Blobs().Distinct(); - Assert.Equal(1, history.Count()); - Assert.Equal(1, changedBlobs.Count()); + Assert.Single(history); + Assert.Single(changedBlobs); Assert.Equal(path, history.First().Path); Assert.Equal(commit, history.First().Commit); @@ -238,8 +241,8 @@ public void EmptyRepositoryHasNoHistory() using (var repo = new Repository(repoPath)) { IEnumerable history = repo.Commits.QueryBy("Test.txt").ToList(); - Assert.Equal(0, history.Count()); - Assert.Equal(0, history.Blobs().Count()); + Assert.Empty(history); + Assert.Empty(history.Blobs()); } } @@ -255,33 +258,33 @@ public void UnsupportedSortStrategyThrows() MakeAndCommitChange(repo, repoPath, path, "Hello World"); Assert.Throws(() => - repo.Commits.QueryBy(path, new FollowFilter + repo.Commits.QueryBy(path, new CommitFilter { SortBy = CommitSortStrategies.None })); Assert.Throws(() => - repo.Commits.QueryBy(path, new FollowFilter + repo.Commits.QueryBy(path, new CommitFilter { SortBy = CommitSortStrategies.Reverse })); Assert.Throws(() => - repo.Commits.QueryBy(path, new FollowFilter + repo.Commits.QueryBy(path, new CommitFilter { SortBy = CommitSortStrategies.Reverse | CommitSortStrategies.Topological })); Assert.Throws(() => - repo.Commits.QueryBy(path, new FollowFilter + repo.Commits.QueryBy(path, new CommitFilter { SortBy = CommitSortStrategies.Reverse | CommitSortStrategies.Time })); Assert.Throws(() => - repo.Commits.QueryBy(path, new FollowFilter + repo.Commits.QueryBy(path, new CommitFilter { SortBy = CommitSortStrategies.Reverse | CommitSortStrategies.Topological | @@ -365,7 +368,7 @@ private Commit MakeAndCommitChange(Repository repo, string repoPath, string path string message = null) { Touch(repoPath, path, text); - repo.Stage(path); + Commands.Stage(repo, path); var commitSignature = GetNextSignature(); return repo.Commit(message ?? "Changed " + path, commitSignature, commitSignature); diff --git a/LibGit2Sharp.Tests/FilterBranchFixture.cs b/LibGit2Sharp.Tests/FilterBranchFixture.cs index aed628a15..60aee38f3 100644 --- a/LibGit2Sharp.Tests/FilterBranchFixture.cs +++ b/LibGit2Sharp.Tests/FilterBranchFixture.cs @@ -29,7 +29,7 @@ public override void Dispose() public void CanRewriteHistoryWithoutChangingCommitMetadata() { var originalRefs = repo.Refs.ToList().OrderBy(r => r.CanonicalName); - var commits = repo.Commits.QueryBy(new CommitFilter { Since = repo.Refs }).ToArray(); + var commits = repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = repo.Refs }).ToArray(); // Noop header rewriter repo.Refs.RewriteHistory(new RewriteHistoryOptions @@ -42,14 +42,14 @@ public void CanRewriteHistoryWithoutChangingCommitMetadata() AssertSucceedingButNotError(); Assert.Equal(originalRefs, repo.Refs.ToList().OrderBy(r => r.CanonicalName)); - Assert.Equal(commits, repo.Commits.QueryBy(new CommitFilter { Since = repo.Refs }).ToArray()); + Assert.Equal(commits, repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = repo.Refs }).ToArray()); } [Fact] public void CanRewriteHistoryWithoutChangingTrees() { var originalRefs = repo.Refs.ToList().OrderBy(r => r.CanonicalName); - var commits = repo.Commits.QueryBy(new CommitFilter { Since = repo.Refs }).ToArray(); + var commits = repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = repo.Refs }).ToArray(); // Noop tree rewriter repo.Refs.RewriteHistory(new RewriteHistoryOptions @@ -62,14 +62,14 @@ public void CanRewriteHistoryWithoutChangingTrees() AssertSucceedingButNotError(); Assert.Equal(originalRefs, repo.Refs.ToList().OrderBy(r => r.CanonicalName)); - Assert.Equal(commits, repo.Commits.QueryBy(new CommitFilter { Since = repo.Refs }).ToArray()); + Assert.Equal(commits, repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = repo.Refs }).ToArray()); } [Fact] public void CanRollbackRewriteByThrowingInOnCompleting() { var originalRefs = repo.Refs.ToList().OrderBy(r => r.CanonicalName); - var commits = repo.Commits.QueryBy(new CommitFilter { Since = repo.Refs }).ToArray(); + var commits = repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = repo.Refs }).ToArray(); Assert.Throws( () => @@ -90,14 +90,14 @@ public void CanRollbackRewriteByThrowingInOnCompleting() AssertSucceedingButNotError(); Assert.Equal(originalRefs, repo.Refs.ToList().OrderBy(r => r.CanonicalName)); - Assert.Equal(commits, repo.Commits.QueryBy(new CommitFilter { Since = repo.Refs }).ToArray()); + Assert.Equal(commits, repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = repo.Refs }).ToArray()); } [Fact] public void ErrorThrownInOnErrorTakesPrecedenceOverErrorDuringCommitHeaderRewriter() { var originalRefs = repo.Refs.ToList().OrderBy(r => r.CanonicalName); - var commits = repo.Commits.QueryBy(new CommitFilter { Since = repo.Refs }).ToArray(); + var commits = repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = repo.Refs }).ToArray(); var thrown = Assert.Throws( () => @@ -117,14 +117,14 @@ public void ErrorThrownInOnErrorTakesPrecedenceOverErrorDuringCommitHeaderRewrit Assert.Equal("From CommitHeaderRewriter", thrown.InnerException.Message); Assert.Equal(originalRefs, repo.Refs.ToList().OrderBy(r => r.CanonicalName)); - Assert.Equal(commits, repo.Commits.QueryBy(new CommitFilter { Since = repo.Refs }).ToArray()); + Assert.Equal(commits, repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = repo.Refs }).ToArray()); } [Fact] public void ErrorThrownInOnErrorTakesPrecedenceOverErrorDuringCommitTreeRewriter() { var originalRefs = repo.Refs.ToList().OrderBy(r => r.CanonicalName); - var commits = repo.Commits.QueryBy(new CommitFilter { Since = repo.Refs }).ToArray(); + var commits = repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = repo.Refs }).ToArray(); var thrown = Assert.Throws( () => @@ -144,13 +144,13 @@ public void ErrorThrownInOnErrorTakesPrecedenceOverErrorDuringCommitTreeRewriter Assert.Equal("From CommitTreeRewriter", thrown.InnerException.Message); Assert.Equal(originalRefs, repo.Refs.ToList().OrderBy(r => r.CanonicalName)); - Assert.Equal(commits, repo.Commits.QueryBy(new CommitFilter { Since = repo.Refs }).ToArray()); + Assert.Equal(commits, repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = repo.Refs }).ToArray()); } [Fact] public void CanRewriteAuthorOfCommits() { - var commits = repo.Commits.QueryBy(new CommitFilter { Since = repo.Refs }).ToArray(); + var commits = repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = repo.Refs }).ToArray(); repo.Refs.RewriteHistory(new RewriteHistoryOptions { OnError = OnError, @@ -164,7 +164,7 @@ public void CanRewriteAuthorOfCommits() var nonBackedUpRefs = repo.Refs.Where( x => !x.CanonicalName.StartsWith("refs/original/") && !x.CanonicalName.StartsWith("refs/notes/")); - Assert.Empty(repo.Commits.QueryBy(new CommitFilter { Since = nonBackedUpRefs }) + Assert.Empty(repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = nonBackedUpRefs }) .Where(c => c.Author.Name != "Ben Straub")); } @@ -191,10 +191,10 @@ public void CanRewriteAuthorOfCommitsOnlyBeingPointedAtByTags() AssertSucceedingButNotError(); var lightweightTag = repo.Tags["so-lonely"]; - Assert.Equal("Bam!\n", ((Commit)lightweightTag.Target).Message); + Assert.Equal("Bam!", ((Commit)lightweightTag.Target).Message); var annotatedTag = repo.Tags["so-lonely-but-annotated"]; - Assert.Equal("Bam!\n", ((Commit)annotatedTag.Target).Message); + Assert.Equal("Bam!", ((Commit)annotatedTag.Target).Message); } [Fact] @@ -217,7 +217,7 @@ public void CanRewriteTrees() [Fact] public void CanRewriteTreesByInjectingTreeEntry() { - var commits = repo.Commits.QueryBy(new CommitFilter { Since = repo.Branches }).ToArray(); + var commits = repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = repo.Branches }).ToArray(); var currentReadme = repo.Head["README"]; @@ -236,7 +236,7 @@ public void CanRewriteTreesByInjectingTreeEntry() Assert.Equal(new Commit[0], repo.Commits - .QueryBy(new CommitFilter {Since = repo.Branches}) + .QueryBy(new CommitFilter {IncludeReachableFrom = repo.Branches}) .Where(c => c["README"] != null && c["README"].Target.Id != currentReadme.Target.Id) .ToArray()); @@ -366,7 +366,7 @@ public void OnlyRewriteSelectedCommits() var commit = repo.Branches["packed"].Tip; var parent = commit.Parents.Single(); - Assert.True(parent.Sha.StartsWith("5001298")); + Assert.StartsWith("5001298", parent.Sha); Assert.NotEqual(Constants.Signature, commit.Author); Assert.NotEqual(Constants.Signature, parent.Author); @@ -495,7 +495,7 @@ public void DoesNotRewriteRefsThatDontChange() var parents = repo.Branches["br2"].Tip.Parents.ToList(); Assert.Equal(2, parents.Count()); Assert.NotEmpty(parents.Where(c => c.Sha.StartsWith("9fd738e"))); - Assert.Equal("abc\n", parents.Single(c => !c.Sha.StartsWith("9fd738e")).Message); + Assert.Equal("abc", parents.Single(c => !c.Sha.StartsWith("9fd738e")).Message); } [Fact] @@ -530,7 +530,7 @@ public void CanNotOverWriteBackedUpReferences() AssertErrorFired(ex); AssertSucceedingNotFired(); - Assert.Equal("abc\n", repo.Head.Tip.Message); + Assert.Equal("abc", repo.Head.Tip.Message); var newOriginalRefs = repo.Refs.FromGlob("refs/original/*").OrderBy(r => r.CanonicalName).ToArray(); Assert.Equal(originalRefs, newOriginalRefs); @@ -541,7 +541,7 @@ public void CanNotOverWriteBackedUpReferences() [Fact] public void CanNotOverWriteAnExistingReference() { - var commits = repo.Commits.QueryBy(new CommitFilter { Since = repo.Refs }).ToArray(); + var commits = repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = repo.Refs }).ToArray(); var ex = Assert.Throws( () => @@ -557,7 +557,7 @@ public void CanNotOverWriteAnExistingReference() AssertErrorFired(ex); AssertSucceedingNotFired(); - Assert.Equal(0, repo.Refs.FromGlob("refs/original/*").Count()); + Assert.Empty(repo.Refs.FromGlob("refs/original/*")); } // Graft the orphan "test" branch to the tip of "packed" @@ -633,7 +633,7 @@ public void CanRewriteParentWithRewrittenCommit() var commitToRewrite = repo.Lookup("6dcf9bf"); var newParent = repo.Branches["packed"].Tip; - Assert.True(newParent.Sha.StartsWith("41bc8c6")); + Assert.StartsWith("41bc8c6", newParent.Sha); repo.Refs.RewriteHistory(new RewriteHistoryOptions { @@ -649,22 +649,22 @@ public void CanRewriteParentWithRewrittenCommit() AssertSucceedingButNotError(); // Assert "packed" hasn't been rewritten - Assert.True(repo.Branches["packed"].Tip.Sha.StartsWith("41bc8c6")); + Assert.StartsWith("41bc8c6", repo.Branches["packed"].Tip.Sha); // Assert (test, tag: lw, tag: e90810b, tag: test) have been rewritten var rewrittenTestCommit = repo.Branches["test"].Tip; - Assert.True(rewrittenTestCommit.Sha.StartsWith("f558880")); + Assert.StartsWith("f558880", rewrittenTestCommit.Sha); Assert.Equal(rewrittenTestCommit, repo.Lookup("refs/tags/lw^{commit}")); Assert.Equal(rewrittenTestCommit, repo.Lookup("refs/tags/e90810b^{commit}")); Assert.Equal(rewrittenTestCommit, repo.Lookup("refs/tags/test^{commit}")); // Assert parent of rewritten commit var rewrittenTestCommitParent = rewrittenTestCommit.Parents.Single(); - Assert.True(rewrittenTestCommitParent.Sha.StartsWith("0c25efa")); + Assert.StartsWith("0c25efa", rewrittenTestCommitParent.Sha); // Assert grand parent of rewritten commit var rewrittenTestCommitGrandParent = rewrittenTestCommitParent.Parents.Single(); - Assert.True(rewrittenTestCommitGrandParent.Sha.StartsWith("41bc8c6")); + Assert.StartsWith("41bc8c6", rewrittenTestCommitGrandParent.Sha); } [Fact] @@ -700,7 +700,7 @@ public void CanProvideNewNamesForTags() CommitHeaderRewriter = c => CommitRewriteInfo.From(c, message: ""), TagNameRewriter = TagNameRewriter, - }, repo.Commits.QueryBy(new CommitFilter { Since = repo.Refs["refs/heads/test"] })); + }, repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = repo.Refs["refs/heads/test"] })); AssertSucceedingButNotError(); @@ -791,7 +791,7 @@ public void HandlesNameRewritingOfChainedTags() var newCommit = newAnnotationC.Target as Commit; Assert.NotNull(newCommit); Assert.NotEqual(newCommit, theCommit); - Assert.Equal("Rewrote\n", newCommit.Message); + Assert.Equal("Rewrote", newCommit.Message); // Ensure the original tag doesn't exist anymore Assert.Null(repo.Tags["lightweightA"]); @@ -807,7 +807,7 @@ 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 { Since = originalNotesRefs }).ToArray(); + var commits = repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = originalNotesRefs }).ToArray(); repo.Refs.RewriteHistory(new RewriteHistoryOptions { diff --git a/LibGit2Sharp.Tests/FilterFixture.cs b/LibGit2Sharp.Tests/FilterFixture.cs new file mode 100644 index 000000000..4de354003 --- /dev/null +++ b/LibGit2Sharp.Tests/FilterFixture.cs @@ -0,0 +1,489 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.IO; +using LibGit2Sharp.Tests.TestHelpers; +using Xunit; +using System.Threading.Tasks; + +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 void 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.Factory.StartNew(() => + { + string repoPath = InitNewRepository(); + return CheckoutFileForSmudge(repoPath, branchName, encodedInput); + }); + } + + Task.WaitAll(tasks); + + foreach(var task in tasks) + { + FileInfo expectedFile = task.Result; + + string readAllText = File.ReadAllText(expectedFile.FullName); + Assert.Equal(decodedInput, readAllText); + } + } + finally + { + GlobalSettings.DeregisterFilter(registration); + } + } + + [Fact] + public void WhenCheckingOutAFileFileSmudgeWritesCorrectFileToWorkingDirectory() + { + const string decodedInput = "This is a substitution cipher"; + const string encodedInput = "Guvf vf n fhofgvghgvba pvcure"; + + const string branchName = "branch"; + string repoPath = InitNewRepository(); + + Action smudgeCallback = SubstitutionCipherFilter.RotateByThirteenPlaces; + + var filter = new FakeFilter(FilterName, attributes, null, smudgeCallback); + var registration = GlobalSettings.RegisterFilter(filter); + + try + { + FileInfo expectedFile = CheckoutFileForSmudge(repoPath, branchName, encodedInput); + + string combine = Path.Combine(repoPath, "..", expectedFile.Name); + string readAllText = File.ReadAllText(combine); + Assert.Equal(decodedInput, readAllText); + } + finally + { + GlobalSettings.DeregisterFilter(registration); + } + } + + [Fact] + public void CanFilterLargeFiles() + { + const int ContentLength = 128 * 1024 * 1024 - 13; + const char ContentValue = 'x'; + + char[] content = (new string(ContentValue, 1024)).ToCharArray(); + + string repoPath = InitNewRepository(); + + var filter = new FileExportFilter(FilterName, attributes); + var registration = GlobalSettings.RegisterFilter(filter); + + try + { + string filePath = Path.Combine(Directory.GetParent(repoPath).Parent.FullName, Guid.NewGuid().ToString() + ".blob"); + FileInfo contentFile = new FileInfo(filePath); + using (var writer = new StreamWriter(contentFile.OpenWrite()) { AutoFlush = true }) + { + int remain = ContentLength; + + while (remain > 0) + { + int chunkSize = remain > content.Length ? content.Length : remain; + writer.Write(content, 0, chunkSize); + remain -= chunkSize; + } + } + + string attributesPath = Path.Combine(Directory.GetParent(repoPath).Parent.FullName, ".gitattributes"); + FileInfo attributesFile = new FileInfo(attributesPath); + + using (Repository repo = new Repository(repoPath)) + { + CreateConfigurationWithDummyUser(repo, Constants.Identity); + File.WriteAllText(attributesPath, "*.blob filter=test"); + Commands.Stage(repo, attributesFile.Name); + Commands.Stage(repo, contentFile.Name); + repo.Commit("test", Constants.Signature, Constants.Signature); + contentFile.Delete(); + Commands.Checkout(repo, "HEAD", new CheckoutOptions() { CheckoutModifiers = CheckoutModifiers.Force }); + } + + contentFile = new FileInfo(filePath); + Assert.True(contentFile.Exists, "Contents not restored correctly by forced checkout."); + using (StreamReader reader = contentFile.OpenText()) + { + int totalRead = 0; + char[] block = new char[1024]; + int read; + while ((read = reader.Read(block, 0, block.Length)) > 0) + { + Assert.True(CharArrayAreEqual(block, content, read)); + totalRead += read; + } + + Assert.Equal(ContentLength, totalRead); + } + + contentFile.Delete(); + } + finally + { + GlobalSettings.DeregisterFilter(registration); + } + } + + [Fact] + public void DoubleRegistrationFailsButDoubleDeregistrationDoesNot() + { + Assert.Empty(GlobalSettings.GetRegisteredFilters()); + + var filter = new EmptyFilter(FilterName, attributes); + var registration = GlobalSettings.RegisterFilter(filter); + + Assert.Throws(() => { GlobalSettings.RegisterFilter(filter); }); + Assert.Single(GlobalSettings.GetRegisteredFilters()); + + Assert.True(registration.IsValid, "FilterRegistration.IsValid should be true."); + + GlobalSettings.DeregisterFilter(registration); + Assert.Empty(GlobalSettings.GetRegisteredFilters()); + + Assert.False(registration.IsValid, "FilterRegistration.IsValid should be false."); + + GlobalSettings.DeregisterFilter(registration); + Assert.Empty(GlobalSettings.GetRegisteredFilters()); + + Assert.False(registration.IsValid, "FilterRegistration.IsValid should be false."); + } + + private unsafe bool CharArrayAreEqual(char[] array1, char[] array2, int count) + { + if (ReferenceEquals(array1, array2)) + { + return true; + } + if (ReferenceEquals(array1, null) || ReferenceEquals(null, array2)) + { + return false; + } + if (array1.Length < count || array2.Length < count) + { + return false; + } + + int len = count * sizeof(char); + int cnt = len / sizeof(long); + + fixed (char* c1 = array1, c2 = array2) + { + long* p1 = (long*)c1, + p2 = (long*)c2; + + for (int i = 0; i < cnt; i++) + { + if (p1[i] != p2[i]) + { + return false; + } + } + + byte* b1 = (byte*)c1, + b2 = (byte*)c2; + + for (int i = len * sizeof(long); i < len; i++) + { + if (b1[i] != b2[i]) + { + return false; + } + } + } + + return true; + } + + private FileInfo CheckoutFileForSmudge(string repoPath, string branchName, string content) + { + FileInfo expectedPath; + using (var repo = CreateTestRepository(repoPath)) + { + StageNewFile(repo, content); + + repo.Commit("Initial commit", Constants.Signature, Constants.Signature); + + expectedPath = CommitFileOnBranch(repo, branchName, content); + + Commands.Checkout(repo, "master"); + + Commands.Checkout(repo, branchName); + } + return expectedPath; + } + + private static FileInfo CommitFileOnBranch(Repository repo, string branchName, String content) + { + var branch = repo.CreateBranch(branchName); + Commands.Checkout(repo, branch.FriendlyName); + + FileInfo expectedPath = StageNewFile(repo, content); + repo.Commit("Commit", Constants.Signature, Constants.Signature); + return expectedPath; + } + + private static FileInfo StageNewFile(IRepository repo, string contents = "null") + { + string newFilePath = Touch(repo.Info.WorkingDirectory, Guid.NewGuid() + ".txt", contents); + var stageNewFile = new FileInfo(newFilePath); + Commands.Stage(repo, newFilePath); + return stageNewFile; + } + + private Repository CreateTestRepository(string path) + { + var repository = new Repository(path); + CreateConfigurationWithDummyUser(repository, Constants.Identity); + CreateAttributesFile(repository, "* filter=test"); + return repository; + } + + class EmptyFilter : Filter + { + public EmptyFilter(string name, IEnumerable attributes) + : base(name, attributes) + { } + } + + class FakeFilter : Filter + { + private readonly Action cleanCallback; + private readonly Action smudgeCallback; + private readonly Action initCallback; + + public FakeFilter(string name, IEnumerable attributes, + Action cleanCallback = null, + Action smudgeCallback = null, + Action initCallback = null) + : base(name, attributes) + { + this.cleanCallback = cleanCallback; + this.smudgeCallback = smudgeCallback; + this.initCallback = initCallback; + } + + protected override void Clean(string path, string root, Stream input, Stream output) + { + if (cleanCallback == null) + { + base.Clean(path, root, input, output); + } + else + { + cleanCallback(input, output); + } + } + + protected override void Smudge(string path, string root, Stream input, Stream output) + { + if (smudgeCallback == null) + { + base.Smudge(path, root, input, output); + } + else + { + smudgeCallback(input, output); + } + } + + protected override void Initialize() + { + if (initCallback == null) + { + base.Initialize(); + } + else + { + initCallback(); + } + } + } + } +} diff --git a/LibGit2Sharp.Tests/FilterSubstitutionCipherFixture.cs b/LibGit2Sharp.Tests/FilterSubstitutionCipherFixture.cs new file mode 100644 index 000000000..89b61c546 --- /dev/null +++ b/LibGit2Sharp.Tests/FilterSubstitutionCipherFixture.cs @@ -0,0 +1,215 @@ +using System; +using System.Collections.Generic; +using System.IO; +using LibGit2Sharp.Tests.TestHelpers; +using Xunit; +using Xunit.Extensions; + +namespace LibGit2Sharp.Tests +{ + public class FilterSubstitutionCipherFixture : BaseFixture + { + [Fact] + public void SmugdeIsNotCalledForFileWhichDoesNotMatchAnAttributeEntry() + { + const string decodedInput = "This is a substitution cipher"; + const string encodedInput = "Guvf vf n fhofgvghgvba pvcure"; + + var attributes = new List { new FilterAttributeEntry("rot13") }; + var filter = new SubstitutionCipherFilter("cipher-filter", attributes); + var filterRegistration = GlobalSettings.RegisterFilter(filter); + + string repoPath = InitNewRepository(); + string fileName = Guid.NewGuid() + ".rot13"; + using (var repo = new Repository(repoPath)) + { + CreateConfigurationWithDummyUser(repo, Constants.Identity); + CreateAttributesFile(repo, "*.rot13 filter=rot13"); + + var blob = CommitOnBranchAndReturnDatabaseBlob(repo, fileName, decodedInput); + var textDetected = blob.GetContentText(); + + Assert.Equal(encodedInput, textDetected); + Assert.Equal(1, filter.CleanCalledCount); + Assert.Equal(0, filter.SmudgeCalledCount); + + var branch = repo.CreateBranch("delete-files"); + Commands.Checkout(repo, branch.FriendlyName); + + DeleteFile(repo, fileName); + + Commands.Checkout(repo, "master"); + + var fileContents = ReadTextFromFile(repo, fileName); + Assert.Equal(1, filter.SmudgeCalledCount); + Assert.Equal(decodedInput, fileContents); + } + + GlobalSettings.DeregisterFilter(filterRegistration); + } + + [Fact] + public void CorrectlyEncodesAndDecodesInput() + { + const string decodedInput = "This is a substitution cipher"; + const string encodedInput = "Guvf vf n fhofgvghgvba pvcure"; + + var attributes = new List { new FilterAttributeEntry("rot13") }; + var filter = new SubstitutionCipherFilter("cipher-filter", attributes); + var filterRegistration = GlobalSettings.RegisterFilter(filter); + + string repoPath = InitNewRepository(); + string fileName = Guid.NewGuid() + ".rot13"; + using (var repo = new Repository(repoPath)) + { + CreateConfigurationWithDummyUser(repo, Constants.Identity); + CreateAttributesFile(repo, "*.rot13 filter=rot13"); + + var blob = CommitOnBranchAndReturnDatabaseBlob(repo, fileName, decodedInput); + var textDetected = blob.GetContentText(); + + Assert.Equal(encodedInput, textDetected); + Assert.Equal(1, filter.CleanCalledCount); + Assert.Equal(0, filter.SmudgeCalledCount); + + var branch = repo.CreateBranch("delete-files"); + Commands.Checkout(repo, branch.FriendlyName); + + DeleteFile(repo, fileName); + + Commands.Checkout(repo, "master"); + + var fileContents = ReadTextFromFile(repo, fileName); + Assert.Equal(1, filter.SmudgeCalledCount); + Assert.Equal(decodedInput, fileContents); + } + + GlobalSettings.DeregisterFilter(filterRegistration); + } + + [Theory] + [InlineData("*.txt", ".bat", 0, 0)] + [InlineData("*.txt", ".txt", 1, 0)] + public void WhenStagedFileDoesNotMatchPathSpecFileIsNotFiltered(string pathSpec, string fileExtension, int cleanCount, int smudgeCount) + { + const string filterName = "rot13"; + const string decodedInput = "This is a substitution cipher"; + string attributeFileEntry = string.Format("{0} filter={1}", pathSpec, filterName); + + var filterForAttributes = new List { new FilterAttributeEntry(filterName) }; + var filter = new SubstitutionCipherFilter("cipher-filter", filterForAttributes); + + var filterRegistration = GlobalSettings.RegisterFilter(filter); + + string repoPath = InitNewRepository(); + string fileName = Guid.NewGuid() + fileExtension; + + using (var repo = new Repository(repoPath)) + { + CreateConfigurationWithDummyUser(repo, Constants.Identity); + CreateAttributesFile(repo, attributeFileEntry); + + CommitOnBranchAndReturnDatabaseBlob(repo, fileName, decodedInput); + + Assert.Equal(cleanCount, filter.CleanCalledCount); + Assert.Equal(smudgeCount, filter.SmudgeCalledCount); + } + + GlobalSettings.DeregisterFilter(filterRegistration); + } + + [Theory] + [InlineData("rot13", "*.txt filter=rot13", 1)] + [InlineData("rot13", "*.txt filter=fake", 0)] + [InlineData("rot13", "*.bat filter=rot13", 0)] + [InlineData("fake", "*.txt filter=fake", 1)] + [InlineData("fake", "*.txt filter=rot13", 0)] + [InlineData("fake", "*.bat filter=fake", 0)] + [InlineData("rot13", "*.txt filter=rot13 -crlf", 1)] + public void CleanIsCalledIfAttributeEntryMatches(string filterAttribute, string attributeEntry, int cleanCount) + { + const string decodedInput = "This is a substitution cipher"; + + var filterForAttributes = new List { new FilterAttributeEntry(filterAttribute) }; + var filter = new SubstitutionCipherFilter("cipher-filter", filterForAttributes); + + var filterRegistration = GlobalSettings.RegisterFilter(filter); + + string repoPath = InitNewRepository(); + string fileName = Guid.NewGuid() + ".txt"; + + using (var repo = new Repository(repoPath)) + { + CreateConfigurationWithDummyUser(repo, Constants.Identity); + CreateAttributesFile(repo, attributeEntry); + + CommitOnBranchAndReturnDatabaseBlob(repo, fileName, decodedInput); + + Assert.Equal(cleanCount, filter.CleanCalledCount); + } + + GlobalSettings.DeregisterFilter(filterRegistration); + } + + [Theory] + + [InlineData("rot13", "*.txt filter=rot13", 1)] + [InlineData("rot13", "*.txt filter=fake", 0)] + [InlineData("rot13", "*.txt filter=rot13 -crlf", 1)] + public void SmudgeIsCalledIfAttributeEntryMatches(string filterAttribute, string attributeEntry, int smudgeCount) + { + const string decodedInput = "This is a substitution cipher"; + + var filterForAttributes = new List { new FilterAttributeEntry(filterAttribute) }; + var filter = new SubstitutionCipherFilter("cipher-filter", filterForAttributes); + + var filterRegistration = GlobalSettings.RegisterFilter(filter); + + string repoPath = InitNewRepository(); + string fileName = Guid.NewGuid() + ".txt"; + + using (var repo = new Repository(repoPath)) + { + CreateConfigurationWithDummyUser(repo, Constants.Identity); + CreateAttributesFile(repo, attributeEntry); + + CommitOnBranchAndReturnDatabaseBlob(repo, fileName, decodedInput); + + var branch = repo.CreateBranch("delete-files"); + Commands.Checkout(repo, branch.FriendlyName); + + DeleteFile(repo, fileName); + + Commands.Checkout(repo, "master"); + + Assert.Equal(smudgeCount, filter.SmudgeCalledCount); + } + + GlobalSettings.DeregisterFilter(filterRegistration); + + } + + private static string ReadTextFromFile(Repository repo, string fileName) + { + return File.ReadAllText(Path.Combine(repo.Info.WorkingDirectory, fileName)); + } + + private static void DeleteFile(Repository repo, string fileName) + { + File.Delete(Path.Combine(repo.Info.WorkingDirectory, fileName)); + Commands.Stage(repo, fileName); + repo.Commit("remove file", Constants.Signature, Constants.Signature); + } + + private static Blob CommitOnBranchAndReturnDatabaseBlob(Repository repo, string fileName, string input) + { + Touch(repo.Info.WorkingDirectory, fileName, input); + Commands.Stage(repo, fileName); + + var commit = repo.Commit("new file", Constants.Signature, Constants.Signature); + + var blob = (Blob)commit.Tree[fileName].Target; + return blob; + } + } +} diff --git a/LibGit2Sharp.Tests/GlobalSettingsFixture.cs b/LibGit2Sharp.Tests/GlobalSettingsFixture.cs index 698595042..6026f267a 100644 --- a/LibGit2Sharp.Tests/GlobalSettingsFixture.cs +++ b/LibGit2Sharp.Tests/GlobalSettingsFixture.cs @@ -1,4 +1,7 @@ -using System.Text.RegularExpressions; +using System; +using System.IO; +using System.Text.RegularExpressions; +using LibGit2Sharp.Core; using LibGit2Sharp.Tests.TestHelpers; using Xunit; @@ -12,57 +15,70 @@ 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[-preDateTime]-LibGit2Sharp_abbrev_hash-libgit2_abbrev_hash (x86|amd64 - features) + // Major.Minor.Patch[-previewTag]+{LibGit2Sharp_abbrev_hash}.libgit2-{libgit2_abbrev_hash} (x86|x64 - features) // Example output: - // "0.17.0[-pre20170914123547]-deadcafe-06d772d (x86 - Threads, Https)" + // "0.25.0-preview.52+871d13a67f.libgit2-15e1193 (x86 - Threads, Https)" string versionInfo = GlobalSettings.Version.ToString(); // The GlobalSettings.Version returned string should contain : - // version: '0.17.0[-pre20170914123547]' LibGit2Sharp version number. - // git2SharpHash:'unknown' ( when compiled from source ) else LibGit2Sharp library hash. - // git2hash: '06d772d' LibGit2 library hash. - // arch: 'x86' or 'amd64' LibGit2 target. - // git2Features: 'Threads, Ssh' LibGit2 features compiled with. - string regex = @"^(?\d{1,}\.\d{1,2}\.\d{1,3}(-(pre|dev)\d{14})?)-(?\w+)-(?\w+) \((?\w+) - (?(?:\w*(?:, )*\w+)*)\)$"; + // version: '0.25.0[-previewTag]' LibGit2Sharp version number. + // git2SharpHash: '871d13a67f' LibGit2Sharp hash. + // arch: 'x86' or 'x64' libgit2 target. + // git2Features: 'Threads, Ssh' libgit2 features compiled with. + string regex = @"^(?\d+\.\d+\.\d+(-[\w\-\.]+)?\+((?[a-f0-9]{10})\.)?libgit2-[a-f0-9]{7}) \((?\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[-preDateTime]-LibGit2Sharp_abbrev_hash-libgit2_abbrev_hash (x86|amd64 - features)"); - - GroupCollection matchGroups = regexResult.Groups; - - Assert.Equal(8, matchGroups.Count); - - // Check that all groups are valid - for (int i = 0; i < matchGroups.Count; i++) - { - if (i == 1 || i == 2) // '-pre' segment is optional - { - continue; - } - - Assert.True(matchGroups[i].Success); - } + "Major.Minor.Patch[-previewTag]+{LibGit2Sharp_abbrev_hash}.libgit2-{libgit2_abbrev_hash} (x86|x64 - features). " + + "But found \"" + versionInfo + "\" instead."); } [Fact] public void TryingToResetNativeLibraryPathAfterLoadedThrows() { // Do something that loads the native library - Assert.NotNull(GlobalSettings.Version.Features); + 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"); + + try + { + Directory.CreateDirectory(Path.Combine(platformDir, architecture)); + File.Copy(Path.Combine(GlobalSettings.NativeLibraryPath, architecture, nativeDllFileName), Path.Combine(platformDir, architecture, nativeDllFileName)); + + var (output, exitCode) = ProcessHelper.RunProcess(testAppExe, arguments: $@"{NativeDllName.Name} ""{platformDir}""", workingDirectory: tempDir); + + Assert.Empty(output); + Assert.Equal(0, exitCode); + } + finally + { + DirectoryHelper.DeleteDirectory(tempDir); + } + } } } diff --git a/LibGit2Sharp.Tests/IgnoreFixture.cs b/LibGit2Sharp.Tests/IgnoreFixture.cs index 790336512..fc9d65bc2 100644 --- a/LibGit2Sharp.Tests/IgnoreFixture.cs +++ b/LibGit2Sharp.Tests/IgnoreFixture.cs @@ -16,15 +16,15 @@ public void TemporaryRulesShouldApplyUntilCleared() { Touch(repo.Info.WorkingDirectory, "Foo.cs", "Bar"); - Assert.True(repo.RetrieveStatus().Untracked.Select(s => s.FilePath).Contains("Foo.cs")); + Assert.Contains("Foo.cs", repo.RetrieveStatus().Untracked.Select(s => s.FilePath)); repo.Ignore.AddTemporaryRules(new[] { "*.cs" }); - Assert.False(repo.RetrieveStatus().Untracked.Select(s => s.FilePath).Contains("Foo.cs")); + Assert.DoesNotContain("Foo.cs", repo.RetrieveStatus().Untracked.Select(s => s.FilePath)); repo.Ignore.ResetAllTemporaryRules(); - Assert.True(repo.RetrieveStatus().Untracked.Select(s => s.FilePath).Contains("Foo.cs")); + Assert.Contains("Foo.cs", repo.RetrieveStatus().Untracked.Select(s => s.FilePath)); } } @@ -79,8 +79,40 @@ public void CanCheckIfAPathIsIgnoredUsingThePreferedPlatformDirectorySeparatorCh 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 0b0f42553..4c0d150f0 100644 --- a/LibGit2Sharp.Tests/IndexFixture.cs +++ b/LibGit2Sharp.Tests/IndexFixture.cs @@ -10,7 +10,7 @@ namespace LibGit2Sharp.Tests { public class IndexFixture : BaseFixture { - private static readonly string subBranchFile = Path.Combine("1", "branch_file.txt"); + private static readonly string subBranchFile = string.Join("/", "1", "branch_file.txt"); private readonly string[] expectedEntries = new[] { "1.txt", @@ -103,7 +103,7 @@ public void CanRenameAFile() Touch(repo.Info.WorkingDirectory, oldName, "hello test file\n"); Assert.Equal(FileStatus.NewInWorkdir, repo.RetrieveStatus(oldName)); - repo.Stage(oldName); + Commands.Stage(repo, oldName); Assert.Equal(FileStatus.NewInIndex, repo.RetrieveStatus(oldName)); // Generated through @@ -120,7 +120,7 @@ public void CanRenameAFile() const string newName = "being.frakking.polite.txt"; - repo.Move(oldName, newName); + Commands.Move(repo, oldName, newName); Assert.Equal(FileStatus.DeletedFromIndex, repo.RetrieveStatus(oldName)); Assert.Equal(FileStatus.NewInIndex, repo.RetrieveStatus(newName)); @@ -150,7 +150,7 @@ public void CanMoveAnExistingFileOverANonExistingFile(string sourcePath, FileSta Assert.Equal(sourceStatus, repo.RetrieveStatus(sourcePath)); Assert.Equal(destStatus, repo.RetrieveStatus(destPath)); - repo.Move(sourcePath, destPath); + Commands.Move(repo, sourcePath, destPath); Assert.Equal(sourcePostStatus, repo.RetrieveStatus(sourcePath)); Assert.Equal(destPostStatus, repo.RetrieveStatus(destPath)); @@ -158,28 +158,34 @@ public void CanMoveAnExistingFileOverANonExistingFile(string sourcePath, FileSta } [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.NewInIndex, new[] { "README", "new_tracked_file.txt", "modified_staged_file.txt", "modified_unstaged_file.txt", "new_untracked_file.txt" })] - [InlineData("modified_staged_file.txt", FileStatus.ModifiedInIndex, new[] { "README", "new_tracked_file.txt", "modified_staged_file.txt", "modified_unstaged_file.txt", "new_untracked_file.txt" })] - [InlineData("modified_unstaged_file.txt", FileStatus.ModifiedInWorkdir, new[] { "README", "new_tracked_file.txt", "modified_staged_file.txt", "modified_unstaged_file.txt", "new_untracked_file.txt" })] - public void MovingOverAnExistingFileThrows(string sourcePath, FileStatus sourceStatus, IEnumerable destPaths) + [InlineData("README", FileStatus.Unaltered)] + [InlineData("new_tracked_file.txt", FileStatus.NewInIndex)] + [InlineData("modified_staged_file.txt", FileStatus.ModifiedInIndex)] + [InlineData("modified_unstaged_file.txt", FileStatus.ModifiedInWorkdir)] + public void MovingOverAnExistingFileThrows(string sourcePath, FileStatus sourceStatus) { + var destPaths = new[] { "README", "new_tracked_file.txt", "modified_staged_file.txt", "modified_unstaged_file.txt", "new_untracked_file.txt" }; + InvalidMoveUseCases(sourcePath, sourceStatus, destPaths); } [Theory] - [InlineData("new_untracked_file.txt", FileStatus.NewInWorkdir, new[] { "README", "new_tracked_file.txt", "modified_staged_file.txt", "modified_unstaged_file.txt", "new_untracked_file.txt", "deleted_unstaged_file.txt", "deleted_staged_file.txt", "i_dont_exist.txt" })] - public void MovingAFileWichIsNotUnderSourceControlThrows(string sourcePath, FileStatus sourceStatus, IEnumerable destPaths) + [InlineData("new_untracked_file.txt", FileStatus.NewInWorkdir)] + public void MovingAFileWichIsNotUnderSourceControlThrows(string sourcePath, FileStatus sourceStatus) { + var destPaths = new[] { "README", "new_tracked_file.txt", "modified_staged_file.txt", "modified_unstaged_file.txt", "new_untracked_file.txt", "deleted_unstaged_file.txt", "deleted_staged_file.txt", "i_dont_exist.txt" }; + InvalidMoveUseCases(sourcePath, sourceStatus, destPaths); } [Theory] - [InlineData("deleted_unstaged_file.txt", FileStatus.DeletedFromWorkdir, 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.DeletedFromIndex, new[] { "README", "new_tracked_file.txt", "modified_staged_file.txt", "modified_unstaged_file.txt", "new_untracked_file.txt", "deleted_unstaged_file.txt", "deleted_staged_file.txt", "i_dont_exist.txt" })] - [InlineData("i_dont_exist.txt", FileStatus.Nonexistent, new[] { "README", "new_tracked_file.txt", "modified_staged_file.txt", "modified_unstaged_file.txt", "new_untracked_file.txt", "deleted_unstaged_file.txt", "deleted_staged_file.txt", "i_dont_exist.txt" })] - public void MovingAFileNotInTheWorkingDirectoryThrows(string sourcePath, FileStatus sourceStatus, IEnumerable destPaths) + [InlineData("deleted_unstaged_file.txt", FileStatus.DeletedFromWorkdir)] + [InlineData("deleted_staged_file.txt", FileStatus.DeletedFromIndex)] + [InlineData("i_dont_exist.txt", FileStatus.Nonexistent)] + public void MovingAFileNotInTheWorkingDirectoryThrows(string sourcePath, FileStatus sourceStatus) { + var destPaths = new[] { "README", "new_tracked_file.txt", "modified_staged_file.txt", "modified_unstaged_file.txt", "new_untracked_file.txt", "deleted_unstaged_file.txt", "deleted_staged_file.txt", "i_dont_exist.txt" }; + InvalidMoveUseCases(sourcePath, sourceStatus, destPaths); } @@ -193,7 +199,7 @@ private void InvalidMoveUseCases(string sourcePath, FileStatus sourceStatus, IEn foreach (var destPath in destPaths) { string path = destPath; - Assert.Throws(() => repo.Move(sourcePath, path)); + Assert.Throws(() => Commands.Move(repo, sourcePath, path)); } } } @@ -202,7 +208,7 @@ private void InvalidMoveUseCases(string sourcePath, FileStatus sourceStatus, IEn public void PathsOfIndexEntriesAreExpressedInNativeFormat() { // Build relative path - string relFilePath = Path.Combine("directory", "Testfile.txt"); + string relFilePath = Path.Combine("directory", "Testfile.txt").Replace('\\', '/'); string repoPath = InitNewRepository(); @@ -211,7 +217,7 @@ public void PathsOfIndexEntriesAreExpressedInNativeFormat() Touch(repo.Info.WorkingDirectory, relFilePath, "Anybody out there?"); // Stage the file - repo.Stage(relFilePath); + Commands.Stage(repo, relFilePath); // Get the index Index index = repo.Index; @@ -248,7 +254,7 @@ public void StagingAFileWhenTheIndexIsLockedThrowsALockedFileException() Touch(repo.Info.Path, "index.lock"); Touch(repo.Info.WorkingDirectory, "newfile", "my my, this is gonna crash\n"); - Assert.Throws(() => repo.Stage("newfile")); + Assert.Throws(() => Commands.Stage(repo, "newfile")); } } @@ -273,7 +279,7 @@ public void CanCopeWithExternalChangesToTheIndex() Assert.True(readStatus.IsDirty); Assert.Equal(0, repoRead.Index.Count); - repoWrite.Stage("*"); + Commands.Stage(repoWrite, "*"); repoWrite.Commit("message", Constants.Signature, Constants.Signature); writeStatus = repoWrite.RetrieveStatus(); @@ -304,9 +310,11 @@ public void CanResetFullyMergedIndexFromTree() var headIndexTree = repo.Lookup(headIndexTreeSha); Assert.NotNull(headIndexTree); - repo.Index.Replace(headIndexTree); + var index = repo.Index; + index.Replace(headIndexTree); + index.Write(); - Assert.True(repo.Index.IsFullyMerged); + Assert.True(index.IsFullyMerged); Assert.Equal(FileStatus.NewInWorkdir, repo.RetrieveStatus(testFile)); } @@ -335,9 +343,11 @@ public void CanResetIndexWithUnmergedEntriesFromTree() var headIndexTree = repo.Lookup(headIndexTreeSha); Assert.NotNull(headIndexTree); - repo.Index.Replace(headIndexTree); + var index = repo.Index; + index.Replace(headIndexTree); + index.Write(); - Assert.True(repo.Index.IsFullyMerged); + Assert.True(index.IsFullyMerged); Assert.Equal(FileStatus.ModifiedInWorkdir, repo.RetrieveStatus(testFile)); } @@ -359,10 +369,11 @@ public void CanClearTheIndex() using (var repo = new Repository(path)) { Assert.Equal(FileStatus.Unaltered, repo.RetrieveStatus(testFile)); - Assert.NotEqual(0, repo.Index.Count); - - repo.Index.Clear(); - Assert.Equal(0, repo.Index.Count); + 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)); } @@ -451,18 +462,18 @@ public void CanMimicGitAddAll() using (var repo = new Repository(path)) { var before = repo.RetrieveStatus(); - Assert.True(before.Any(se => se.State == FileStatus.NewInWorkdir)); - Assert.True(before.Any(se => se.State == FileStatus.ModifiedInWorkdir)); - Assert.True(before.Any(se => se.State == FileStatus.DeletedFromWorkdir)); + 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); - repo.Stage("*"); + Commands.Stage(repo, "*"); var after = repo.RetrieveStatus(); - Assert.False(after.Any(se => se.State == FileStatus.NewInWorkdir)); - Assert.False(after.Any(se => se.State == FileStatus.ModifiedInWorkdir)); - Assert.False(after.Any(se => se.State == FileStatus.DeletedFromWorkdir)); + Assert.DoesNotContain(after, se => se.State == FileStatus.NewInWorkdir); + Assert.DoesNotContain(after, se => se.State == FileStatus.ModifiedInWorkdir); + Assert.DoesNotContain(after, se => se.State == FileStatus.DeletedFromWorkdir); } } diff --git a/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj b/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj index 6a700f01c..55bee3a32 100644 --- a/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj +++ b/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj @@ -1,161 +1,42 @@ - - - + + - Debug - AnyCPU - 8.0.30703 - 2.0 - {286E63EB-04DD-4ADE-88D6-041B57800761} - Library - Properties - LibGit2Sharp.Tests - LibGit2Sharp.Tests - v4.0 - 512 - - - - true - full - false - bin\Debug\ - TRACE;DEBUG;NET40 - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - + netcoreapp3.1 + linux-x64 + - - False - ..\packages\Moq.4.2.1409.1722\lib\net40\Moq.dll - - - - - - False - ..\packages\xunit.1.9.2\lib\net20\xunit.dll - - - ..\packages\xunit.extensions.1.9.2\lib\net20\xunit.extensions.dll - - - - - TestHelpers\Epoch.cs - - - TestHelpers\Platform.cs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - - {EE6ED99F-CB12-4683-B055-D28FC7357A34} - LibGit2Sharp - + + + + + + + + + - - + + + + - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - + + + \ No newline at end of file diff --git a/LibGit2Sharp.Tests/LogFixture.cs b/LibGit2Sharp.Tests/LogFixture.cs index b74fe97b0..b8d43fe36 100644 --- a/LibGit2Sharp.Tests/LogFixture.cs +++ b/LibGit2Sharp.Tests/LogFixture.cs @@ -25,13 +25,13 @@ public void CanEnableAndDisableLogging() GlobalSettings.LogConfiguration = new LogConfiguration(LogLevel.Warning, (l, m) => { level = l; message = m; }); Assert.Equal(LogLevel.None, level); - Assert.Equal(null, message); + Assert.Null(message); // Similarly, turning logging off should produce no messages. GlobalSettings.LogConfiguration = LogConfiguration.None; Assert.Equal(LogLevel.None, level); - Assert.Equal(null, message); + Assert.Null(message); } } } diff --git a/LibGit2Sharp.Tests/MergeFixture.cs b/LibGit2Sharp.Tests/MergeFixture.cs index 2153e85e6..7ce3ff496 100644 --- a/LibGit2Sharp.Tests/MergeFixture.cs +++ b/LibGit2Sharp.Tests/MergeFixture.cs @@ -16,7 +16,7 @@ public void ANewRepoIsFullyMerged() using (var repo = new Repository(repoPath)) { - Assert.Equal(true, repo.Index.IsFullyMerged); + Assert.True(repo.Index.IsFullyMerged); } } @@ -26,7 +26,7 @@ public void AFullyMergedRepoOnlyContainsStagedIndexEntries() string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { - Assert.Equal(true, repo.Index.IsFullyMerged); + Assert.True(repo.Index.IsFullyMerged); foreach (var entry in repo.Index) { @@ -41,7 +41,7 @@ public void SoftResetARepoWithUnmergedEntriesThrows() 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(); @@ -56,7 +56,7 @@ public void CommitAgainARepoWithUnmergedEntriesThrows() var path = SandboxMergedTestRepo(); using (var repo = new Repository(path)) { - Assert.Equal(false, repo.Index.IsFullyMerged); + Assert.False(repo.Index.IsFullyMerged); var author = Constants.Signature; Assert.Throws( @@ -93,7 +93,7 @@ public void CanMergeRepoNonFastForward(bool shouldMergeOccurInDetachedHeadState) using (var repo = new Repository(path)) { var firstBranch = repo.CreateBranch("FirstBranch"); - repo.Checkout(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). @@ -106,11 +106,11 @@ public void CanMergeRepoNonFastForward(bool shouldMergeOccurInDetachedHeadState) if (shouldMergeOccurInDetachedHeadState) { // Detaches HEAD - repo.Checkout(secondBranch.Tip); + Commands.Checkout(repo, secondBranch.Tip); } else { - repo.Checkout(secondBranch); + 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). @@ -142,14 +142,14 @@ public void IsUpToDateMerge() using (var repo = new Repository(path)) { var firstBranch = repo.CreateBranch("FirstBranch"); - repo.Checkout(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"); - repo.Checkout(secondBranch); + Commands.Checkout(repo, secondBranch); MergeResult mergeResult = repo.Merge(repo.Branches["FirstBranch"].Tip, Constants.Signature); @@ -175,7 +175,7 @@ public void CanFastForwardRepos(bool shouldMergeOccurInDetachedHeadState) repo.RemoveUntrackedFiles(); var firstBranch = repo.CreateBranch("FirstBranch"); - repo.Checkout(firstBranch); + Commands.Checkout(repo, firstBranch); // Commit with ONE new file to both first & second branch (SecondBranch is created on this commit). AddFileCommitToRepo(repo, sharedBranchFileName); @@ -188,11 +188,11 @@ public void CanFastForwardRepos(bool shouldMergeOccurInDetachedHeadState) if (shouldMergeOccurInDetachedHeadState) { // Detaches HEAD - repo.Checkout(secondBranch.Tip); + Commands.Checkout(repo, secondBranch.Tip); } else { - repo.Checkout(secondBranch); + Commands.Checkout(repo, secondBranch); } Assert.Equal(shouldMergeOccurInDetachedHeadState, repo.Info.IsHeadDetached); @@ -204,7 +204,7 @@ public void CanFastForwardRepos(bool shouldMergeOccurInDetachedHeadState) Assert.Equal(repo.Branches["FirstBranch"].Tip, repo.Head.Tip); Assert.Equal(repo.Head.Tip, mergeResult.Commit); - Assert.Equal(0, repo.RetrieveStatus().Count()); + Assert.Empty(repo.RetrieveStatus()); Assert.Equal(shouldMergeOccurInDetachedHeadState, repo.Info.IsHeadDetached); if (!shouldMergeOccurInDetachedHeadState) @@ -226,7 +226,7 @@ public void ConflictingMergeRepos() using (var repo = new Repository(path)) { var firstBranch = repo.CreateBranch("FirstBranch"); - repo.Checkout(firstBranch); + Commands.Checkout(repo, firstBranch); // Commit with ONE new file to both first & second branch (SecondBranch is created on this commit). AddFileCommitToRepo(repo, sharedBranchFileName); @@ -236,7 +236,7 @@ public void ConflictingMergeRepos() AddFileCommitToRepo(repo, firstBranchFileName); AddFileCommitToRepo(repo, sharedBranchFileName, "The first branches comment"); // Change file in first branch - repo.Checkout(secondBranch); + 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 @@ -246,7 +246,7 @@ public void ConflictingMergeRepos() Assert.Equal(MergeStatus.Conflicts, mergeResult.Status); Assert.Null(mergeResult.Commit); - Assert.Equal(1, repo.Index.Conflicts.Count()); + Assert.Single(repo.Index.Conflicts); var conflict = repo.Index.Conflicts.First(); var changes = repo.Diff.Compare(repo.Lookup(conflict.Theirs.Id), repo.Lookup(conflict.Ours.Id)); @@ -266,7 +266,7 @@ public void ConflictingMergeReposBinary() using (var repo = new Repository(path)) { var firstBranch = repo.CreateBranch("FirstBranch"); - repo.Checkout(firstBranch); + Commands.Checkout(repo, firstBranch); // Commit with ONE new file to both first & second branch (SecondBranch is created on this commit). AddFileCommitToRepo(repo, sharedBranchFileName); @@ -276,7 +276,7 @@ public void ConflictingMergeReposBinary() AddFileCommitToRepo(repo, firstBranchFileName); AddFileCommitToRepo(repo, sharedBranchFileName, "\0The first branches comment\0"); // Change file in first branch - repo.Checkout(secondBranch); + 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 @@ -285,7 +285,7 @@ public void ConflictingMergeReposBinary() Assert.Equal(MergeStatus.Conflicts, mergeResult.Status); - Assert.Equal(1, repo.Index.Conflicts.Count()); + Assert.Single(repo.Index.Conflicts); Conflict conflict = repo.Index.Conflicts.First(); @@ -295,6 +295,24 @@ public void ConflictingMergeReposBinary() } } + [Fact] + public void CanFailOnFirstMergeConflict() + { + string path = SandboxMergeTestRepo(); + using (var repo = new Repository(path)) + { + var mergeResult = repo.Merge("conflicts", Constants.Signature, new MergeOptions() { FailOnConflict = true, }); + Assert.Equal(MergeStatus.Conflicts, mergeResult.Status); + + var master = repo.Branches["master"]; + var branch = repo.Branches["conflicts"]; + var mergeTreeResult = repo.ObjectDatabase.MergeCommits(master.Tip, branch.Tip, new MergeTreeOptions() { FailOnConflict = true }); + Assert.Equal(MergeTreeStatus.Conflicts, mergeTreeResult.Status); + Assert.Empty(mergeTreeResult.Conflicts); + } + + } + [Theory] [InlineData(true, FastForwardStrategy.Default, fastForwardBranchInitialId, MergeStatus.FastForward)] [InlineData(true, FastForwardStrategy.FastForwardOnly, fastForwardBranchInitialId, MergeStatus.FastForward)] @@ -307,7 +325,7 @@ public void CanFastForwardCommit(bool fromDetachedHead, FastForwardStrategy fast { if(fromDetachedHead) { - repo.Checkout(repo.Head.Tip.Id.Sha); + Commands.Checkout(repo, repo.Head.Tip.Id.Sha); } Commit commitToMerge = repo.Branches["fast_forward"].Tip; @@ -333,7 +351,7 @@ public void CanNonFastForwardMergeCommit(bool fromDetachedHead, FastForwardStrat { if (fromDetachedHead) { - repo.Checkout(repo.Head.Tip.Id.Sha); + Commands.Checkout(repo, repo.Head.Tip.Id.Sha); } Commit commitToMerge = repo.Branches["normal_merge"].Tip; @@ -361,7 +379,7 @@ public void MergeReportsCheckoutProgress() OnCheckoutProgress = (path, completed, total) => wasCalled = true, }; - MergeResult result = repo.Merge(commitToMerge, Constants.Signature, options); + repo.Merge(commitToMerge, Constants.Signature, options); Assert.True(wasCalled); } @@ -384,7 +402,7 @@ public void MergeReportsCheckoutNotifications() CheckoutNotifyFlags = CheckoutNotifyFlags.Updated, }; - MergeResult result = repo.Merge(commitToMerge, Constants.Signature, options); + repo.Merge(commitToMerge, Constants.Signature, options); Assert.True(wasCalled); Assert.Equal(CheckoutNotifyFlags.Updated, actualNotifyFlags); @@ -406,7 +424,7 @@ public void FastForwardMergeReportsCheckoutProgress() OnCheckoutProgress = (path, completed, total) => wasCalled = true, }; - MergeResult result = repo.Merge(commitToMerge, Constants.Signature, options); + repo.Merge(commitToMerge, Constants.Signature, options); Assert.True(wasCalled); } @@ -429,7 +447,7 @@ public void FastForwardMergeReportsCheckoutNotifications() CheckoutNotifyFlags = CheckoutNotifyFlags.Updated, }; - MergeResult result = repo.Merge(commitToMerge, Constants.Signature, options); + repo.Merge(commitToMerge, Constants.Signature, options); Assert.True(wasCalled); Assert.Equal(CheckoutNotifyFlags.Updated, actualNotifyFlags); @@ -451,7 +469,7 @@ public void MergeCanDetectRenames() string repoPath = SandboxMergeTestRepo(); using (var repo = new Repository(repoPath)) { - Branch currentBranch = repo.Checkout("rename_source"); + Branch currentBranch = Commands.Checkout(repo, "rename_source"); Assert.NotNull(currentBranch); Branch branchToMerge = repo.Branches["rename"]; @@ -497,12 +515,12 @@ public void CanMergeAndNotCommit() MergeResult result = repo.Merge(commitToMerge, Constants.Signature, new MergeOptions() { CommitOnSuccess = false}); Assert.Equal(MergeStatus.NonFastForward, result.Status); - Assert.Equal(null, result.Commit); + Assert.Null(result.Commit); RepositoryStatus repoStatus = repo.RetrieveStatus(); // Verify that there is a staged entry. - Assert.Equal(1, repoStatus.Count()); + Assert.Single(repoStatus); Assert.Equal(FileStatus.ModifiedInIndex, repo.RetrieveStatus("b.txt")); } } @@ -552,7 +570,7 @@ public void VerifyUpToDateMerge() MergeResult result = repo.Merge(commitToMerge, Constants.Signature, new MergeOptions() { FastForwardStrategy = FastForwardStrategy.NoFastForward }); Assert.Equal(MergeStatus.UpToDate, result.Status); - Assert.Equal(null, result.Commit); + Assert.Null(result.Commit); Assert.False(repo.RetrieveStatus().Any()); } } @@ -593,7 +611,7 @@ public void MergeWithWorkDirConflictsThrows(bool shouldStage, FastForwardStrateg if (shouldStage) { - repo.Stage("b.txt"); + Commands.Stage(repo, "b.txt"); } Assert.Throws(() => repo.Merge(committishToMerge, Constants.Signature, new MergeOptions() { FastForwardStrategy = strategy })); @@ -660,12 +678,11 @@ public void MergeCanSpecifyMergeFileFavorOption(MergeFileFavor fileFavorFlag) const string conflictBranchName = "conflicts"; string path = SandboxMergeTestRepo(); - using (var repo = InitIsolatedRepository(path)) + using (var repo = new Repository(path)) { Branch branch = repo.Branches[conflictBranchName]; Assert.NotNull(branch); - var status = repo.RetrieveStatus(); MergeOptions mergeOptions = new MergeOptions() { MergeFileFavor = fileFavorFlag, @@ -733,8 +750,8 @@ public void CanMergeIntoOrphanedBranch() // Remove entries from the working directory foreach(var entry in repo.RetrieveStatus()) { - repo.Unstage(entry.FilePath); - repo.Remove(entry.FilePath, true); + Commands.Unstage(repo, entry.FilePath); + Commands.Remove(repo, entry.FilePath, true); } // Assert that we have an empty working directory. @@ -759,7 +776,7 @@ public void CanMergeTreeIntoSameTree() var result = repo.ObjectDatabase.MergeCommits(master, master, null); Assert.Equal(MergeTreeStatus.Succeeded, result.Status); - Assert.Equal(0, result.Conflicts.Count()); + Assert.Empty(result.Conflicts); } } @@ -773,7 +790,7 @@ public void CanMergeTreeIntoTreeFromUnbornBranch() Touch(repo.Info.WorkingDirectory, "README", "Yeah!\n"); repo.Index.Clear(); - repo.Stage("README"); + Commands.Stage(repo, "README"); repo.Commit("A new world, free of the burden of the history", Constants.Signature, Constants.Signature); @@ -783,7 +800,7 @@ public void CanMergeTreeIntoTreeFromUnbornBranch() var result = repo.ObjectDatabase.MergeCommits(master, branch, null); Assert.Equal(MergeTreeStatus.Succeeded, result.Status); Assert.NotNull(result.Tree); - Assert.Equal(0, result.Conflicts.Count()); + Assert.Empty(result.Conflicts); } } @@ -805,7 +822,7 @@ public void CanMergeCommitsAndDetectConflicts() var result = repo.ObjectDatabase.MergeCommits(master, branch, null); Assert.Equal(MergeTreeStatus.Conflicts, result.Status); Assert.Null(result.Tree); - Assert.NotEqual(0, result.Conflicts.Count()); + Assert.NotEmpty(result.Conflicts); } } @@ -821,7 +838,7 @@ public void CanMergeFastForwardTreeWithoutConflicts() var result = repo.ObjectDatabase.MergeCommits(master, branch, null); Assert.Equal(MergeTreeStatus.Succeeded, result.Status); Assert.NotNull(result.Tree); - Assert.Equal(0, result.Conflicts.Count()); + Assert.Empty(result.Conflicts); } } @@ -839,7 +856,7 @@ public void CanIdentifyConflictsInMergeCommits() Assert.Equal(MergeTreeStatus.Conflicts, result.Status); Assert.Null(result.Tree); - Assert.Equal(1, result.Conflicts.Count()); + Assert.Single(result.Conflicts); var conflict = result.Conflicts.First(); Assert.Equal(new ObjectId("8e9daea300fbfef6c0da9744c6214f546d55b279"), conflict.Ancestor.Id); @@ -848,11 +865,94 @@ public void CanIdentifyConflictsInMergeCommits() } } + [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); - repository.Stage(filename); + Commands.Stage(repository, filename); return repository.Commit("New commit", Constants.Signature, Constants.Signature); } diff --git a/LibGit2Sharp.Tests/MetaFixture.cs b/LibGit2Sharp.Tests/MetaFixture.cs index 685ff5f45..b70d9022c 100644 --- a/LibGit2Sharp.Tests/MetaFixture.cs +++ b/LibGit2Sharp.Tests/MetaFixture.cs @@ -1,12 +1,15 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Globalization; using System.Linq; using System.Reflection; +using System.Runtime.CompilerServices; using System.Text; using LibGit2Sharp.Tests.TestHelpers; using Xunit; using Xunit.Extensions; +using Moq; namespace LibGit2Sharp.Tests { @@ -14,9 +17,45 @@ public class MetaFixture { private static readonly HashSet explicitOnlyInterfaces = new HashSet { - typeof(IBelongToARepository), + typeof(IBelongToARepository), typeof(IDiffResult), }; + [Fact] + public void LibGit2SharpPublicInterfacesCoverAllPublicMembers() + { + var methodsMissingFromInterfaces = + from t in typeof(IRepository).GetTypeInfo().Assembly.GetExportedTypes() + where !t.GetTypeInfo().IsInterface + where t.GetTypeInfo().GetInterfaces().Any(i => i.GetTypeInfo().IsPublic && i.Namespace == typeof(IRepository).Namespace && !explicitOnlyInterfaces.Contains(i)) + let interfaceTargetMethods = from i in t.GetTypeInfo().GetInterfaces() + from im in t.GetInterfaceMap(i).TargetMethods + select im + from tm in t.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance) + where !interfaceTargetMethods.Contains(tm) + select t.Name + " has extra method " + tm.Name; + + Assert.Equal("", string.Join(Environment.NewLine, + methodsMissingFromInterfaces.ToArray())); + } + + [Fact] + public void LibGit2SharpExplicitOnlyInterfacesAreIndeedExplicitOnly() + { + var methodsMissingFromInterfaces = + from t in typeof(IRepository).GetTypeInfo().Assembly.GetExportedTypes() + where t.GetInterfaces().Any(explicitOnlyInterfaces.Contains) + let interfaceTargetMethods = from i in t.GetInterfaces() + where explicitOnlyInterfaces.Contains(i) + from im in t.GetTypeInfo().GetInterfaceMap(i).TargetMethods + select im + from tm in t.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance) + where interfaceTargetMethods.Contains(tm) + select t.Name + " has public method " + tm.Name + " which should be explicitly implemented."; + + Assert.Equal("", string.Join(Environment.NewLine, + methodsMissingFromInterfaces.ToArray())); + } + [Fact] public void PublicTestMethodsAreFactsOrTheories() { @@ -25,8 +64,8 @@ public void PublicTestMethodsAreFactsOrTheories() "LibGit2Sharp.Tests.FilterBranchFixture.Dispose", }; - var fixtures = from t in Assembly.GetAssembly(typeof(MetaFixture)).GetExportedTypes() - where t.IsPublic && !t.IsNested + var fixtures = from t in typeof(MetaFixture).GetTypeInfo().Assembly.GetExportedTypes() + where t.GetTypeInfo().IsPublic && !t.IsNested where t.Namespace != typeof(BaseFixture).Namespace // Exclude helpers let methods = t.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public) from m in methods @@ -46,12 +85,12 @@ public void TypesInLibGit2DecoratedWithDebuggerDisplayMustFollowTheStandardImplP { var typesWithDebuggerDisplayAndInvalidImplPattern = new List(); - IEnumerable libGit2SharpTypes = Assembly.GetAssembly(typeof(IRepository)).GetExportedTypes() - .Where(t => t.GetCustomAttributes(typeof(DebuggerDisplayAttribute), false).Any()); + IEnumerable libGit2SharpTypes = typeof(IRepository).GetTypeInfo().Assembly.GetExportedTypes() + .Where(t => t.GetTypeInfo().GetCustomAttributes(typeof(DebuggerDisplayAttribute), false).Any()); 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}") { @@ -86,12 +125,12 @@ public void TypesInLibGit2SharpMustBeExtensibleInATestingContext() { var nonTestableTypes = new Dictionary>(); - IEnumerable libGit2SharpTypes = Assembly.GetAssembly(typeof(IRepository)).GetExportedTypes() + 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(); @@ -105,6 +144,28 @@ public void TypesInLibGit2SharpMustBeExtensibleInATestingContext() { nonTestableTypes.Add(type, new List()); } + + if (type.GetTypeInfo().IsAbstract) + { + continue; + } + + try + { + if (type.GetTypeInfo().ContainsGenericParameters) + { + var constructType = type.MakeGenericType(Enumerable.Repeat(typeof(object), type.GetGenericArguments().Length).ToArray()); + Activator.CreateInstance(constructType, true); + } + else + { + Activator.CreateInstance(type, true); + } + } + catch + { + nonTestableTypes.Add(type, new List()); + } } if (nonTestableTypes.Any()) @@ -115,62 +176,27 @@ public void TypesInLibGit2SharpMustBeExtensibleInATestingContext() private static bool MustBeMockable(Type type) { - if (type.IsSealed) + if (type.GetTypeInfo().IsSealed) { return false; } - if (type.IsAbstract) + if (type.GetTypeInfo().IsAbstract) { - return !type.Assembly.GetExportedTypes() - .Where(t => t.IsSubclassOf(type)) - .All(t => t.IsAbstract || t.IsSealed); + return !type.GetTypeInfo().Assembly.GetExportedTypes() + .Where(t => t.GetTypeInfo().IsSubclassOf(type)) + .All(t => t.GetTypeInfo().IsAbstract || t.GetTypeInfo().IsSealed); } return true; } - [Fact] - public void LibGit2SharpPublicInterfacesCoverAllPublicMembers() - { - var methodsMissingFromInterfaces = - from t in Assembly.GetAssembly(typeof(IRepository)).GetExportedTypes() - where !t.IsInterface - where t.GetInterfaces().Any(i => i.IsPublic && i.Namespace == typeof(IRepository).Namespace && !explicitOnlyInterfaces.Contains(i)) - let interfaceTargetMethods = from i in t.GetInterfaces() - from im in t.GetInterfaceMap(i).TargetMethods - select im - from tm in t.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance) - where !interfaceTargetMethods.Contains(tm) - select t.Name + " has extra method " + tm.Name; - - Assert.Equal("", string.Join(Environment.NewLine, - methodsMissingFromInterfaces.ToArray())); - } - - [Fact] - public void LibGit2SharpExplicitOnlyInterfacesAreIndeedExplicitOnly() - { - var methodsMissingFromInterfaces = - from t in Assembly.GetAssembly(typeof(IRepository)).GetExportedTypes() - where t.GetInterfaces().Any(explicitOnlyInterfaces.Contains) - let interfaceTargetMethods = from i in t.GetInterfaces() - where explicitOnlyInterfaces.Contains(i) - from im in t.GetInterfaceMap(i).TargetMethods - select im - from tm in t.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance) - where interfaceTargetMethods.Contains(tm) - select t.Name + " has public method " + tm.Name + " which should be explicitly implemented."; - - Assert.Equal("", string.Join(Environment.NewLine, - methodsMissingFromInterfaces.ToArray())); - } [Fact] public void EnumsWithFlagsHaveMutuallyExclusiveValues() { - var flagsEnums = Assembly.GetAssembly(typeof(IRepository)).GetExportedTypes() - .Where(t => t.IsEnum && t.GetCustomAttributes(typeof(FlagsAttribute), false).Any()); + var flagsEnums = typeof(IRepository).GetTypeInfo().Assembly.GetExportedTypes() + .Where(t => t.GetTypeInfo().IsEnum && t.GetTypeInfo().GetCustomAttributes(typeof(FlagsAttribute), false).Any()); var overlaps = from t in flagsEnums from int x in Enum.GetValues(t) @@ -234,19 +260,19 @@ private static bool HasEmptyPublicOrProtectedConstructor(Type type) private static bool IsStatic(Type type) { - return type.IsAbstract && type.IsSealed; + return type.GetTypeInfo().IsAbstract && type.GetTypeInfo().IsSealed; } // Related to https://github.com/libgit2/libgit2sharp/issues/644 and https://github.com/libgit2/libgit2sharp/issues/645 [Fact] public void GetEnumeratorMethodsInLibGit2SharpMustBeVirtualForTestability() { - var nonVirtualGetEnumeratorMethods = Assembly.GetAssembly(typeof(IRepository)) + var nonVirtualGetEnumeratorMethods = typeof(IRepository).GetTypeInfo().Assembly .GetExportedTypes() .Where(t => - t.Namespace == typeof (IRepository).Namespace && - !t.IsSealed && - !t.IsAbstract && + t.Namespace == typeof(IRepository).Namespace && + !t.GetTypeInfo().IsSealed && + !t.GetTypeInfo().IsAbstract && t.GetInterfaces().Any(i => i.IsAssignableFrom(typeof(IEnumerable<>)))) .Select(t => t.GetMethod("GetEnumerator")) .Where(m => @@ -273,17 +299,14 @@ public void NoPublicTypesUnderLibGit2SharpCoreNamespace() { const string coreNamespace = "LibGit2Sharp.Core"; - var types = Assembly.GetAssembly(typeof(IRepository)) + 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("+")) - -#if LEAKS_IDENTIFYING - .Where(t => t != typeof(LibGit2Sharp.Core.LeaksContainer)) -#endif + .Where(t => t.FullName != "LibGit2Sharp.Core.LeaksContainer") .ToList(); if (types.Any()) @@ -304,7 +327,7 @@ public void NoPublicTypesUnderLibGit2SharpCoreNamespace() public void NoOptionalParametersinMethods() { IEnumerable mis = - from t in Assembly.GetAssembly(typeof(IRepository)) + from t in typeof(IRepository).GetTypeInfo().Assembly .GetExportedTypes() from m in t.GetMethods() where !m.IsObsolete() @@ -327,7 +350,7 @@ where p.IsOptional public void NoOptionalParametersinConstructors() { IEnumerable mis = - from t in Assembly.GetAssembly(typeof(IRepository)) + from t in typeof(IRepository).GetTypeInfo().Assembly .GetExportedTypes() from c in t.GetConstructors() from p in c.GetParameters() @@ -344,6 +367,52 @@ where p.IsOptional 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 diff --git a/LibGit2Sharp.Tests/NetworkFixture.cs b/LibGit2Sharp.Tests/NetworkFixture.cs index f146a0678..3ac73a2e8 100644 --- a/LibGit2Sharp.Tests/NetworkFixture.cs +++ b/LibGit2Sharp.Tests/NetworkFixture.cs @@ -22,23 +22,26 @@ public void CanListRemoteReferences(string url) using (var repo = new Repository(repoPath)) { Remote remote = repo.Network.Remotes.Add(remoteName, url); - IList references = repo.Network.ListReferences(remote).ToList(); + IList references = repo.Network.ListReferences(remote).ToList(); - foreach (var directReference in references) + + foreach (var reference in references) { // None of those references point to an existing // object in this brand new repository - Assert.Null(directReference.Target); + Assert.Null(reference.ResolveToDirectReference().Target); } List> actualRefs = references. - Select(directRef => new Tuple(directRef.CanonicalName, directRef.TargetIdentifier)).ToList(); + Select(directRef => new Tuple(directRef.CanonicalName, directRef.ResolveToDirectReference() + .TargetIdentifier)).ToList(); - Assert.Equal(ExpectedRemoteRefs.Count, actualRefs.Count); - for (int i = 0; i < ExpectedRemoteRefs.Count; i++) + Assert.Equal(TestRemoteRefs.ExpectedRemoteRefs.Count, actualRefs.Count); + Assert.True(references.Single(reference => reference.CanonicalName == "HEAD") is SymbolicReference); + for (int i = 0; i < TestRemoteRefs.ExpectedRemoteRefs.Count; i++) { - Assert.Equal(ExpectedRemoteRefs[i].Item2, actualRefs[i].Item2); - Assert.Equal(ExpectedRemoteRefs[i].Item1, actualRefs[i].Item1); + Assert.Equal(TestRemoteRefs.ExpectedRemoteRefs[i].Item2, actualRefs[i].Item2); + Assert.Equal(TestRemoteRefs.ExpectedRemoteRefs[i].Item1, actualRefs[i].Item1); } } } @@ -53,23 +56,25 @@ public void CanListRemoteReferencesFromUrl(string url) using (var repo = new Repository(repoPath)) { - IList references = repo.Network.ListReferences(url).ToList(); + IList references = repo.Network.ListReferences(url).ToList(); - foreach (var directReference in references) + foreach (var reference in references) { // None of those references point to an existing // object in this brand new repository - Assert.Null(directReference.Target); + Assert.Null(reference.ResolveToDirectReference().Target); } List> actualRefs = references. - Select(directRef => new Tuple(directRef.CanonicalName, directRef.TargetIdentifier)).ToList(); + Select(directRef => new Tuple(directRef.CanonicalName, directRef.ResolveToDirectReference() + .TargetIdentifier)).ToList(); - Assert.Equal(ExpectedRemoteRefs.Count, actualRefs.Count); - for (int i = 0; i < ExpectedRemoteRefs.Count; i++) + Assert.Equal(TestRemoteRefs.ExpectedRemoteRefs.Count, actualRefs.Count); + Assert.True(references.Single(reference => reference.CanonicalName == "HEAD") is SymbolicReference); + for (int i = 0; i < TestRemoteRefs.ExpectedRemoteRefs.Count; i++) { - Assert.Equal(ExpectedRemoteRefs[i].Item2, actualRefs[i].Item2); - Assert.Equal(ExpectedRemoteRefs[i].Item1, actualRefs[i].Item1); + Assert.Equal(TestRemoteRefs.ExpectedRemoteRefs[i].Item2, actualRefs[i].Item2); + Assert.Equal(TestRemoteRefs.ExpectedRemoteRefs[i].Item1, actualRefs[i].Item1); } } } @@ -87,22 +92,26 @@ public void CanListRemoteReferenceObjects() using (var repo = new Repository(clonedRepoPath)) { Remote remote = repo.Network.Remotes[remoteName]; - IEnumerable references = repo.Network.ListReferences(remote); + IEnumerable references = repo.Network.ListReferences(remote).ToList(); var actualRefs = new List>(); - foreach(DirectReference reference in references) + foreach(Reference reference in references) { Assert.NotNull(reference.CanonicalName); - Assert.NotNull(reference.Target); - actualRefs.Add(new Tuple(reference.CanonicalName, reference.Target.Id.Sha)); + + var directReference = reference.ResolveToDirectReference(); + + Assert.NotNull(directReference.Target); + actualRefs.Add(new Tuple(reference.CanonicalName, directReference.Target.Id.Sha)); } - Assert.Equal(ExpectedRemoteRefs.Count, actualRefs.Count); - for (int i = 0; i < ExpectedRemoteRefs.Count; i++) + Assert.Equal(TestRemoteRefs.ExpectedRemoteRefs.Count, actualRefs.Count); + Assert.True(references.Single(reference => reference.CanonicalName == "HEAD") is SymbolicReference); + for (int i = 0; i < TestRemoteRefs.ExpectedRemoteRefs.Count; i++) { - Assert.Equal(ExpectedRemoteRefs[i].Item1, actualRefs[i].Item1); - Assert.Equal(ExpectedRemoteRefs[i].Item2, actualRefs[i].Item2); + Assert.Equal(TestRemoteRefs.ExpectedRemoteRefs[i].Item1, actualRefs[i].Item1); + Assert.Equal(TestRemoteRefs.ExpectedRemoteRefs[i].Item2, actualRefs[i].Item2); } } } @@ -123,9 +132,9 @@ public void CanListRemoteReferencesWithCredentials() var references = repo.Network.ListReferences(remote, Constants.PrivateRepoCredentials); - foreach (var directReference in references) + foreach (var reference in references) { - Assert.NotNull(directReference); + Assert.NotNull(reference); } } } @@ -155,17 +164,17 @@ public void CanPull(FastForwardStrategy fastForwardStrategy) } }; - MergeResult mergeResult = repo.Network.Pull(Constants.Signature, pullOptions); + MergeResult mergeResult = Commands.Pull(repo, Constants.Signature, pullOptions); if(fastForwardStrategy == FastForwardStrategy.Default || fastForwardStrategy == FastForwardStrategy.FastForwardOnly) { - Assert.Equal(mergeResult.Status, MergeStatus.FastForward); + Assert.Equal(MergeStatus.FastForward, mergeResult.Status); Assert.Equal(mergeResult.Commit, repo.Branches["refs/remotes/origin/master"].Tip); Assert.Equal(repo.Head.Tip, repo.Branches["refs/remotes/origin/master"].Tip); } else { - Assert.Equal(mergeResult.Status, MergeStatus.NonFastForward); + Assert.Equal(MergeStatus.NonFastForward, mergeResult.Status); } } } @@ -180,7 +189,7 @@ public void CanPullIntoEmptyRepo() using (var repo = new Repository(repoPath)) { // Set up remote - Remote remote = repo.Network.Remotes.Add(remoteName, url); + repo.Network.Remotes.Add(remoteName, url); // Set up tracking information repo.Branches.Update(repo.Head, @@ -188,9 +197,9 @@ public void CanPullIntoEmptyRepo() b => b.UpstreamBranch = "refs/heads/master"); // Pull! - MergeResult mergeResult = repo.Network.Pull(Constants.Signature, new PullOptions()); + MergeResult mergeResult = Commands.Pull(repo, Constants.Signature, new PullOptions()); - Assert.Equal(mergeResult.Status, MergeStatus.FastForward); + Assert.Equal(MergeStatus.FastForward, mergeResult.Status); Assert.Equal(mergeResult.Commit, repo.Branches["refs/remotes/origin/master"].Tip); Assert.Equal(repo.Head.Tip, repo.Branches["refs/remotes/origin/master"].Tip); } @@ -215,7 +224,7 @@ public void PullWithoutMergeBranchThrows() try { - repo.Network.Pull(Constants.Signature, new PullOptions()); + Commands.Pull(repo, Constants.Signature, new PullOptions()); } catch(MergeFetchHeadNotFoundException ex) { @@ -243,7 +252,7 @@ public void CanMergeFetchedRefs() Assert.False(repo.RetrieveStatus().Any()); Assert.Equal(repo.Lookup("refs/remotes/origin/master~1"), repo.Head.Tip); - repo.Network.Fetch(repo.Head.Remote); + Commands.Fetch(repo, repo.Head.RemoteName, new string[0], null, null); MergeOptions mergeOptions = new MergeOptions() { @@ -251,36 +260,42 @@ public void CanMergeFetchedRefs() }; MergeResult mergeResult = repo.MergeFetchedRefs(Constants.Signature, mergeOptions); - Assert.Equal(mergeResult.Status, MergeStatus.NonFastForward); + Assert.Equal(MergeStatus.NonFastForward, mergeResult.Status); } } - /* - * 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 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", new string[0], 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", new string[0], null, null); + Assert.NotNull(repo.Refs["refs/remotes/pruner/master"]); + + // but we do when asked by the user + Commands.Fetch(repo, "pruner", new string[0], 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 7da99db07..98801e3b2 100644 --- a/LibGit2Sharp.Tests/NoteFixture.cs +++ b/LibGit2Sharp.Tests/NoteFixture.cs @@ -10,7 +10,7 @@ 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() @@ -20,7 +20,7 @@ public void RetrievingNotesFromANonExistingGitObjectYieldsNoResult() { var notes = repo.Notes[ObjectId.Zero]; - Assert.Equal(0, notes.Count()); + Assert.Empty(notes); } } @@ -32,7 +32,7 @@ public void RetrievingNotesFromAGitObjectWhichHasNoNoteYieldsNoResult() { var notes = repo.Notes[new ObjectId("4c062a6361ae6959e06292c1fa5e2822d9c96345")]; - Assert.Equal(0, notes.Count()); + Assert.Empty(notes); } } @@ -168,14 +168,16 @@ public void CreatingANoteWhichAlreadyExistsOverwritesThePreviousNote() [Fact] public void CanAddANoteWithSignatureFromConfig() { - string configPath = CreateConfigurationWithDummyUser(Constants.Signature); - var options = new RepositoryOptions { GlobalConfigurationLocation = configPath }; string path = SandboxBareTestRepo(); - using (var repo = new Repository(path, options)) + using (var repo = new Repository(path)) { + CreateConfigurationWithDummyUser(repo, Constants.Identity); var commit = repo.Lookup("9fd738e8f7967c078dceed8190330fc8648ee56a"); - var note = repo.Notes.Add(commit.Id, "I'm batman!\n", "batmobile"); + + Signature signature = repo.Config.BuildSignature(DateTimeOffset.Now); + + var note = repo.Notes.Add(commit.Id, "I'm batman!\n", signature, signature, "batmobile"); var newNote = commit.Notes.Single(); Assert.Equal(note, newNote); @@ -183,7 +185,7 @@ public void CanAddANoteWithSignatureFromConfig() Assert.Equal("I'm batman!\n", newNote.Message); Assert.Equal("batmobile", newNote.Namespace); - AssertCommitSignaturesAre(repo.Lookup("refs/notes/batmobile"), Constants.Signature); + AssertCommitIdentitiesAre(repo.Lookup("refs/notes/batmobile"), Constants.Identity); } } @@ -265,22 +267,23 @@ public void RemovingANonExistingNoteDoesntThrow() [Fact] public void CanRemoveANoteWithSignatureFromConfig() { - string configPath = CreateConfigurationWithDummyUser(Constants.Signature); - RepositoryOptions options = new RepositoryOptions() { GlobalConfigurationLocation = configPath }; string path = SandboxBareTestRepo(); - using (var repo = new Repository(path, options)) + using (var repo = new Repository(path)) { + CreateConfigurationWithDummyUser(repo, Constants.Identity); var commit = repo.Lookup("8496071c1b46c854b31185ea97743be6a8774479"); var notes = repo.Notes[commit.Id]; Assert.NotEmpty(notes); - repo.Notes.Remove(commit.Id, repo.Notes.DefaultNamespace); + Signature signature = repo.Config.BuildSignature(DateTimeOffset.Now); + + repo.Notes.Remove(commit.Id, signature, signature, repo.Notes.DefaultNamespace); Assert.Empty(notes); - AssertCommitSignaturesAre(repo.Lookup("refs/notes/" + repo.Notes.DefaultNamespace), Constants.Signature); + AssertCommitIdentitiesAre(repo.Lookup("refs/notes/" + repo.Notes.DefaultNamespace), Constants.Identity); } } @@ -305,6 +308,21 @@ public void CanRetrieveTheListOfNotesForAGivenNamespace() } } + [Fact] + public void CanRetrieveNotesWhenThereAreNotAny() + { + string path = InitNewRepository(); // doesn't reproduce an error when using a sandbox repository so we have to create an actual repo. + using (var repo = new Repository(path)) + { + foreach (var note in repo.Notes) + { + Assert.NotNull(note); + } + Assert.Empty(repo.Notes); + } + } + + private static T[] SortedNotes(IEnumerable notes, Func selector) { return notes.OrderBy(n => n.Message, StringComparer.Ordinal).Select(selector).ToArray(); diff --git a/LibGit2Sharp.Tests/ObjectDatabaseFixture.cs b/LibGit2Sharp.Tests/ObjectDatabaseFixture.cs index 62c4a67a3..34d3eb77c 100644 --- a/LibGit2Sharp.Tests/ObjectDatabaseFixture.cs +++ b/LibGit2Sharp.Tests/ObjectDatabaseFixture.cs @@ -61,7 +61,7 @@ public void RetrieveObjectMetadataReturnsCorrectSizeAndTypeForBlob() GitObjectMetadata blobMetadata = repo.ObjectDatabase.RetrieveObjectMetadata(blob.Id); Assert.Equal(blobMetadata.Size, blob.Size); - Assert.Equal(blobMetadata.Type, ObjectType.Blob); + Assert.Equal(ObjectType.Blob, blobMetadata.Type); Blob fetchedBlob = repo.Lookup(blob.Id); Assert.Equal(blobMetadata.Size, fetchedBlob.Size); @@ -103,15 +103,13 @@ public void CanCreateABlobIntoTheDatabaseOfABareRepository() [InlineData("e9671e138a780833cb689753570fd10a55be84fb", "dummy.guess")] public void CanCreateABlobFromAStream(string expectedSha, string hintPath) { - string path = InitNewRepository(); - var sb = new StringBuilder(); for (int i = 0; i < 6; i++) { sb.Append("libgit2\n\r\n"); } - using (var repo = new Repository(path)) + using (var repo = new Repository(InitNewRepository())) { CreateAttributesFiles(Path.Combine(repo.Info.Path, "info"), "attributes"); @@ -123,6 +121,31 @@ public void CanCreateABlobFromAStream(string expectedSha, string hintPath) } } + [Fact] + public void CanWriteABlobFromAByteArray() + { + var ba = Encoding.ASCII.GetBytes("libgit2\r\n"); + + using (var repo = new Repository(InitNewRepository())) + { + var id = repo.ObjectDatabase.Write(ba); + Assert.Equal(new ObjectId("99115ea359379a218c47cffc83cd0af8c91c4061"), id); + } + } + + [Fact] + public void CanWriteABlobFromAStream() + { + var ba = Encoding.ASCII.GetBytes("libgit2\r\n"); + + using (var stream = new MemoryStream(ba)) + using (var repo = new Repository(InitNewRepository())) + { + var id = repo.ObjectDatabase.Write(stream, stream.Length); + Assert.Equal(new ObjectId("99115ea359379a218c47cffc83cd0af8c91c4061"), id); + } + } + Stream PrepareMemoryStream(int contentSize) { var sb = new StringBuilder(); @@ -451,7 +474,7 @@ public void CanCreateABinaryBlobFromAStream() { Blob blob = repo.ObjectDatabase.CreateBlob(stream); Assert.Equal(6, blob.Size); - Assert.Equal(true, blob.IsBinary); + Assert.True(blob.IsBinary); } } } @@ -678,7 +701,7 @@ public void TestMergeIntoOtherUnbornBranchHasNoConflicts() Touch(repo.Info.WorkingDirectory, "README", "Yeah!\n"); repo.Index.Clear(); - repo.Stage("README"); + Commands.Stage(repo, "README"); repo.Commit("A new world, free of the burden of the history", Constants.Signature, Constants.Signature); diff --git a/LibGit2Sharp.Tests/ObjectIdFixture.cs b/LibGit2Sharp.Tests/ObjectIdFixture.cs index e118cfdb8..8d3468bdd 100644 --- a/LibGit2Sharp.Tests/ObjectIdFixture.cs +++ b/LibGit2Sharp.Tests/ObjectIdFixture.cs @@ -133,7 +133,7 @@ public void TryParse(string maybeSha, bool isValidSha) Assert.NotNull(parsedObjectId); Assert.Equal(maybeSha, parsedObjectId.Sha); - Assert.True(maybeSha.StartsWith(parsedObjectId.ToString(3))); + Assert.StartsWith(parsedObjectId.ToString(3), maybeSha); Assert.Equal(maybeSha, parsedObjectId.ToString(42)); } diff --git a/LibGit2Sharp.Tests/OdbBackendFixture.cs b/LibGit2Sharp.Tests/OdbBackendFixture.cs index 2ca40a4cb..975d0e88c 100644 --- a/LibGit2Sharp.Tests/OdbBackendFixture.cs +++ b/LibGit2Sharp.Tests/OdbBackendFixture.cs @@ -16,7 +16,7 @@ private static Commit AddCommitToRepo(IRepository repo) { string relativeFilepath = "test.txt"; Touch(repo.Info.WorkingDirectory, relativeFilepath, content); - repo.Stage(relativeFilepath); + Commands.Stage(repo, relativeFilepath); var ie = repo.Index[relativeFilepath]; Assert.NotNull(ie); @@ -28,7 +28,7 @@ private static Commit AddCommitToRepo(IRepository repo) relativeFilepath = "big.txt"; var zeros = new string('0', 32*1024 + 3); Touch(repo.Info.WorkingDirectory, relativeFilepath, zeros); - repo.Stage(relativeFilepath); + Commands.Stage(repo, relativeFilepath); ie = repo.Index[relativeFilepath]; Assert.NotNull(ie); @@ -90,37 +90,46 @@ public void CanGeneratePredictableObjectShasWithAProvidedBackend() [Fact] public void CanRetrieveObjectsThroughOddSizedShortShas() { - string repoPath = InitNewRepository(); - - using (var repo = new Repository(repoPath)) + try { - var backend = new MockOdbBackend(); - repo.ObjectDatabase.AddBackend(backend, priority: 5); + GlobalSettings.SetStrictHashVerification(false); - AddCommitToRepo(repo); + string repoPath = InitNewRepository(); - var blob1 = repo.Lookup("9daeaf"); - Assert.NotNull(blob1); + using (var repo = new Repository(repoPath)) + { + var backend = new MockOdbBackend(); + repo.ObjectDatabase.AddBackend(backend, priority: 5); - const string dummy = "dummy\n"; + AddCommitToRepo(repo); - // Inserts a fake blob with a similarly prefixed sha - var fakeId = new ObjectId("9daeaf0000000000000000000000000000000000"); - using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(dummy))) - { - Assert.Equal(0, backend.Write(fakeId, ms, dummy.Length, ObjectType.Blob)); - } + var blob1 = repo.Lookup("9daeaf"); + Assert.NotNull(blob1); + + const string dummy = "dummy\n"; + + // Inserts a fake blob with a similarly prefixed sha + var fakeId = new ObjectId("9daeaf0000000000000000000000000000000000"); + using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(dummy))) + { + Assert.Equal(0, backend.Write(fakeId, ms, dummy.Length, ObjectType.Blob)); + } - var blob2 = repo.Lookup(fakeId); - Assert.NotNull(blob2); + var blob2 = repo.Lookup(fakeId); + Assert.NotNull(blob2); - Assert.Throws(() => repo.Lookup("9daeaf")); + Assert.Throws(() => repo.Lookup("9daeaf")); - var newBlob1 = repo.Lookup("9daeafb"); - var newBlob2 = repo.Lookup("9daeaf0"); + var newBlob1 = repo.Lookup("9daeafb"); + var newBlob2 = repo.Lookup("9daeaf0"); - Assert.Equal(blob1, newBlob1); - Assert.Equal(blob2, newBlob2); + Assert.Equal(blob1, newBlob1); + Assert.Equal(blob2, newBlob2); + } + } + finally + { + GlobalSettings.SetStrictHashVerification(true); } } 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..dc2552a10 --- /dev/null +++ b/LibGit2Sharp.Tests/PatchEntryChangesFixture.cs @@ -0,0 +1,44 @@ +using System; +using System.IO; +using System.Linq; +using System.Text; +using LibGit2Sharp.Tests.TestHelpers; +using Xunit; +using Xunit.Extensions; + +namespace LibGit2Sharp.Tests +{ + public class PatchEntryChangesFixture : BaseFixture + { + [Fact] + public void PatchEntryBasics() + { + // Init test repo + var path = SandboxStandardTestRepoGitDir(); + string file = "numbers.txt"; + + // The repo + using (var repo = new Repository(path)) + { + Tree rootCommitTree = repo.Lookup("f8d44d7").Tree; + Tree commitTreeWithUpdatedFile = repo.Lookup("ec9e401").Tree; + + // Create patch by diffing + using (var patch = repo.Diff.Compare(rootCommitTree, commitTreeWithUpdatedFile)) + { + PatchEntryChanges entryChanges = patch[file]; + Assert.Equal(2, entryChanges.LinesAdded); + Assert.Equal(1, entryChanges.LinesDeleted); + Assert.Equal(187, entryChanges.Patch.Length); + // Smoke test + Assert.Equal(Mode.NonExecutableFile, entryChanges.Mode); + Assert.Equal(new ObjectId("4625a3628cb78970c57e23a2fe2574514ba403c7"), entryChanges.Oid); + Assert.Equal(ChangeKind.Modified, entryChanges.Status); + Assert.Equal(file, entryChanges.OldPath); + Assert.Equal(Mode.NonExecutableFile, entryChanges.OldMode); + Assert.Equal(new ObjectId("7909961ae96accd75b6813d32e0fc1d6d52ec941"), entryChanges.OldOid); + } + } + } + } +} diff --git a/LibGit2Sharp.Tests/PatchStatsFixture.cs b/LibGit2Sharp.Tests/PatchStatsFixture.cs index 41d3fdb23..758a08e2a 100644 --- a/LibGit2Sharp.Tests/PatchStatsFixture.cs +++ b/LibGit2Sharp.Tests/PatchStatsFixture.cs @@ -13,14 +13,15 @@ public void CanExtractStatisticsFromDiff() { var oldTree = repo.Lookup("origin/packed-test").Tree; var newTree = repo.Lookup("HEAD").Tree; - var stats = repo.Diff.Compare(oldTree, newTree); + using (var stats = repo.Diff.Compare(oldTree, newTree)) + { + Assert.Equal(8, stats.TotalLinesAdded); + Assert.Equal(1, stats.TotalLinesDeleted); - Assert.Equal(8, stats.TotalLinesAdded); - Assert.Equal(1, stats.TotalLinesDeleted); - - var contentStats = stats["new.txt"]; - Assert.Equal(1, contentStats.LinesAdded); - Assert.Equal(1, contentStats.LinesDeleted); + var contentStats = stats["new.txt"]; + Assert.Equal(1, contentStats.LinesAdded); + Assert.Equal(1, contentStats.LinesDeleted); + } } } } diff --git a/LibGit2Sharp.Tests/Properties/AssemblyInfo.cs b/LibGit2Sharp.Tests/Properties/AssemblyInfo.cs index 6d2256554..a4bcec543 100644 --- a/LibGit2Sharp.Tests/Properties/AssemblyInfo.cs +++ b/LibGit2Sharp.Tests/Properties/AssemblyInfo.cs @@ -1,39 +1,3 @@ -using System.Reflection; -using System.Runtime.InteropServices; +using Xunit; -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. - -[assembly: AssemblyTitle("libgit2sharp.Tests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("libgit2sharp.Tests")] -[assembly: AssemblyCopyright("Copyright © 2010")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. - -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM - -[assembly: Guid("808554a4-f9fd-4035-8ab9-325793c7da51")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] - -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: CollectionBehavior(DisableTestParallelization = true)] diff --git a/LibGit2Sharp.Tests/PushFixture.cs b/LibGit2Sharp.Tests/PushFixture.cs index 10261d0b3..d8cf2befe 100644 --- a/LibGit2Sharp.Tests/PushFixture.cs +++ b/LibGit2Sharp.Tests/PushFixture.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using LibGit2Sharp.Handlers; @@ -37,7 +38,7 @@ private void AssertPush(Action push) // Change local state (commit) const string relativeFilepath = "new_file.txt"; Touch(clonedRepo.Info.WorkingDirectory, relativeFilepath, "__content__"); - clonedRepo.Stage(relativeFilepath); + Commands.Stage(clonedRepo, relativeFilepath); clonedRepo.Commit("__commit_message__", Constants.Signature, Constants.Signature); // Assert local state has changed @@ -78,6 +79,63 @@ public void CanPushABranchTrackingAnUpstreamBranch() Assert.True(packBuilderCalled); } + [Fact] + public void CanInvokePrePushCallbackAndSucceed() + { + bool packBuilderCalled = false; + bool prePushHandlerCalled = false; + PackBuilderProgressHandler packBuilderCb = (x, y, z) => { packBuilderCalled = true; return true; }; + PrePushHandler prePushHook = (IEnumerable updates) => + { + Assert.True(updates.Count() == 1, "Expected 1 update, received " + updates.Count()); + prePushHandlerCalled = true; + return true; + }; + + AssertPush(repo => repo.Network.Push(repo.Head)); + AssertPush(repo => repo.Network.Push(repo.Branches["master"])); + + PushOptions options = new PushOptions() + { + OnPushStatusError = OnPushStatusError, + OnPackBuilderProgress = packBuilderCb, + OnNegotiationCompletedBeforePush = prePushHook, + }; + + AssertPush(repo => repo.Network.Push(repo.Network.Remotes["origin"], "HEAD", @"refs/heads/master", options)); + Assert.True(packBuilderCalled); + Assert.True(prePushHandlerCalled); + } + + [Fact] + public void CanInvokePrePushCallbackAndFail() + { + bool packBuilderCalled = false; + bool prePushHandlerCalled = false; + PackBuilderProgressHandler packBuilderCb = (x, y, z) => { packBuilderCalled = true; return true; }; + PrePushHandler prePushHook = (IEnumerable updates) => + { + Assert.True(updates.Count() == 1, "Expected 1 update, received " + updates.Count()); + prePushHandlerCalled = true; + return false; + }; + + AssertPush(repo => repo.Network.Push(repo.Head)); + AssertPush(repo => repo.Network.Push(repo.Branches["master"])); + + PushOptions options = new PushOptions() + { + OnPushStatusError = OnPushStatusError, + OnPackBuilderProgress = packBuilderCb, + OnNegotiationCompletedBeforePush = prePushHook + }; + + Assert.Throws(() => { AssertPush(repo => repo.Network.Push(repo.Network.Remotes["origin"], "HEAD", @"refs/heads/master", options)); }); + + Assert.False(packBuilderCalled); + Assert.True(prePushHandlerCalled); + } + [Fact] public void PushingABranchThatDoesNotTrackAnUpstreamBranchThrows() { @@ -124,6 +182,9 @@ public void CanForcePush() // Force push the new commit string pushRefSpec = string.Format("+{0}:{0}", localRepo.Head.CanonicalName); + + var before = DateTimeOffset.Now.TruncateMilliseconds(); + localRepo.Network.Push(localRepo.Network.Remotes.Single(), pushRefSpec); AssertRemoteHeadTipEquals(localRepo, second.Sha); @@ -131,16 +192,16 @@ public void CanForcePush() AssertRefLogEntry(localRepo, "refs/remotes/origin/master", "update by push", oldId, localRepo.Head.Tip.Id, - Constants.Identity, DateTimeOffset.Now); + Constants.Identity, before); } } private static void AssertRemoteHeadTipEquals(IRepository localRepo, string sha) { var remoteReferences = localRepo.Network.ListReferences(localRepo.Network.Remotes.Single()); - DirectReference remoteHead = remoteReferences.Single(r => r.CanonicalName == "HEAD"); + Reference remoteHead = remoteReferences.Single(r => r.CanonicalName == "HEAD"); - Assert.Equal(sha, remoteHead.TargetIdentifier); + Assert.Equal(sha, remoteHead.ResolveToDirectReference().TargetIdentifier); } private void UpdateTheRemoteRepositoryWithANewCommit(string remoteRepoPath) @@ -167,7 +228,7 @@ private Commit AddCommitToRepo(IRepository repository) Touch(repository.Info.WorkingDirectory, filename, random); - repository.Stage(filename); + Commands.Stage(repository, filename); return repository.Commit("New commit", Constants.Signature, Constants.Signature); } diff --git a/LibGit2Sharp.Tests/RebaseFixture.cs b/LibGit2Sharp.Tests/RebaseFixture.cs new file mode 100644 index 000000000..240ca8985 --- /dev/null +++ b/LibGit2Sharp.Tests/RebaseFixture.cs @@ -0,0 +1,784 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using LibGit2Sharp.Tests.TestHelpers; +using Xunit; +using Xunit.Extensions; + +namespace LibGit2Sharp.Tests +{ + public class RebaseFixture : BaseFixture + { + const string masterBranch1Name = "M1"; + const string masterBranch2Name = "M2"; + const string topicBranch1Name = "T1"; + const string topicBranch2Name = "T2"; + const string conflictBranch1Name = "C1"; + const string topicBranch1PrimeName = "T1Prime"; + + string filePathA = "a.txt"; + string filePathB = "b.txt"; + string filePathC = "c.txt"; + string filePathD = "d.txt"; + + [Theory] + [InlineData(topicBranch2Name, topicBranch2Name, topicBranch1Name, masterBranch1Name, 3)] + [InlineData(topicBranch2Name, topicBranch2Name, topicBranch1Name, topicBranch1Name, 3)] + [InlineData(topicBranch2Name, topicBranch1Name, masterBranch2Name, masterBranch2Name, 3)] + [InlineData(topicBranch2Name, topicBranch1Name, masterBranch2Name, null, 3)] + [InlineData(topicBranch1Name, null, masterBranch2Name, null, 3)] + public void CanRebase(string initialBranchName, + string branchName, + string upstreamName, + string ontoName, + int stepCount) + { + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + var path = Repository.Init(scd.DirectoryPath); + using (Repository repo = new Repository(path)) + { + ConstructRebaseTestRepository(repo); + + Commands.Checkout(repo, initialBranchName); + Assert.False(repo.RetrieveStatus().IsDirty); + + Branch branch = (branchName == null) ? null : repo.Branches[branchName]; + Branch upstream = repo.Branches[upstreamName]; + Branch onto = (ontoName == null) ? null : repo.Branches[ontoName]; + Commit expectedSinceCommit = (branch == null) ? repo.Head.Tip : branch.Tip; + Commit expectedUntilCommit = upstream.Tip; + Commit expectedOntoCommit = (onto == null) ? upstream.Tip : onto.Tip; + + int beforeStepCallCount = 0; + int afterStepCallCount = 0; + bool beforeRebaseStepCountCorrect = true; + bool afterRebaseStepCountCorrect = true; + bool totalStepCountCorrect = true; + + List PreRebaseCommits = new List(); + List PostRebaseResults = new List(); + ObjectId expectedParentId = upstream.Tip.Id; + + RebaseOptions options = new RebaseOptions() + { + RebaseStepStarting = x => + { + beforeRebaseStepCountCorrect &= beforeStepCallCount == x.StepIndex; + totalStepCountCorrect &= (x.TotalStepCount == stepCount); + beforeStepCallCount++; + PreRebaseCommits.Add(x.StepInfo.Commit); + }, + RebaseStepCompleted = x => + { + afterRebaseStepCountCorrect &= (afterStepCallCount == x.CompletedStepIndex); + totalStepCountCorrect &= (x.TotalStepCount == stepCount); + afterStepCallCount++; + PostRebaseResults.Add(new CompletedRebaseStepInfo(x.Commit, x.WasPatchAlreadyApplied)); + }, + }; + + RebaseResult rebaseResult = repo.Rebase.Start(branch, upstream, onto, Constants.Identity, options); + + // Validation: + Assert.True(afterRebaseStepCountCorrect, "Unexpected CompletedStepIndex value in RebaseStepCompleted"); + Assert.True(beforeRebaseStepCountCorrect, "Unexpected StepIndex value in RebaseStepStarting"); + Assert.True(totalStepCountCorrect, "Unexpected TotalStepcount value in Rebase step callback"); + Assert.Equal(RebaseStatus.Complete, rebaseResult.Status); + Assert.Equal(stepCount, rebaseResult.TotalStepCount); + Assert.Null(rebaseResult.CurrentStepInfo); + + Assert.Equal(stepCount, rebaseResult.CompletedStepCount); + Assert.False(repo.RetrieveStatus().IsDirty); + + Assert.Equal(stepCount, beforeStepCallCount); + Assert.Equal(stepCount, afterStepCallCount); + + // Verify the chain of source commits that were rebased. + CommitFilter sourceCommitFilter = new CommitFilter() + { + IncludeReachableFrom = expectedSinceCommit, + ExcludeReachableFrom = expectedUntilCommit, + SortBy = CommitSortStrategies.Reverse | CommitSortStrategies.Topological, + }; + Assert.Equal(repo.Commits.QueryBy(sourceCommitFilter), PreRebaseCommits); + + // Verify the chain of commits that resulted from the rebase. + Commit expectedParent = expectedOntoCommit; + foreach (CompletedRebaseStepInfo stepInfo in PostRebaseResults) + { + Commit rebasedCommit = stepInfo.Commit; + Assert.Equal(expectedParent.Id, rebasedCommit.Parents.First().Id); + Assert.False(stepInfo.WasPatchAlreadyApplied); + expectedParent = rebasedCommit; + } + + Assert.Equal(repo.Head.Tip, PostRebaseResults.Last().Commit); + } + } + + [Fact] + public void CanRebaseBranchOntoItself() + { + // Maybe we should have an "up-to-date" return type for scenarios such as these, + // but for now this test is to make sure we do something reasonable + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + var path = Repository.Init(scd.DirectoryPath); + using (Repository repo = new Repository(path)) + { + ConstructRebaseTestRepository(repo); + Commands.Checkout(repo, topicBranch2Name); + Branch b = repo.Branches[topicBranch2Name]; + + RebaseResult result = repo.Rebase.Start(b, b, null, Constants.Identity, new RebaseOptions()); + Assert.Equal(0, result.TotalStepCount); + Assert.Equal(RebaseStatus.Complete, result.Status); + Assert.Equal(0, result.CompletedStepCount); + } + } + + private class CompletedRebaseStepInfo + { + public CompletedRebaseStepInfo(Commit commit, bool wasPatchAlreadyApplied) + { + Commit = commit; + WasPatchAlreadyApplied = wasPatchAlreadyApplied; + } + + public Commit Commit { get; set; } + + public bool WasPatchAlreadyApplied { get; set; } + + public override string ToString() + { + return string.Format("CompletedRebaseStepInfo: {0}", Commit); + } + } + + private class CompletedRebaseStepInfoEqualityComparer : IEqualityComparer + { + bool IEqualityComparer.Equals(CompletedRebaseStepInfo x, CompletedRebaseStepInfo y) + { + if (x == null && y == null) + { + return true; + } + + if ((x == null && y != null) || + (x != null && y == null)) + { + return false; + } + + return x.WasPatchAlreadyApplied == y.WasPatchAlreadyApplied && + ObjectId.Equals(x.Commit, y.Commit); + } + + int IEqualityComparer.GetHashCode(CompletedRebaseStepInfo obj) + { + int hashCode = obj.WasPatchAlreadyApplied.GetHashCode(); + + if (obj.Commit != null) + { + hashCode += obj.Commit.GetHashCode(); + } + + return hashCode; + } + } + + /// + /// Verify a single rebase, but in more detail. + /// + [Theory] + [InlineData("* text=auto", "\r\n", new[] { "2cad6e96a0028f1764dcbde6292a9a1471acb114", "18fd3deebe6124b5dacc8426d589d617a968e8d1", "048977d8cb90d530e83cc615a17a49f3068f68c1" })] + [InlineData("* text=auto", "\n", new[] { "2cad6e96a0028f1764dcbde6292a9a1471acb114", "18fd3deebe6124b5dacc8426d589d617a968e8d1", "048977d8cb90d530e83cc615a17a49f3068f68c1" })] + [InlineData("* text=auto\n*.txt eol=lf", "\n", new[] { "577d176b00a55e88e9b34da87e4357dfc9a486fd", "ea0ad4d8b500394a61874ebfda5904376e2b1098", "521b8383ca3fde9e369587492e7a3945677f1b2c" })] + [InlineData("* text=auto\r\n*.txt eol=crlf", "\r\n", new[] { "67d29fdf654ac4773c9405ab4b54aa7ff092f339", "7b70c02e175d378b44ea28aeeece775cd972047a", "81f203dbfe00a5c1ecd9c0e6b03705e6cffda5c0" })] + [InlineData("* binary", "\r\n", new[] { "f5a5ded935597108709224170accddc5aeb5c287", "518adb8bb1ea1058d1825d3fe08d27f80c0e829b", "d2db503ab553c970d34e1b5e3ff68768adef05bc" })] + [InlineData("* binary", "\n", new[] { "93a0e9680246d1f1e43fbd5308f7936424d9e81a", "5fd40bffbdd884632c330a254a2bd1dfaaaad3c1", "4df5c91b2d8318781b07d04f6bfa77304c372f1e" })] + public void VerifyRebaseDetailed(string attributes, string lineEnding, string[] expectedIds) + { + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + var path = Repository.Init(scd.DirectoryPath); + + using (Repository repo = new Repository(path)) + { + ConstructRebaseTestRepository(repo, attributes, lineEnding); + + Branch initialBranch = repo.Branches[topicBranch1Name]; + Branch upstreamBranch = repo.Branches[masterBranch2Name]; + + Commands.Checkout(repo, initialBranch); + Assert.False(repo.RetrieveStatus().IsDirty); + + bool wasCheckoutProgressCalled = false; + bool wasCheckoutProgressCalledForResetingHead = false; + bool wasCheckoutNotifyCalled = false; + bool wasCheckoutNotifyCalledForResetingHead = false; + + bool startedApplyingSteps = false; + + RebaseOptions options = new RebaseOptions() + { + OnCheckoutProgress = (x, y, z) => + { + if (startedApplyingSteps) + { + wasCheckoutProgressCalled = true; + } + else + { + wasCheckoutProgressCalledForResetingHead = true; + } + }, + OnCheckoutNotify = (x, y) => + { + if (startedApplyingSteps) + { + wasCheckoutNotifyCalled = true; + } + else + { + wasCheckoutNotifyCalledForResetingHead = true; + } + + return true; + }, + CheckoutNotifyFlags = CheckoutNotifyFlags.Updated, + + RebaseStepStarting = x => startedApplyingSteps = true, + + }; + + repo.Rebase.Start(null, upstreamBranch, null, Constants.Identity2, options); + + Assert.True(wasCheckoutNotifyCalledForResetingHead); + Assert.True(wasCheckoutProgressCalledForResetingHead); + Assert.True(wasCheckoutNotifyCalled); + Assert.True(wasCheckoutProgressCalled); + + // Verify the chain of resultant rebased commits. + CommitFilter commitFilter = new CommitFilter() + { + IncludeReachableFrom = repo.Head.Tip, + ExcludeReachableFrom = upstreamBranch.Tip, + SortBy = CommitSortStrategies.Reverse | CommitSortStrategies.Topological, + }; + + List expectedTreeIds = new List() + { + new ObjectId(expectedIds[0]), + new ObjectId(expectedIds[1]), + new ObjectId(expectedIds[2]), + }; + + List rebasedCommits = repo.Commits.QueryBy(commitFilter).ToList(); + + Assert.Equal(3, rebasedCommits.Count); + for(int i = 0; i < 3; i++) + { + Assert.Equal(expectedTreeIds[i], rebasedCommits[i].Tree.Id); + Assert.Equal(Constants.Signature.Name, rebasedCommits[i].Author.Name); + Assert.Equal(Constants.Signature.Email, rebasedCommits[i].Author.Email); + Assert.Equal(Constants.Signature2.Name, rebasedCommits[i].Committer.Name); + Assert.Equal(Constants.Signature2.Email, rebasedCommits[i].Committer.Email); + } + } + } + + [Fact] + public void CanContinueRebase() + { + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + var path = Repository.Init(scd.DirectoryPath); + using (Repository repo = new Repository(path)) + { + ConstructRebaseTestRepository(repo); + + Commands.Checkout(repo, topicBranch1Name); + Assert.False(repo.RetrieveStatus().IsDirty); + + Branch branch = repo.Branches[topicBranch1Name]; + Branch upstream = repo.Branches[conflictBranch1Name]; + Branch onto = repo.Branches[conflictBranch1Name]; + + int beforeStepCallCount = 0; + int afterStepCallCount = 0; + bool wasCheckoutProgressCalled = false; + bool wasCheckoutNotifyCalled = false; + + RebaseOptions options = new RebaseOptions() + { + RebaseStepStarting = x => beforeStepCallCount++, + RebaseStepCompleted = x => afterStepCallCount++, + OnCheckoutProgress = (x, y, z) => wasCheckoutProgressCalled = true, + OnCheckoutNotify = (x, y) => { wasCheckoutNotifyCalled = true; return true; }, + CheckoutNotifyFlags = CheckoutNotifyFlags.Updated, + }; + + RebaseResult rebaseResult = repo.Rebase.Start(branch, upstream, onto, Constants.Identity, options); + + // Verify that we have a conflict. + Assert.Equal(CurrentOperation.RebaseMerge, repo.Info.CurrentOperation); + Assert.Equal(RebaseStatus.Conflicts, rebaseResult.Status); + Assert.True(repo.RetrieveStatus().IsDirty); + Assert.False(repo.Index.IsFullyMerged); + Assert.Equal(0, rebaseResult.CompletedStepCount); + Assert.Equal(3, rebaseResult.TotalStepCount); + + // Verify that expected callbacks were called + Assert.Equal(1, beforeStepCallCount); + Assert.Equal(0, afterStepCallCount); + Assert.True(wasCheckoutProgressCalled, "CheckoutProgress callback was not called."); + + // Resolve the conflict + foreach (Conflict conflict in repo.Index.Conflicts) + { + Touch(repo.Info.WorkingDirectory, + conflict.Theirs.Path, + repo.Lookup(conflict.Theirs.Id).GetContentText(new FilteringOptions(conflict.Theirs.Path))); + Commands.Stage(repo, conflict.Theirs.Path); + } + + Assert.True(repo.Index.IsFullyMerged); + + // Clear the flags: + wasCheckoutProgressCalled = false; wasCheckoutNotifyCalled = false; + RebaseResult continuedRebaseResult = repo.Rebase.Continue(Constants.Identity, options); + + Assert.NotNull(continuedRebaseResult); + Assert.Equal(RebaseStatus.Complete, continuedRebaseResult.Status); + Assert.False(repo.RetrieveStatus().IsDirty); + Assert.True(repo.Index.IsFullyMerged); + Assert.Equal(0, rebaseResult.CompletedStepCount); + Assert.Equal(3, rebaseResult.TotalStepCount); + + Assert.Equal(3, beforeStepCallCount); + Assert.Equal(3, afterStepCallCount); + Assert.True(wasCheckoutProgressCalled, "CheckoutProgress callback was not called."); + Assert.True(wasCheckoutNotifyCalled, "CheckoutNotify callback was not called."); + } + } + + [Fact] + public void ContinuingRebaseWithUnstagedChangesThrows() + { + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + var path = Repository.Init(scd.DirectoryPath); + using (Repository repo = new Repository(path)) + { + ConstructRebaseTestRepository(repo); + + Commands.Checkout(repo, topicBranch1Name); + Assert.False(repo.RetrieveStatus().IsDirty); + + Branch branch = repo.Branches[topicBranch1Name]; + Branch upstream = repo.Branches[conflictBranch1Name]; + Branch onto = repo.Branches[conflictBranch1Name]; + + RebaseResult rebaseResult = repo.Rebase.Start(branch, upstream, onto, Constants.Identity, null); + + // Verify that we have a conflict. + Assert.Equal(CurrentOperation.RebaseMerge, repo.Info.CurrentOperation); + Assert.Equal(RebaseStatus.Conflicts, rebaseResult.Status); + Assert.True(repo.RetrieveStatus().IsDirty); + Assert.False(repo.Index.IsFullyMerged); + Assert.Equal(0, rebaseResult.CompletedStepCount); + Assert.Equal(3, rebaseResult.TotalStepCount); + + Assert.Throws(() => + repo.Rebase.Continue(Constants.Identity, null)); + + // Resolve the conflict + foreach (Conflict conflict in repo.Index.Conflicts) + { + Touch(repo.Info.WorkingDirectory, + conflict.Theirs.Path, + repo.Lookup(conflict.Theirs.Id).GetContentText(new FilteringOptions(conflict.Theirs.Path))); + Commands.Stage(repo, conflict.Theirs.Path); + } + + Touch(repo.Info.WorkingDirectory, + filePathA, + "Unstaged content"); + + Assert.Throws(() => + repo.Rebase.Continue(Constants.Identity, null)); + + Assert.True(repo.Index.IsFullyMerged); + } + } + + [Fact] + public void CanSpecifyFileConflictStrategy() + { + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + var path = Repository.Init(scd.DirectoryPath); + using (Repository repo = new Repository(path)) + { + ConstructRebaseTestRepository(repo); + + Commands.Checkout(repo, topicBranch1Name); + Assert.False(repo.RetrieveStatus().IsDirty); + + Branch branch = repo.Branches[topicBranch1Name]; + Branch upstream = repo.Branches[conflictBranch1Name]; + Branch onto = repo.Branches[conflictBranch1Name]; + + RebaseOptions options = new RebaseOptions() + { + FileConflictStrategy = CheckoutFileConflictStrategy.Ours, + }; + + RebaseResult rebaseResult = repo.Rebase.Start(branch, upstream, onto, Constants.Identity, options); + + // Verify that we have a conflict. + Assert.Equal(CurrentOperation.RebaseMerge, repo.Info.CurrentOperation); + Assert.Equal(RebaseStatus.Conflicts, rebaseResult.Status); + Assert.True(repo.RetrieveStatus().IsDirty); + Assert.False(repo.Index.IsFullyMerged); + Assert.Equal(0, rebaseResult.CompletedStepCount); + Assert.Equal(3, rebaseResult.TotalStepCount); + + string conflictFile = filePathB; + // Get the information on the conflict. + Conflict conflict = repo.Index.Conflicts[conflictFile]; + + Assert.NotNull(conflict); + Assert.NotNull(conflict.Theirs); + Assert.NotNull(conflict.Ours); + + Blob expectedBlob = repo.Lookup(conflict.Ours.Id); + + // Check the content of the file on disk matches what is expected. + string expectedContent = expectedBlob.GetContentText(new FilteringOptions(conflictFile)); + Assert.Equal(expectedContent, File.ReadAllText(Path.Combine(repo.Info.WorkingDirectory, conflictFile))); + } + } + + [Fact] + public void CanQueryRebaseOperation() + { + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + var path = Repository.Init(scd.DirectoryPath); + using (Repository repo = new Repository(path)) + { + ConstructRebaseTestRepository(repo); + + Commands.Checkout(repo, topicBranch1Name); + Assert.False(repo.RetrieveStatus().IsDirty); + + Branch branch = repo.Branches[topicBranch1Name]; + Branch upstream = repo.Branches[conflictBranch1Name]; + Branch onto = repo.Branches[conflictBranch1Name]; + + RebaseResult rebaseResult = repo.Rebase.Start(branch, upstream, onto, Constants.Identity, null); + + // Verify that we have a conflict. + Assert.Equal(RebaseStatus.Conflicts, rebaseResult.Status); + Assert.True(repo.RetrieveStatus().IsDirty); + Assert.False(repo.Index.IsFullyMerged); + Assert.Equal(0, rebaseResult.CompletedStepCount); + Assert.Equal(3, rebaseResult.TotalStepCount); + + RebaseStepInfo info = repo.Rebase.GetCurrentStepInfo(); + + Assert.Equal(0, repo.Rebase.GetCurrentStepIndex()); + Assert.Equal(3, repo.Rebase.GetTotalStepCount()); + Assert.Equal(RebaseStepOperation.Pick, info.Type); + } + } + + [Fact] + public void CanAbortRebase() + { + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + var path = Repository.Init(scd.DirectoryPath); + using (Repository repo = new Repository(path)) + { + ConstructRebaseTestRepository(repo); + + Commands.Checkout(repo, topicBranch1Name); + Assert.False(repo.RetrieveStatus().IsDirty); + + Branch branch = repo.Branches[topicBranch1Name]; + Branch upstream = repo.Branches[conflictBranch1Name]; + Branch onto = repo.Branches[conflictBranch1Name]; + + RebaseResult rebaseResult = repo.Rebase.Start(branch, upstream, onto, Constants.Identity, null); + + // Verify that we have a conflict. + Assert.Equal(RebaseStatus.Conflicts, rebaseResult.Status); + Assert.True(repo.RetrieveStatus().IsDirty); + Assert.False(repo.Index.IsFullyMerged); + Assert.Equal(0, rebaseResult.CompletedStepCount); + Assert.Equal(3, rebaseResult.TotalStepCount); + + // Set up the callbacks to verify that checkout progress / notify + // callbacks are called. + bool wasCheckoutProgressCalled = false; + bool wasCheckoutNotifyCalled = false; + RebaseOptions options = new RebaseOptions() + { + OnCheckoutProgress = (x, y, z) => wasCheckoutProgressCalled = true, + OnCheckoutNotify = (x, y) => { wasCheckoutNotifyCalled = true; return true; }, + CheckoutNotifyFlags = CheckoutNotifyFlags.Updated, + }; + + repo.Rebase.Abort(options); + Assert.False(repo.RetrieveStatus().IsDirty, "Repository workdir is dirty after Rebase.Abort."); + Assert.True(repo.Index.IsFullyMerged, "Repository index is not fully merged after Rebase.Abort."); + Assert.Equal(CurrentOperation.None, repo.Info.CurrentOperation); + + Assert.True(wasCheckoutProgressCalled, "Checkout progress callback was not called during Rebase.Abort."); + Assert.True(wasCheckoutNotifyCalled, "Checkout notify callback was not called during Rebase.Abort."); + } + } + + [Fact] + public void RebaseWhileAlreadyRebasingThrows() + { + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + var path = Repository.Init(scd.DirectoryPath); + using (Repository repo = new Repository(path)) + { + ConstructRebaseTestRepository(repo); + + Commands.Checkout(repo, topicBranch1Name); + Assert.False(repo.RetrieveStatus().IsDirty); + + Branch branch = repo.Branches[topicBranch1Name]; + Branch upstream = repo.Branches[conflictBranch1Name]; + Branch onto = repo.Branches[conflictBranch1Name]; + + RebaseResult rebaseResult = repo.Rebase.Start(branch, upstream, onto, Constants.Identity, null); + + // Verify that we have a conflict. + Assert.Equal(RebaseStatus.Conflicts, rebaseResult.Status); + Assert.True(repo.RetrieveStatus().IsDirty); + Assert.Equal(CurrentOperation.RebaseMerge, repo.Info.CurrentOperation); + + Assert.Throws(() => + repo.Rebase.Start(branch, upstream, onto, Constants.Identity, null)); + } + } + + [Fact] + public void RebaseOperationsWithoutRebasingThrow() + { + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + var path = Repository.Init(scd.DirectoryPath); + using (Repository repo = new Repository(path)) + { + ConstructRebaseTestRepository(repo); + + Commands.Checkout(repo, topicBranch1Name); + + Assert.Throws(() => + repo.Rebase.Continue(Constants.Identity, new RebaseOptions())); + + Assert.Throws(() => + repo.Rebase.Abort()); + } + } + + [Fact] + public void CurrentStepInfoIsNullWhenNotRebasing() + { + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + var path = Repository.Init(scd.DirectoryPath); + using (Repository repo = new Repository(path)) + { + ConstructRebaseTestRepository(repo); + Commands.Checkout(repo, topicBranch1Name); + + Assert.Null(repo.Rebase.GetCurrentStepInfo()); + } + } + + [Theory] + [InlineData("* text=auto", "\r\n", "379e80ed7824be7672e1e30ddd8f44aa081d57d4")] + [InlineData("* text=auto", "\n", "379e80ed7824be7672e1e30ddd8f44aa081d57d4")] + [InlineData("* text=auto\n*.txt eol=lf", "\n", "94121eeebf7cfe0acf22425eab36fcdc737132b6")] + [InlineData("* text=auto\r\n*.txt eol=crlf", "\r\n", "dad06142cc632aea81cbc8486583011c4d622580")] + [InlineData("* binary", "\r\n", "44492d98b725189cfc0203d4192dfbb1fd34bf02")] + [InlineData("* binary", "\n", "f4b5b95de77f4cd97b4728617bae2dd8ba9af914")] + public void CanRebaseHandlePatchAlreadyApplied(string attributes, string lineEnding, string expectedShaText) + { + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + var path = Repository.Init(scd.DirectoryPath); + using (Repository repo = new Repository(path)) + { + ConstructRebaseTestRepository(repo, attributes, lineEnding); + + Commands.Checkout(repo, topicBranch1Name); + + Branch topicBranch1Prime = repo.CreateBranch(topicBranch1PrimeName, masterBranch1Name); + + string newFileRelativePath = "new_file.txt"; + Touch(repo.Info.WorkingDirectory, newFileRelativePath, "New Content"); + Commands.Stage(repo, newFileRelativePath); + Commit commit = repo.Commit("new commit 1", Constants.Signature, Constants.Signature, new CommitOptions()); + + Commands.Checkout(repo, topicBranch1Prime); + var cherryPickResult = repo.CherryPick(commit, Constants.Signature2); + Assert.Equal(CherryPickStatus.CherryPicked, cherryPickResult.Status); + + string newFileRelativePath2 = "new_file_2.txt"; + Touch(repo.Info.WorkingDirectory, newFileRelativePath2, "New Content for path 2"); + Commands.Stage(repo, newFileRelativePath2); + repo.Commit("new commit 2", Constants.Signature, Constants.Signature, new CommitOptions()); + + Branch upstreamBranch = repo.Branches[topicBranch1Name]; + + List rebaseResults = new List(); + + RebaseOptions options = new RebaseOptions() + { + RebaseStepCompleted = x => + { + rebaseResults.Add(new CompletedRebaseStepInfo(x.Commit, x.WasPatchAlreadyApplied)); + } + }; + + repo.Rebase.Start(null, upstreamBranch, null, Constants.Identity2, options); + ObjectId secondCommitExpectedTreeId = new ObjectId(expectedShaText); + Signature secondCommitAuthorSignature = Constants.Signature; + Identity secondCommitCommiterIdentity = Constants.Identity2; + + Assert.Equal(2, rebaseResults.Count); + Assert.True(rebaseResults[0].WasPatchAlreadyApplied); + + Assert.False(rebaseResults[1].WasPatchAlreadyApplied); + Assert.NotNull(rebaseResults[1].Commit); + + // This is the expected tree ID of the new commit. + Assert.Equal(secondCommitExpectedTreeId, rebaseResults[1].Commit.Tree.Id); + Assert.True(Signature.Equals(secondCommitAuthorSignature, rebaseResults[1].Commit.Author)); + Assert.Equal(secondCommitCommiterIdentity.Name, rebaseResults[1].Commit.Committer.Name, StringComparer.Ordinal); + Assert.Equal(secondCommitCommiterIdentity.Email, rebaseResults[1].Commit.Committer.Email, StringComparer.Ordinal); + } + } + + [Fact] + public void RebasingInBareRepositoryThrows() + { + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) + { + Branch rebaseUpstreamBranch = repo.Branches["refs/heads/test"]; + + Assert.NotNull(rebaseUpstreamBranch); + Assert.Throws(() => repo.Rebase.Start(null, rebaseUpstreamBranch, null, Constants.Identity, new RebaseOptions())); + Assert.Throws(() => repo.Rebase.Continue(Constants.Identity, new RebaseOptions())); + Assert.Throws(() => repo.Rebase.Abort()); + } + } + + private void ConstructRebaseTestRepository(Repository repo, string attributes = "* text=auto", string lineEnding = "\r\n") + { + // Constructs a graph that looks like: + // * -- * -- * (modifications to c.txt) + // / | + // / T2 + // / + // * -- * -- * (modifications to b.txt) + // / | + // / T1 + // / + // *--*--*--*--*--*---- + // | | \ + // M1 M2 \ + // ---* + // | + // C1 + const string fileContentA1 = "A1"; + + const string fileContentB1 = "B1"; + const string fileContentB2 = "B2"; + const string fileContentB3 = "B3"; + const string fileContentB4 = "B4"; + + const string fileContentC1 = "C1"; + const string fileContentC2 = "C2"; + const string fileContentC3 = "C3"; + const string fileContentC4 = "C4"; + + const string fileContentD1 = "D1"; + const string fileContentD2 = "D2"; + const string fileContentD3 = "D3"; + + string workdir = repo.Info.WorkingDirectory; + Commit commit = null; + + CreateAttributesFile(repo, attributes); + + Commands.Stage(repo, ".gitattributes"); + commit = repo.Commit("setup", Constants.Signature, Constants.Signature, new CommitOptions()); + + Touch(workdir, filePathA, fileContentA1); + Commands.Stage(repo, filePathA); + commit = repo.Commit("commit 1", Constants.Signature, Constants.Signature, new CommitOptions()); + + Touch(workdir, filePathB, fileContentB1); + Commands.Stage(repo, filePathB); + commit = repo.Commit("commit 2", Constants.Signature, Constants.Signature, new CommitOptions()); + + Touch(workdir, filePathC, fileContentC1); + Commands.Stage(repo, filePathC); + commit = repo.Commit("commit 3", Constants.Signature, Constants.Signature, new CommitOptions()); + + Branch masterBranch1 = repo.CreateBranch(masterBranch1Name, commit); + + Touch(workdir, filePathB, string.Join(lineEnding, fileContentB1, fileContentB2)); + Commands.Stage(repo, filePathB); + commit = repo.Commit("commit 4", Constants.Signature, Constants.Signature, new CommitOptions()); + + Touch(workdir, filePathB, string.Join(lineEnding, fileContentB1, fileContentB2, fileContentB3)); + Commands.Stage(repo, filePathB); + commit = repo.Commit("commit 5", Constants.Signature, Constants.Signature, new CommitOptions()); + + Touch(workdir, filePathB, string.Join(lineEnding, fileContentB1, fileContentB2, fileContentB3, fileContentB4)); + Commands.Stage(repo, filePathB); + commit = repo.Commit("commit 6", Constants.Signature, Constants.Signature, new CommitOptions()); + + repo.CreateBranch(topicBranch1Name, commit); + + Touch(workdir, filePathC, string.Join(lineEnding, fileContentC1, fileContentC2)); + Commands.Stage(repo, filePathC); + commit = repo.Commit("commit 7", Constants.Signature, Constants.Signature, new CommitOptions()); + + Touch(workdir, filePathC, string.Join(lineEnding, fileContentC1, fileContentC2, fileContentC3)); + Commands.Stage(repo, filePathC); + commit = repo.Commit("commit 8", Constants.Signature, Constants.Signature, new CommitOptions()); + + Touch(workdir, filePathC, string.Join(lineEnding, fileContentC1, fileContentC2, fileContentC3, fileContentC4)); + Commands.Stage(repo, filePathC); + commit = repo.Commit("commit 9", Constants.Signature, Constants.Signature, new CommitOptions()); + + repo.CreateBranch(topicBranch2Name, commit); + + Commands.Checkout(repo, masterBranch1.Tip); + Touch(workdir, filePathD, fileContentD1); + Commands.Stage(repo, filePathD); + commit = repo.Commit("commit 10", Constants.Signature, Constants.Signature, new CommitOptions()); + + Touch(workdir, filePathD, string.Join(lineEnding, fileContentD1, fileContentD2)); + Commands.Stage(repo, filePathD); + commit = repo.Commit("commit 11", Constants.Signature, Constants.Signature, new CommitOptions()); + + Touch(workdir, filePathD, string.Join(lineEnding, fileContentD1, fileContentD2, fileContentD3)); + Commands.Stage(repo, filePathD); + commit = repo.Commit("commit 12", Constants.Signature, Constants.Signature, new CommitOptions()); + + repo.CreateBranch(masterBranch2Name, commit); + + // Create commit / branch that conflicts with T1 and T2 + Touch(workdir, filePathB, string.Join(lineEnding, fileContentB1, fileContentB2 + fileContentB3 + fileContentB4)); + Commands.Stage(repo, filePathB); + commit = repo.Commit("commit 13", Constants.Signature, Constants.Signature, new CommitOptions()); + repo.CreateBranch(conflictBranch1Name, commit); + } + } +} diff --git a/LibGit2Sharp.Tests/RefSpecFixture.cs b/LibGit2Sharp.Tests/RefSpecFixture.cs index dc82a5fc3..50bf3343b 100644 --- a/LibGit2Sharp.Tests/RefSpecFixture.cs +++ b/LibGit2Sharp.Tests/RefSpecFixture.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System.Collections.Generic; +using System.Linq; using LibGit2Sharp.Tests.TestHelpers; using Xunit; using Xunit.Extensions; @@ -11,10 +12,10 @@ public class RefSpecFixture : BaseFixture public void CanCountRefSpecs() { var path = SandboxStandardTestRepo(); - using (var repo = InitIsolatedRepository(path)) + using (var repo = new Repository(path)) { var remote = repo.Network.Remotes["origin"]; - Assert.Equal(1, remote.RefSpecs.Count()); + Assert.Single(remote.RefSpecs); } } @@ -22,7 +23,7 @@ public void CanCountRefSpecs() public void CanIterateOverRefSpecs() { var path = SandboxStandardTestRepo(); - using (var repo = InitIsolatedRepository(path)) + using (var repo = new Repository(path)) { var remote = repo.Network.Remotes["origin"]; int count = 0; @@ -39,7 +40,7 @@ public void CanIterateOverRefSpecs() public void FetchAndPushRefSpecsComposeRefSpecs() { var path = SandboxStandardTestRepo(); - using (var repo = InitIsolatedRepository(path)) + using (var repo = new Repository(path)) { var remote = repo.Network.Remotes["origin"]; @@ -53,7 +54,7 @@ public void FetchAndPushRefSpecsComposeRefSpecs() public void CanReadRefSpecDetails() { var path = SandboxStandardTestRepo(); - using (var repo = InitIsolatedRepository(path)) + using (var repo = new Repository(path)) { var remote = repo.Network.Remotes["origin"]; @@ -62,7 +63,7 @@ public void CanReadRefSpecDetails() Assert.Equal("refs/heads/*", refSpec.Source); Assert.Equal("refs/remotes/origin/*", refSpec.Destination); - Assert.Equal(true, refSpec.ForceUpdate); + Assert.True(refSpec.ForceUpdate); } } @@ -73,27 +74,32 @@ public void CanReadRefSpecDetails() public void CanReplaceRefSpecs(string[] newFetchRefSpecs, string[] newPushRefSpecs) { var path = SandboxStandardTestRepo(); - using (var repo = InitIsolatedRepository(path)) + using (var repo = new Repository(path)) { - var remote = repo.Network.Remotes["origin"]; - var oldRefSpecs = remote.RefSpecs.ToList(); - - var newRemote = repo.Network.Remotes.Update(remote, - r => r.FetchRefSpecs = newFetchRefSpecs, r => r.PushRefSpecs = newPushRefSpecs); - - Assert.Equal(oldRefSpecs, remote.RefSpecs.ToList()); + List oldRefSpecs; + using (var remote = repo.Network.Remotes["origin"]) + { + oldRefSpecs = remote.RefSpecs.ToList(); - var actualNewFetchRefSpecs = newRemote.RefSpecs - .Where(s => s.Direction == RefSpecDirection.Fetch) - .Select(r => r.Specification) - .ToArray(); - Assert.Equal(newFetchRefSpecs, actualNewFetchRefSpecs); + repo.Network.Remotes.Update("origin", + r => r.FetchRefSpecs = newFetchRefSpecs, r => r.PushRefSpecs = newPushRefSpecs); + Assert.Equal(oldRefSpecs, remote.RefSpecs.ToList()); + } - var actualNewPushRefSpecs = newRemote.RefSpecs - .Where(s => s.Direction == RefSpecDirection.Push) - .Select(r => r.Specification) - .ToArray(); - Assert.Equal(newPushRefSpecs, actualNewPushRefSpecs); + using (var newRemote = repo.Network.Remotes["origin"]) + { + var actualNewFetchRefSpecs = newRemote.RefSpecs + .Where(s => s.Direction == RefSpecDirection.Fetch) + .Select(r => r.Specification) + .ToArray(); + Assert.Equal(newFetchRefSpecs, actualNewFetchRefSpecs); + + var actualNewPushRefSpecs = newRemote.RefSpecs + .Where(s => s.Direction == RefSpecDirection.Push) + .Select(r => r.Specification) + .ToArray(); + Assert.Equal(newPushRefSpecs, actualNewPushRefSpecs); + } } } @@ -101,17 +107,16 @@ public void CanReplaceRefSpecs(string[] newFetchRefSpecs, string[] newPushRefSpe public void RemoteUpdaterSavesRefSpecsPermanently() { var fetchRefSpecs = new string[] { "refs/their/heads/*:refs/my/heads/*", "+refs/their/tag:refs/my/tag" }; - var path = SandboxStandardTestRepo(); - using (var repo = InitIsolatedRepository(path)) + + using (var repo = new Repository(path)) { - var remote = repo.Network.Remotes["origin"]; - repo.Network.Remotes.Update(remote, r => r.FetchRefSpecs = fetchRefSpecs); + repo.Network.Remotes.Update("origin", r => r.FetchRefSpecs = fetchRefSpecs); } - using (var repo = InitIsolatedRepository(path)) + using (var repo = new Repository(path)) + using (var remote = repo.Network.Remotes["origin"]) { - var remote = repo.Network.Remotes["origin"]; var actualRefSpecs = remote.RefSpecs .Where(r => r.Direction == RefSpecDirection.Fetch) .Select(r => r.Specification) @@ -124,25 +129,29 @@ public void RemoteUpdaterSavesRefSpecsPermanently() public void CanAddAndRemoveRefSpecs() { string newRefSpec = "+refs/heads/test:refs/heads/other-test"; - var path = SandboxStandardTestRepo(); - using (var repo = InitIsolatedRepository(path)) - { - var remote = repo.Network.Remotes["origin"]; - remote = repo.Network.Remotes.Update(remote, + using (var repo = new Repository(path)) + { + repo.Network.Remotes.Update("origin", r => r.FetchRefSpecs.Add(newRefSpec), r => r.PushRefSpecs.Add(newRefSpec)); - Assert.Contains(newRefSpec, remote.FetchRefSpecs.Select(r => r.Specification)); - Assert.Contains(newRefSpec, remote.PushRefSpecs.Select(r => r.Specification)); + using (var remote = repo.Network.Remotes["origin"]) + { + Assert.Contains(newRefSpec, remote.FetchRefSpecs.Select(r => r.Specification)); + Assert.Contains(newRefSpec, remote.PushRefSpecs.Select(r => r.Specification)); + } - remote = repo.Network.Remotes.Update(remote, + repo.Network.Remotes.Update("origin", r => r.FetchRefSpecs.Remove(newRefSpec), r => r.PushRefSpecs.Remove(newRefSpec)); - Assert.DoesNotContain(newRefSpec, remote.FetchRefSpecs.Select(r => r.Specification)); - Assert.DoesNotContain(newRefSpec, remote.PushRefSpecs.Select(r => r.Specification)); + using (var remote = repo.Network.Remotes["origin"]) + { + Assert.DoesNotContain(newRefSpec, remote.FetchRefSpecs.Select(r => r.Specification)); + Assert.DoesNotContain(newRefSpec, remote.PushRefSpecs.Select(r => r.Specification)); + } } } @@ -150,20 +159,22 @@ public void CanAddAndRemoveRefSpecs() public void CanClearRefSpecs() { var path = SandboxStandardTestRepo(); - using (var repo = InitIsolatedRepository(path)) + using (var repo = new Repository(path)) { - var remote = repo.Network.Remotes["origin"]; // Push refspec does not exist in cloned repository - remote = repo.Network.Remotes.Update(remote, r => r.PushRefSpecs.Add("+refs/test:refs/test")); + repo.Network.Remotes.Update("origin", r => r.PushRefSpecs.Add("+refs/test:refs/test")); - remote = repo.Network.Remotes.Update(remote, + repo.Network.Remotes.Update("origin", r => r.FetchRefSpecs.Clear(), r => r.PushRefSpecs.Clear()); - Assert.Empty(remote.FetchRefSpecs); - Assert.Empty(remote.PushRefSpecs); - Assert.Empty(remote.RefSpecs); + using (var remote = repo.Network.Remotes["origin"]) + { + Assert.Empty(remote.FetchRefSpecs); + Assert.Empty(remote.PushRefSpecs); + Assert.Empty(remote.RefSpecs); + } } } @@ -178,17 +189,55 @@ public void CanClearRefSpecs() public void SettingInvalidRefSpecsThrows(string refSpec) { var path = SandboxStandardTestRepo(); - using (var repo = InitIsolatedRepository(path)) + using (var repo = new Repository(path)) { - var remote = repo.Network.Remotes["origin"]; - var oldRefSpecs = remote.RefSpecs.Select(r => r.Specification).ToList(); + IEnumerable oldRefSpecs; + using (var remote = repo.Network.Remotes["origin"]) + { + oldRefSpecs = remote.RefSpecs.Select(r => r.Specification).ToList(); + } Assert.Throws(() => - repo.Network.Remotes.Update(remote, r => r.FetchRefSpecs.Add(refSpec))); + 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 074326324..186d2e869 100644 --- a/LibGit2Sharp.Tests/ReferenceFixture.cs +++ b/LibGit2Sharp.Tests/ReferenceFixture.cs @@ -26,6 +26,8 @@ public void CanAddADirectReference() { EnableRefLog(repo); + var before = DateTimeOffset.Now.TruncateMilliseconds(); + var newRef = (DirectReference)repo.Refs.Add(name, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); Assert.NotNull(newRef); Assert.Equal(name, newRef.CanonicalName); @@ -36,7 +38,7 @@ public void CanAddADirectReference() AssertRefLogEntry(repo, name, "branch: Created from be3563ae3f795b2b4353bcce3a527ad0a4f7f644", - null, newRef.ResolveToDirectReference().Target.Id, Constants.Identity, DateTimeOffset.Now + null, newRef.ResolveToDirectReference().Target.Id, Constants.Identity, before ); } } @@ -52,6 +54,8 @@ public void CanAddADirectReferenceFromRevParseSpec() { 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); @@ -62,7 +66,7 @@ public void CanAddADirectReferenceFromRevParseSpec() AssertRefLogEntry(repo, name, logMessage, null, newRef.ResolveToDirectReference().Target.Id, - Constants.Identity, DateTimeOffset.Now); + Constants.Identity, before); } } @@ -157,6 +161,9 @@ public void CanAddAndOverwriteADirectReference() 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); @@ -167,7 +174,7 @@ public void CanAddAndOverwriteADirectReference() AssertRefLogEntry(repo, name, logMessage, ((DirectReference)oldRef).Target.Id, newRef.ResolveToDirectReference().Target.Id, - Constants.Identity, DateTimeOffset.Now); + Constants.Identity, before); } } @@ -184,6 +191,9 @@ public void CanAddAndOverwriteASymbolicReference() 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); @@ -194,7 +204,7 @@ public void CanAddAndOverwriteASymbolicReference() AssertRefLogEntry(repo, name, logMessage, oldtarget, newRef.ResolveToDirectReference().Target.Id, - Constants.Identity, DateTimeOffset.Now); + Constants.Identity, before); } } @@ -285,12 +295,12 @@ public void RemovingAReferenceDecreasesTheRefsCount() 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); } @@ -506,6 +516,8 @@ public void CanUpdateHeadWithEitherAnObjectIdOrAReference() Reference head = repo.Refs.Head; Reference test = repo.Refs["refs/heads/test"]; + 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); @@ -515,9 +527,12 @@ public void CanUpdateHeadWithEitherAnObjectIdOrAReference() AssertRefLogEntry(repo, "HEAD", null, head.ResolveToDirectReference().Target.Id, testTargetId, - Constants.Identity, DateTimeOffset.Now); + 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); @@ -527,7 +542,7 @@ public void CanUpdateHeadWithEitherAnObjectIdOrAReference() secondLogMessage, testTargetId, testTargetId, - Constants.Identity, DateTimeOffset.Now); + Constants.Identity, before); } } @@ -545,6 +560,9 @@ public void CanUpdateTargetOfADirectReferenceWithARevparseSpec() 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", logMessage); Assert.NotNull(newRef); Assert.Equal(name, newRef.CanonicalName); @@ -557,7 +575,7 @@ public void CanUpdateTargetOfADirectReferenceWithARevparseSpec() logMessage, @from, newRef.Target.Id, - Constants.Identity, DateTimeOffset.Now); + Constants.Identity, before); } } @@ -657,6 +675,8 @@ public void CanRenameAReferenceToADifferentReferenceHierarchy() var oldId = repo.Refs[oldName].ResolveToDirectReference().Target.Id; + var before = DateTimeOffset.Now.TruncateMilliseconds(); + Reference renamed = repo.Refs.Rename(oldName, newName); Assert.NotNull(renamed); Assert.Equal(newName, renamed.CanonicalName); @@ -666,7 +686,7 @@ public void CanRenameAReferenceToADifferentReferenceHierarchy() string.Format("reference: renamed {0} to {1}", oldName, newName), oldId, renamed.ResolveToDirectReference().Target.Id, - Constants.Identity, DateTimeOffset.Now); + Constants.Identity, before); } } @@ -716,13 +736,13 @@ public void RenamingAReferenceDoesNotDecreaseTheRefsCount() const string newName = "refs/atic/tagtest"; List refs = repo.Refs.Select(r => r.CanonicalName).ToList(); - Assert.True(refs.Contains(oldName)); + Assert.Contains(oldName, refs); repo.Refs.Rename(oldName, newName); List refs2 = repo.Refs.Select(r => r.CanonicalName).ToList(); - Assert.False(refs2.Contains(oldName)); - Assert.True(refs2.Contains(newName)); + Assert.DoesNotContain(oldName, refs2); + Assert.Contains(newName, refs2); Assert.Equal(refs2.Count, refs.Count); } @@ -754,7 +774,7 @@ public void CanFilterReferencesWithAGlob() Assert.Equal(5, repo.Refs.FromGlob("refs/heads/*").Count()); Assert.Equal(5, repo.Refs.FromGlob("refs/tags/*").Count()); Assert.Equal(3, repo.Refs.FromGlob("*t?[pqrs]t*").Count()); - Assert.Equal(0, repo.Refs.FromGlob("test").Count()); + Assert.Empty(repo.Refs.FromGlob("test")); } } @@ -809,15 +829,15 @@ 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()); + 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()); + Assert.True(repo.Refs["refs/notes/commits"].IsNote); } } @@ -851,7 +871,7 @@ public void CanQueryReachabilityAmongASubsetOfreferences() using (var repo = new Repository(path)) { var result = repo.Refs.ReachableFrom( - repo.Refs.Where(r => r.IsTag()), + repo.Refs.Where(r => r.IsTag), new[] { repo.Lookup("f8d44d7"), repo.Lookup("6dcf9bf") }); var expected = new[] diff --git a/LibGit2Sharp.Tests/ReflogFixture.cs b/LibGit2Sharp.Tests/ReflogFixture.cs index 88785d672..f93952e6e 100644 --- a/LibGit2Sharp.Tests/ReflogFixture.cs +++ b/LibGit2Sharp.Tests/ReflogFixture.cs @@ -23,7 +23,7 @@ public void CanReadReflog() // Initial commit assertions Assert.Equal("timothy.clem@gmail.com", reflog.Last().Committer.Email); - Assert.True(reflog.Last().Message.StartsWith("clone: from")); + Assert.StartsWith("clone: from", reflog.Last().Message); Assert.Equal(ObjectId.Zero, reflog.Last().From); // second commit assertions @@ -68,40 +68,42 @@ public void CommitShouldCreateReflogEntryOnHeadAndOnTargetedDirectReference() const string relativeFilepath = "new.txt"; Touch(repo.Info.WorkingDirectory, relativeFilepath, "content\n"); - repo.Stage(relativeFilepath); + Commands.Stage(repo, relativeFilepath); var author = Constants.Signature; const string commitMessage = "Hope reflog behaves as it should"; + + var before = DateTimeOffset.Now.TruncateMilliseconds(); + Commit commit = repo.Commit(commitMessage, author, author); // Assert a reflog entry is created on HEAD - Assert.Equal(1, repo.Refs.Log("HEAD").Count()); + Assert.Single(repo.Refs.Log("HEAD")); var reflogEntry = repo.Refs.Log("HEAD").First(); Assert.Equal(identity.Name, reflogEntry.Committer.Name); Assert.Equal(identity.Email, reflogEntry.Committer.Email); var now = DateTimeOffset.Now; - Assert.InRange(reflogEntry.Committer.When, now - TimeSpan.FromSeconds(1), now); + Assert.InRange(reflogEntry.Committer.When, before, now); Assert.Equal(commit.Id, reflogEntry.To); Assert.Equal(ObjectId.Zero, reflogEntry.From); // Assert the same reflog entry is created on refs/heads/master - Assert.Equal(1, repo.Refs.Log("refs/heads/master").Count()); + Assert.Single(repo.Refs.Log("refs/heads/master")); reflogEntry = repo.Refs.Log("HEAD").First(); Assert.Equal(identity.Name, reflogEntry.Committer.Name); Assert.Equal(identity.Email, reflogEntry.Committer.Email); - now = DateTimeOffset.Now; - Assert.InRange(reflogEntry.Committer.When, now - TimeSpan.FromSeconds(1), now); + Assert.InRange(reflogEntry.Committer.When, before, now); Assert.Equal(commit.Id, reflogEntry.To); Assert.Equal(ObjectId.Zero, reflogEntry.From); // Assert no reflog entry is created on refs/heads/unit_test - Assert.Equal(0, repo.Refs.Log("refs/heads/unit_test").Count()); + Assert.Empty(repo.Refs.Log("refs/heads/unit_test")); } } @@ -114,14 +116,14 @@ public void CommitOnUnbornReferenceShouldCreateReflogEntryWithInitialTag() { const string relativeFilepath = "new.txt"; Touch(repo.Info.WorkingDirectory, relativeFilepath, "content\n"); - repo.Stage(relativeFilepath); + Commands.Stage(repo, relativeFilepath); var author = Constants.Signature; const string commitMessage = "First commit should be logged as initial"; repo.Commit(commitMessage, author, author); // Assert the reflog entry message is correct - Assert.Equal(1, repo.Refs.Log("HEAD").Count()); + Assert.Single(repo.Refs.Log("HEAD")); Assert.Equal(string.Format("commit (initial): {0}", commitMessage), repo.Refs.Log("HEAD").First().Message); } } @@ -138,15 +140,18 @@ public void CommitOnDetachedHeadShouldInsertReflogEntry() Assert.False(repo.Info.IsHeadDetached); var parentCommit = repo.Head.Tip.Parents.First(); - repo.Checkout(parentCommit.Sha); + Commands.Checkout(repo, parentCommit.Sha); Assert.True(repo.Info.IsHeadDetached); const string relativeFilepath = "new.txt"; Touch(repo.Info.WorkingDirectory, relativeFilepath, "content\n"); - repo.Stage(relativeFilepath); + Commands.Stage(repo, relativeFilepath); var author = Constants.Signature; const string commitMessage = "Commit on detached head"; + + var before = DateTimeOffset.Now.TruncateMilliseconds(); + var commit = repo.Commit(commitMessage, author, author); // Assert a reflog entry is created on HEAD @@ -156,7 +161,7 @@ public void CommitOnDetachedHeadShouldInsertReflogEntry() Assert.Equal(identity.Email, reflogEntry.Committer.Email); var now = DateTimeOffset.Now; - Assert.InRange(reflogEntry.Committer.When, now - TimeSpan.FromSeconds(1), now); + Assert.InRange(reflogEntry.Committer.When, before, now); Assert.Equal(commit.Id, reflogEntry.To); Assert.Equal(string.Format("commit: {0}", commitMessage), repo.Refs.Log("HEAD").First().Message); @@ -206,8 +211,11 @@ public void UnsignedMethodsWriteCorrectlyToTheReflog() var commit = repo.ObjectDatabase.CreateCommit(Constants.Signature, Constants.Signature, "yoink", tree, Enumerable.Empty(), false); + var before = DateTimeOffset.Now.TruncateMilliseconds(); + var direct = repo.Refs.Add("refs/heads/direct", commit.Id); - AssertRefLogEntry(repo, direct.CanonicalName, null, null, direct.ResolveToDirectReference().Target.Id, Constants.Identity, DateTimeOffset.Now); + 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 8b8c81133..36990bb6e 100644 --- a/LibGit2Sharp.Tests/RemoteFixture.cs +++ b/LibGit2Sharp.Tests/RemoteFixture.cs @@ -66,10 +66,13 @@ public void CanSetTagFetchMode(TagFetchMode tagFetchMode) Remote remote = repo.Network.Remotes[name]; Assert.NotNull(remote); - Remote updatedremote = repo.Network.Remotes.Update(remote, + repo.Network.Remotes.Update(name, r => r.TagFetchMode = tagFetchMode); - Assert.Equal(tagFetchMode, updatedremote.TagFetchMode); + using (var updatedremote = repo.Network.Remotes[name]) + { + Assert.Equal(tagFetchMode, updatedremote.TagFetchMode); + } } } @@ -87,12 +90,14 @@ public void CanSetRemoteUrl() Remote remote = repo.Network.Remotes[name]; Assert.NotNull(remote); - Remote updatedremote = repo.Network.Remotes.Update(remote, - r => r.Url = newUrl); + repo.Network.Remotes.Update(name, r => r.Url = newUrl); - Assert.Equal(newUrl, updatedremote.Url); - // with no push url set, PushUrl defaults to the fetch url - Assert.Equal(newUrl, updatedremote.PushUrl); + using (var updatedremote = repo.Network.Remotes[name]) + { + Assert.Equal(newUrl, updatedremote.Url); + // with no push url set, PushUrl defaults to the fetch url + Assert.Equal(newUrl, updatedremote.PushUrl); + } } } @@ -114,34 +119,14 @@ public void CanSetRemotePushUrl() Assert.Equal(url, remote.Url); Assert.Equal(url, remote.PushUrl); - Remote updatedremote = repo.Network.Remotes.Update(remote, - r => r.PushUrl = pushurl); - - // url should not change, push url should be set to new value - Assert.Equal(url, updatedremote.Url); - Assert.Equal(pushurl, updatedremote.PushUrl); - } - } + repo.Network.Remotes.Update(name, r => r.PushUrl = pushurl); - [Fact] - public void CanCheckEqualityOfRemote() - { - string path = SandboxStandardTestRepo(); - 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); - - Remote createdRemote = repo.Network.Remotes.Add("origin2", oneOrigin.Url); - - Remote loadedRemote = repo.Network.Remotes["origin2"]; - Assert.NotNull(loadedRemote); - Assert.Equal(createdRemote, loadedRemote); - - Assert.NotEqual(oneOrigin, loadedRemote); + using (var updatedremote = repo.Network.Remotes[name]) + { + // url should not change, push url should be set to new value + Assert.Equal(url, updatedremote.Url); + Assert.Equal(pushurl, updatedremote.PushUrl); + } } } @@ -218,7 +203,7 @@ public void DoesNotThrowWhenARemoteHasNoUrlSet() { var noUrlRemote = repo.Network.Remotes["no_url"]; Assert.NotNull(noUrlRemote); - Assert.Equal(null, noUrlRemote.Url); + Assert.Null(noUrlRemote.Url); var remotes = repo.Network.Remotes.ToList(); Assert.Equal(1, remotes.Count(r => r.Name == "no_url")); @@ -314,8 +299,7 @@ public void ReportsRemotesWithNonDefaultRefSpecs() { Assert.NotNull(repo.Network.Remotes["origin"]); - repo.Network.Remotes.Update( - repo.Network.Remotes["origin"], + repo.Network.Remotes.Update("origin", r => r.FetchRefSpecs = new[] { "+refs/heads/*:refs/remotes/upstream/*" }); repo.Network.Remotes.Rename("origin", "nondefault", problem => Assert.Equal("+refs/heads/*:refs/remotes/upstream/*", problem)); @@ -377,9 +361,8 @@ public void CanNotRenameWhenRemoteWithSameNameExists() public void ShoudlPruneOnFetchReflectsTheConfiguredSetting(bool? fetchPrune, bool? remotePrune, bool expectedFetchPrune) { var path = SandboxStandardTestRepo(); - var scd = BuildSelfCleaningDirectory(); - using (var repo = new Repository(path, BuildFakeConfigs(scd))) + using (var repo = new Repository(path)) { Assert.Null(repo.Config.Get("fetch.prune")); Assert.Null(repo.Config.Get("remote.origin.prune")); diff --git a/LibGit2Sharp.Tests/RemoveFixture.cs b/LibGit2Sharp.Tests/RemoveFixture.cs index 94a376daf..e97636d9c 100644 --- a/LibGit2Sharp.Tests/RemoveFixture.cs +++ b/LibGit2Sharp.Tests/RemoveFixture.cs @@ -28,14 +28,14 @@ public class RemoveFixture : BaseFixture * '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, 0)] + [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, 0)] + [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. @@ -43,7 +43,7 @@ public class RemoveFixture : BaseFixture * '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, 0)] + [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) { @@ -59,12 +59,12 @@ public void CanRemoveAnUnalteredFileFromTheIndexWithoutRemovingItFromTheWorkingD if (throws) { - Assert.Throws(() => repo.Remove(filename, removeFromWorkdir)); + Assert.Throws(() => Commands.Remove(repo, filename, removeFromWorkdir)); Assert.Equal(count, repo.Index.Count); } else { - repo.Remove(filename, removeFromWorkdir); + Commands.Remove(repo, filename, removeFromWorkdir); Assert.Equal(count - 1, repo.Index.Count); Assert.Equal(existsAfterRemove, File.Exists(fullpath)); @@ -88,13 +88,13 @@ public void RemovingAModifiedFileWhoseChangesHaveBeenPromotedToTheIndexAndWithAd { string fullpath = Path.Combine(repo.Info.WorkingDirectory, filename); - Assert.Equal(true, File.Exists(fullpath)); + Assert.True(File.Exists(fullpath)); File.AppendAllText(fullpath, "additional content"); Assert.Equal(FileStatus.ModifiedInIndex | FileStatus.ModifiedInWorkdir, repo.RetrieveStatus(filename)); - Assert.Throws(() => repo.Remove(filename)); - Assert.Throws(() => repo.Remove(filename, false)); + Assert.Throws(() => Commands.Remove(repo, filename)); + Assert.Throws(() => Commands.Remove(repo, filename, false)); } } @@ -104,16 +104,16 @@ public void CanRemoveAFolderThroughUsageOfPathspecsForNewlyAddedFiles() string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { - repo.Stage(Touch(repo.Info.WorkingDirectory, "2/subdir1/2.txt", "whone")); - repo.Stage(Touch(repo.Info.WorkingDirectory, "2/subdir1/3.txt", "too")); - repo.Stage(Touch(repo.Info.WorkingDirectory, "2/subdir2/4.txt", "tree")); - repo.Stage(Touch(repo.Info.WorkingDirectory, "2/5.txt", "for")); - repo.Stage(Touch(repo.Info.WorkingDirectory, "2/6.txt", "fyve")); + Commands.Stage(repo, Touch(repo.Info.WorkingDirectory, "2/subdir1/2.txt", "whone")); + Commands.Stage(repo, Touch(repo.Info.WorkingDirectory, "2/subdir1/3.txt", "too")); + Commands.Stage(repo, Touch(repo.Info.WorkingDirectory, "2/subdir2/4.txt", "tree")); + Commands.Stage(repo, Touch(repo.Info.WorkingDirectory, "2/5.txt", "for")); + Commands.Stage(repo, Touch(repo.Info.WorkingDirectory, "2/6.txt", "fyve")); int count = repo.Index.Count; Assert.True(Directory.Exists(Path.Combine(repo.Info.WorkingDirectory, "2"))); - repo.Remove("2", false); + Commands.Remove(repo, "2", false); Assert.Equal(count - 5, repo.Index.Count); } @@ -128,7 +128,7 @@ public void CanRemoveAFolderThroughUsageOfPathspecsForFilesAlreadyInTheIndexAndI int count = repo.Index.Count; Assert.True(Directory.Exists(Path.Combine(repo.Info.WorkingDirectory, "1"))); - repo.Remove("1"); + Commands.Remove(repo, "1"); Assert.False(Directory.Exists(Path.Combine(repo.Info.WorkingDirectory, "1"))); Assert.Equal(count - 1, repo.Index.Count); @@ -148,8 +148,8 @@ public void RemovingAnUnknownFileWithLaxExplicitPathsValidationDoesntThrow(strin Assert.Null(repo.Index[relativePath]); Assert.Equal(status, repo.RetrieveStatus(relativePath)); - repo.Remove(relativePath, i % 2 == 0); - repo.Remove(relativePath, i % 2 == 0, + Commands.Remove(repo, relativePath, i % 2 == 0); + Commands.Remove(repo, relativePath, i % 2 == 0, new ExplicitPathsOptions {ShouldFailOnUnmatchedPath = false}); } } @@ -169,7 +169,7 @@ public void RemovingAnUnknownFileThrowsIfExplicitPath(string relativePath, FileS Assert.Equal(status, repo.RetrieveStatus(relativePath)); Assert.Throws( - () => repo.Remove(relativePath, i%2 == 0, new ExplicitPathsOptions())); + () => Commands.Remove(repo, relativePath, i%2 == 0, new ExplicitPathsOptions())); } } } @@ -180,10 +180,10 @@ public void RemovingFileWithBadParamsThrows() var path = SandboxStandardTestRepoGitDir(); using (var repo = new Repository(path)) { - Assert.Throws(() => repo.Remove(string.Empty)); - Assert.Throws(() => repo.Remove((string)null)); - Assert.Throws(() => repo.Remove(new string[] { })); - Assert.Throws(() => repo.Remove(new string[] { null })); + Assert.Throws(() => Commands.Remove(repo, string.Empty)); + Assert.Throws(() => Commands.Remove(repo, (string)null)); + Assert.Throws(() => Commands.Remove(repo, new string[] { })); + Assert.Throws(() => Commands.Remove(repo, new string[] { null })); } } } diff --git a/LibGit2Sharp.Tests/RepositoryFixture.cs b/LibGit2Sharp.Tests/RepositoryFixture.cs index 381973348..5c551fabd 100644 --- a/LibGit2Sharp.Tests/RepositoryFixture.cs +++ b/LibGit2Sharp.Tests/RepositoryFixture.cs @@ -4,6 +4,7 @@ using System.Linq; using LibGit2Sharp.Tests.TestHelpers; using Xunit; +using Xunit.Extensions; namespace LibGit2Sharp.Tests { @@ -26,6 +27,7 @@ public void CanCreateBareRepo() Assert.Null(repo.Info.WorkingDirectory); Assert.Equal(Path.GetFullPath(repoPath), repo.Info.Path); Assert.True(repo.Info.IsBare); + Assert.Throws(() => { var idx = repo.Index; }); AssertInitializedRepository(repo, "refs/heads/master"); @@ -56,6 +58,26 @@ public void CanCheckIfADirectoryLeadsToAValidRepository() Assert.False(Repository.IsValid(scd.DirectoryPath)); } + + [Fact] + public void IsValidWithNullPathThrows() + { + Assert.Throws(() => Repository.IsValid(null)); + } + + [Fact] + public void IsNotValidWithEmptyPath() + { + Assert.False(Repository.IsValid(string.Empty)); + } + + [Fact] + public void IsValidWithValidPath() + { + string repoPath = InitNewRepository(); + Assert.True(Repository.IsValid(repoPath)); + } + [Fact] public void CanCreateStandardRepo() { @@ -101,7 +123,7 @@ public void CanCreateStandardRepoAndSpecifyAFolderWhichWillContainTheNewlyCreate Assert.True(Repository.IsValid(repo.Info.WorkingDirectory)); Assert.True(Repository.IsValid(repo.Info.Path)); - Assert.Equal(false, repo.Info.IsBare); + Assert.False(repo.Info.IsBare); char sep = Path.DirectorySeparatorChar; Assert.Equal(scd1.RootedDirectoryPath + sep, repo.Info.WorkingDirectory); @@ -126,7 +148,7 @@ public void CanCreateStandardRepoAndDirectlySpecifyAGitDirectory() Assert.True(Repository.IsValid(repo.Info.WorkingDirectory)); Assert.True(Repository.IsValid(repo.Info.Path)); - Assert.Equal(false, repo.Info.IsBare); + Assert.False(repo.Info.IsBare); char sep = Path.DirectorySeparatorChar; Assert.Equal(scd1.RootedDirectoryPath + sep, repo.Info.WorkingDirectory); @@ -145,6 +167,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)); @@ -160,7 +186,7 @@ public void CanFetchFromRemoteByName() using (var repo = new Repository(repoPath)) { - Remote remote = repo.Network.Remotes.Add(remoteName, url); + repo.Network.Remotes.Add(remoteName, url); // We will first fetch without specifying any Tag options. // After we verify this fetch, we will perform a second fetch @@ -187,13 +213,13 @@ public void CanFetchFromRemoteByName() } // Perform the actual fetch - repo.Fetch(remote.Name, new FetchOptions { OnUpdateTips = expectedFetchState.RemoteUpdateTipsHandler }); + Commands.Fetch(repo, remoteName, new string[0], new FetchOptions { OnUpdateTips = expectedFetchState.RemoteUpdateTipsHandler }, null); // Verify the expected state expectedFetchState.CheckUpdatedReferences(repo); // Now fetch the rest of the tags - repo.Fetch(remote.Name, new FetchOptions { TagFetchMode = TagFetchMode.All }); + Commands.Fetch(repo, remoteName, new string[0], new FetchOptions { TagFetchMode = TagFetchMode.All }, null); // Verify that the "nearly-dangling" tag is now in the repo. Tag nearlyDanglingTag = repo.Tags["nearly-dangling"]; @@ -241,18 +267,18 @@ private static void AssertInitializedRepository(IRepository repo, string expecte Assert.Equal(headRef.TargetIdentifier, repo.Head.CanonicalName); Assert.Null(repo.Head.Tip); - Assert.Equal(0, repo.Commits.Count()); - Assert.Equal(0, repo.Commits.QueryBy(new CommitFilter()).Count()); - Assert.Equal(0, repo.Commits.QueryBy(new CommitFilter { Since = repo.Refs.Head }).Count()); - Assert.Equal(0, repo.Commits.QueryBy(new CommitFilter { Since = repo.Head }).Count()); - Assert.Equal(0, repo.Commits.QueryBy(new CommitFilter { Since = "HEAD" }).Count()); - Assert.Equal(0, repo.Commits.QueryBy(new CommitFilter { Since = expectedHeadTargetIdentifier }).Count()); + Assert.Empty(repo.Commits); + Assert.Empty(repo.Commits.QueryBy(new CommitFilter())); + Assert.Empty(repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = repo.Refs.Head })); + Assert.Empty(repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = repo.Head })); + Assert.Empty(repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = "HEAD" })); + Assert.Empty(repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = expectedHeadTargetIdentifier })); Assert.Null(repo.Head["subdir/I-do-not-exist"]); - Assert.Equal(0, repo.Branches.Count()); - Assert.Equal(0, repo.Refs.Count()); - Assert.Equal(0, repo.Tags.Count()); + Assert.Empty(repo.Branches); + Assert.Empty(repo.Refs); + Assert.Empty(repo.Tags); } [Fact] @@ -422,7 +448,7 @@ public void CanLookupWhithShortIdentifers() { const string filename = "new.txt"; Touch(repo.Info.WorkingDirectory, filename, "one "); - repo.Stage(filename); + Commands.Stage(repo, filename); Signature author = Constants.Signature; Commit commit = repo.Commit("Initial commit", author, author); @@ -589,9 +615,9 @@ public void QueryingTheRemoteForADetachedHeadBranchReturnsNull() string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { - repo.Checkout(repo.Head.Tip.Sha, new CheckoutOptions() { CheckoutModifiers = CheckoutModifiers.Force }); + Commands.Checkout(repo, repo.Head.Tip.Sha, new CheckoutOptions() { CheckoutModifiers = CheckoutModifiers.Force }); Branch trackLocal = repo.Head; - Assert.Null(trackLocal.Remote); + Assert.Null(trackLocal.RemoteName); } } @@ -651,5 +677,105 @@ public void CanDetectShallowness() 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")] + [InlineData("git://github.com/libgit2/TestGitRepository.git")] + 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 496cf3029..707e0ecae 100644 --- a/LibGit2Sharp.Tests/RepositoryOptionsFixture.cs +++ b/LibGit2Sharp.Tests/RepositoryOptionsFixture.cs @@ -55,7 +55,7 @@ public void CanOpenABareRepoAsIfItWasAStandardOneWithANonExisitingIndexFile() [Fact] public void CanOpenABareRepoWithOptions() { - var options = new RepositoryOptions { GlobalConfigurationLocation = null }; + var options = new RepositoryOptions { }; string path = SandboxBareTestRepo(); using (var repo = new Repository(path, options)) @@ -90,7 +90,7 @@ public void CanProvideADifferentIndexToAStandardRepo() { Assert.Equal(FileStatus.NewInWorkdir, repo.RetrieveStatus("new_untracked_file.txt")); - repo.Stage("new_untracked_file.txt"); + Commands.Stage(repo, "new_untracked_file.txt"); Assert.Equal(FileStatus.NewInIndex, repo.RetrieveStatus("new_untracked_file.txt")); @@ -148,50 +148,11 @@ private string MeanwhileInAnotherDimensionAnEvilMastermindIsAtWork(string workin const string filename = "zomg.txt"; Touch(sneakyRepo.Info.WorkingDirectory, filename, "I'm being sneaked in!\n"); - sneakyRepo.Stage(filename); + Commands.Stage(sneakyRepo, filename); return sneakyRepo.Commit("Tadaaaa!", Constants.Signature, Constants.Signature).Sha; } } - [Fact] - public void CanProvideDifferentConfigurationFilesToARepository() - { - string globalLocation = Path.Combine(newWorkdir, "my-global-config"); - string xdgLocation = Path.Combine(newWorkdir, "my-xdg-config"); - string systemLocation = Path.Combine(newWorkdir, "my-system-config"); - - const string name = "Adam 'aroben' Roben"; - const string email = "adam@github.com"; - - StringBuilder sb = new StringBuilder() - .AppendLine("[user]") - .AppendFormat("name = {0}{1}", name, Environment.NewLine) - .AppendFormat("email = {0}{1}", email, Environment.NewLine); - - File.WriteAllText(globalLocation, sb.ToString()); - File.WriteAllText(systemLocation, string.Empty); - File.WriteAllText(xdgLocation, string.Empty); - - var options = new RepositoryOptions { - GlobalConfigurationLocation = globalLocation, - XdgConfigurationLocation = xdgLocation, - SystemConfigurationLocation = systemLocation, - }; - - string path = SandboxBareTestRepo(); - using (var repo = new Repository(path, options)) - { - Assert.True(repo.Config.HasConfig(ConfigurationLevel.Global)); - Assert.Equal(name, repo.Config.Get("user.name").Value); - Assert.Equal(email, repo.Config.Get("user.email").Value); - - repo.Config.Set("xdg.setting", "https://twitter.com/libgit2sharp", ConfigurationLevel.Xdg); - repo.Config.Set("help.link", "https://twitter.com/xpaulbettsx/status/205761932626636800", ConfigurationLevel.System); - } - - AssertValueInConfigFile(systemLocation, "xpaulbettsx"); - } - [Fact] public void CanCommitOnBareRepository() { @@ -210,11 +171,11 @@ public void CanCommitOnBareRepository() { const string relativeFilepath = "test.txt"; Touch(repo.Info.WorkingDirectory, relativeFilepath, "test\n"); - repo.Stage(relativeFilepath); + Commands.Stage(repo, relativeFilepath); Assert.NotNull(repo.Commit("Initial commit", Constants.Signature, Constants.Signature)); - Assert.Equal(1, repo.Head.Commits.Count()); - Assert.Equal(1, repo.Commits.Count()); + Assert.Single(repo.Head.Commits); + Assert.Single(repo.Commits); } } } diff --git a/LibGit2Sharp.Tests/ResetHeadFixture.cs b/LibGit2Sharp.Tests/ResetHeadFixture.cs index 4f6921914..83a7efcb9 100644 --- a/LibGit2Sharp.Tests/ResetHeadFixture.cs +++ b/LibGit2Sharp.Tests/ResetHeadFixture.cs @@ -102,7 +102,7 @@ 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); @@ -110,6 +110,8 @@ private void AssertSoftReset(Func branchIdentifierRetriever, boo Assert.Equal(expectedHeadName, repo.Head.FriendlyName); Assert.Equal(branch.Tip.Sha, repo.Head.Tip.Sha); + var before = DateTimeOffset.Now.TruncateMilliseconds(); + /* Reset --soft the Head to a tag through its canonical name */ repo.Reset(ResetMode.Soft, tag.CanonicalName); Assert.Equal(expectedHeadName, repo.Head.FriendlyName); @@ -121,7 +123,7 @@ private void AssertSoftReset(Func branchIdentifierRetriever, boo string.Format("reset: moving to {0}", tag.Target.Sha), oldHeadId, tag.Target.Id, - Constants.Identity, DateTimeOffset.Now); + Constants.Identity, before); if (!shouldHeadBeDetached) { @@ -129,9 +131,11 @@ private void AssertSoftReset(Func branchIdentifierRetriever, boo string.Format("reset: moving to {0}", tag.Target.Sha), oldHeadId, tag.Target.Id, - Constants.Identity, DateTimeOffset.Now); + Constants.Identity, before); } + before = DateTimeOffset.Now.TruncateMilliseconds(); + /* Reset --soft the Head to a commit through its sha */ repo.Reset(ResetMode.Soft, branch.Tip.Sha); Assert.Equal(expectedHeadName, repo.Head.FriendlyName); @@ -143,7 +147,7 @@ private void AssertSoftReset(Func branchIdentifierRetriever, boo string.Format("reset: moving to {0}", branch.Tip.Sha), tag.Target.Id, branch.Tip.Id, - Constants.Identity, DateTimeOffset.Now); + Constants.Identity, before); if (!shouldHeadBeDetached) { @@ -151,7 +155,7 @@ private void AssertSoftReset(Func branchIdentifierRetriever, boo string.Format("reset: moving to {0}", branch.Tip.Sha), tag.Target.Id, branch.Tip.Id, - Constants.Identity, DateTimeOffset.Now); + Constants.Identity, before); } } } @@ -159,18 +163,18 @@ private void AssertSoftReset(Func branchIdentifierRetriever, boo private static void FeedTheRepository(IRepository repo) { string fullPath = Touch(repo.Info.WorkingDirectory, "a.txt", "Hello\n"); - repo.Stage(fullPath); + Commands.Stage(repo, fullPath); repo.Commit("Initial commit", Constants.Signature, Constants.Signature); repo.ApplyTag("mytag"); File.AppendAllText(fullPath, "World\n"); - repo.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.RetrieveStatus().IsDirty); } @@ -188,6 +192,8 @@ public void MixedResetRefreshesTheIndex() Tag tag = repo.Tags["mytag"]; + var before = DateTimeOffset.Now.TruncateMilliseconds(); + repo.Reset(ResetMode.Mixed, tag.CanonicalName); Assert.Equal(FileStatus.ModifiedInWorkdir, repo.RetrieveStatus("a.txt")); @@ -196,13 +202,13 @@ public void MixedResetRefreshesTheIndex() string.Format("reset: moving to {0}", tag.Target.Sha), oldHeadId, tag.Target.Id, - Constants.Identity, DateTimeOffset.Now); + Constants.Identity, before); AssertRefLogEntry(repo, "refs/heads/mybranch", string.Format("reset: moving to {0}", tag.Target.Sha), oldHeadId, tag.Target.Id, - Constants.Identity, DateTimeOffset.Now); + Constants.Identity, before); } } @@ -229,6 +235,8 @@ public void HardResetInABareRepositoryThrows() [Fact] public void HardResetUpdatesTheContentOfTheWorkingDirectory() { + bool progressCalled = false; + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { @@ -239,11 +247,16 @@ public void HardResetUpdatesTheContentOfTheWorkingDirectory() Assert.True(names.Count > 4); - repo.Reset(ResetMode.Hard, "HEAD~3"); + var commit = repo.Lookup("HEAD~3"); + repo.Reset(ResetMode.Hard, commit, new CheckoutOptions() + { + OnCheckoutProgress = (_path, _completed, _total) => { progressCalled = true; }, + }); names = new DirectoryInfo(repo.Info.WorkingDirectory).GetFileSystemInfos().Select(fsi => fsi.Name).ToList(); names.Sort(StringComparer.Ordinal); + Assert.True(progressCalled); Assert.Equal(new[] { ".git", "README", "WillNotBeRemoved.txt", "branch_file.txt", "new.txt", "new_untracked_file.txt" }, names); } } diff --git a/LibGit2Sharp.Tests/ResetIndexFixture.cs b/LibGit2Sharp.Tests/ResetIndexFixture.cs index 082566218..97a1eef88 100644 --- a/LibGit2Sharp.Tests/ResetIndexFixture.cs +++ b/LibGit2Sharp.Tests/ResetIndexFixture.cs @@ -62,7 +62,7 @@ public void ResetTheIndexWithTheHeadUnstagesEverything() repo.Index.Replace(repo.Head.Tip); RepositoryStatus newStatus = repo.RetrieveStatus(); - Assert.Equal(0, newStatus.Where(IsStaged).Count()); + Assert.Empty(newStatus.Where(IsStaged)); // Assert that no reflog entry is created Assert.Equal(reflogEntriesCount, repo.Refs.Log(repo.Refs.Head).Count()); @@ -79,7 +79,7 @@ public void CanResetTheIndexToTheContentOfACommitWithCommitAsArgument() 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()); @@ -116,11 +116,11 @@ public void CanResetTheIndexWhenARenameExists() { using (var repo = new Repository(SandboxStandardTestRepo())) { - repo.Move("branch_file.txt", "renamed_branch_file.txt"); + Commands.Move(repo, "branch_file.txt", "renamed_branch_file.txt"); repo.Index.Replace(repo.Lookup("32eab9c")); RepositoryStatus status = repo.RetrieveStatus(); - Assert.Equal(0, status.Where(IsStaged).Count()); + Assert.Empty(status.Where(IsStaged)); } } @@ -129,17 +129,17 @@ public void CanResetSourceOfARenameInIndex() { using (var repo = new Repository(SandboxStandardTestRepo())) { - repo.Move("branch_file.txt", "renamed_branch_file.txt"); + Commands.Move(repo, "branch_file.txt", "renamed_branch_file.txt"); RepositoryStatus oldStatus = repo.RetrieveStatus(); - Assert.Equal(1, oldStatus.RenamedInIndex.Count()); + 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" }); RepositoryStatus newStatus = repo.RetrieveStatus(); - Assert.Equal(0, newStatus.RenamedInIndex.Count()); + Assert.Empty(newStatus.RenamedInIndex); Assert.Equal(FileStatus.DeletedFromWorkdir, newStatus["branch_file.txt"].State); Assert.Equal(FileStatus.NewInIndex, newStatus["renamed_branch_file.txt"].State); } @@ -150,16 +150,16 @@ public void CanResetTargetOfARenameInIndex() { using (var repo = new Repository(SandboxStandardTestRepo())) { - repo.Move("branch_file.txt", "renamed_branch_file.txt"); + Commands.Move(repo, "branch_file.txt", "renamed_branch_file.txt"); RepositoryStatus oldStatus = repo.RetrieveStatus(); - Assert.Equal(1, oldStatus.RenamedInIndex.Count()); + 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.Equal(0, newStatus.RenamedInIndex.Count()); + Assert.Empty(newStatus.RenamedInIndex); Assert.Equal(FileStatus.NewInWorkdir, newStatus["renamed_branch_file.txt"].State); Assert.Equal(FileStatus.DeletedFromIndex, newStatus["branch_file.txt"].State); } diff --git a/LibGit2Sharp.Tests/Resources/.gitattributes b/LibGit2Sharp.Tests/Resources/.gitattributes index 23d9bdb29..48eb3141c 100644 --- a/LibGit2Sharp.Tests/Resources/.gitattributes +++ b/LibGit2Sharp.Tests/Resources/.gitattributes @@ -1 +1,4 @@ * binary +.gitattributes diff +.gitignore diff +config diff diff --git a/LibGit2Sharp.Tests/Resources/.gitignore b/LibGit2Sharp.Tests/Resources/.gitignore index 29ebe04b9..6b70b2273 100644 --- a/LibGit2Sharp.Tests/Resources/.gitignore +++ b/LibGit2Sharp.Tests/Resources/.gitignore @@ -1 +1,6 @@ *.sample +COMMIT_EDITMSG +exclude +logs/ +!testrepo_wd/dot_git/logs/ +description diff --git a/LibGit2Sharp.Tests/Resources/assume_unchanged_wd/dot_git/logs/HEAD b/LibGit2Sharp.Tests/Resources/assume_unchanged_wd/dot_git/logs/HEAD deleted file mode 100644 index 8a423a7ae..000000000 --- a/LibGit2Sharp.Tests/Resources/assume_unchanged_wd/dot_git/logs/HEAD +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 872129051d644790636b416d1ef1ec830c5f6b90 nulltoken 1422023333 +0100 commit (initial): Initial commit diff --git a/LibGit2Sharp.Tests/Resources/assume_unchanged_wd/dot_git/logs/refs/heads/master b/LibGit2Sharp.Tests/Resources/assume_unchanged_wd/dot_git/logs/refs/heads/master deleted file mode 100644 index 8a423a7ae..000000000 --- a/LibGit2Sharp.Tests/Resources/assume_unchanged_wd/dot_git/logs/refs/heads/master +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 872129051d644790636b416d1ef1ec830c5f6b90 nulltoken 1422023333 +0100 commit (initial): Initial commit diff --git a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/description b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/description deleted file mode 100644 index 498b267a8..000000000 --- a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/description +++ /dev/null @@ -1 +0,0 @@ -Unnamed repository; edit this file 'description' to name the repository. diff --git a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/index b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/index index abbebf181..1d91f96f1 100644 Binary files a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/index and b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/index differ diff --git a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/logs/HEAD b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/logs/HEAD deleted file mode 100644 index 9e2a5c74a..000000000 --- a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/logs/HEAD +++ /dev/null @@ -1,9 +0,0 @@ -0000000000000000000000000000000000000000 a3ccc66533922b02858da9ed489cd19d573d6378 Jameson Miller 1393902455 -0500 commit (initial): Initial commit -a3ccc66533922b02858da9ed489cd19d573d6378 83cebf5389a4adbcb80bda6b68513caee4559802 Jameson Miller 1393902574 -0500 commit: Second commit on master -83cebf5389a4adbcb80bda6b68513caee4559802 83cebf5389a4adbcb80bda6b68513caee4559802 Jameson Miller 1393902595 -0500 checkout: moving from master to fast_forward -83cebf5389a4adbcb80bda6b68513caee4559802 4dfaa1500526214ae7b33f9b2c1144ca8b6b1f53 Jameson Miller 1393902643 -0500 commit: Commit on fast_forward -4dfaa1500526214ae7b33f9b2c1144ca8b6b1f53 a3ccc66533922b02858da9ed489cd19d573d6378 Jameson Miller 1393902655 -0500 checkout: moving from fast_forward to normal_merge -a3ccc66533922b02858da9ed489cd19d573d6378 625186280ed2a6ec9b65d250ed90cf2e4acef957 Jameson Miller 1393902725 -0500 commit: Commit on normal_merge -625186280ed2a6ec9b65d250ed90cf2e4acef957 a3ccc66533922b02858da9ed489cd19d573d6378 Jameson Miller 1393902735 -0500 checkout: moving from normal_merge to conflicts -a3ccc66533922b02858da9ed489cd19d573d6378 0771966a0d112d3787006a6fda5b6226a770e15a Jameson Miller 1393902760 -0500 commit: Commit on conflicts -0771966a0d112d3787006a6fda5b6226a770e15a 83cebf5389a4adbcb80bda6b68513caee4559802 Jameson Miller 1393907470 -0500 checkout: moving from conflicts to master diff --git a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/logs/refs/heads/conflicts b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/logs/refs/heads/conflicts deleted file mode 100644 index bf17c1056..000000000 --- a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/logs/refs/heads/conflicts +++ /dev/null @@ -1,2 +0,0 @@ -0000000000000000000000000000000000000000 a3ccc66533922b02858da9ed489cd19d573d6378 Jameson Miller 1393902539 -0500 branch: Created from master -a3ccc66533922b02858da9ed489cd19d573d6378 0771966a0d112d3787006a6fda5b6226a770e15a Jameson Miller 1393902760 -0500 commit: Commit on conflicts diff --git a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/logs/refs/heads/fast_forward b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/logs/refs/heads/fast_forward deleted file mode 100644 index 9b3d80c28..000000000 --- a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/logs/refs/heads/fast_forward +++ /dev/null @@ -1,2 +0,0 @@ -0000000000000000000000000000000000000000 83cebf5389a4adbcb80bda6b68513caee4559802 Jameson Miller 1393902591 -0500 branch: Created from master -83cebf5389a4adbcb80bda6b68513caee4559802 4dfaa1500526214ae7b33f9b2c1144ca8b6b1f53 Jameson Miller 1393902643 -0500 commit: Commit on fast_forward diff --git a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/logs/refs/heads/master b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/logs/refs/heads/master deleted file mode 100644 index 8201a1408..000000000 --- a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/logs/refs/heads/master +++ /dev/null @@ -1,2 +0,0 @@ -0000000000000000000000000000000000000000 a3ccc66533922b02858da9ed489cd19d573d6378 Jameson Miller 1393902455 -0500 commit (initial): Initial commit -a3ccc66533922b02858da9ed489cd19d573d6378 83cebf5389a4adbcb80bda6b68513caee4559802 Jameson Miller 1393902574 -0500 commit: Second commit on master diff --git a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/logs/refs/heads/normal_merge b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/logs/refs/heads/normal_merge deleted file mode 100644 index e17413bdb..000000000 --- a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/logs/refs/heads/normal_merge +++ /dev/null @@ -1,2 +0,0 @@ -0000000000000000000000000000000000000000 a3ccc66533922b02858da9ed489cd19d573d6378 Jameson Miller 1393902530 -0500 branch: Created from master -a3ccc66533922b02858da9ed489cd19d573d6378 625186280ed2a6ec9b65d250ed90cf2e4acef957 Jameson Miller 1393902725 -0500 commit: Commit on normal_merge diff --git a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/logs/refs/heads/rename b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/logs/refs/heads/rename deleted file mode 100644 index a2466a9d2..000000000 --- a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/logs/refs/heads/rename +++ /dev/null @@ -1,3 +0,0 @@ -0000000000000000000000000000000000000000 83cebf5389a4adbcb80bda6b68513caee4559802 Jameson Miller 1398366152 -0400 branch: Created from HEAD -83cebf5389a4adbcb80bda6b68513caee4559802 9075c06ff9cd736610dea688bca7e912903ff2d1 Jameson Miller 1398366254 -0400 commit: Add content to b.txt -9075c06ff9cd736610dea688bca7e912903ff2d1 24434077dec097c1203ef9e1345c0545c190936a Jameson Miller 1398366641 -0400 commit: edit to b.txt diff --git a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/logs/refs/heads/rename_source b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/logs/refs/heads/rename_source deleted file mode 100644 index 1515dd26e..000000000 --- a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/logs/refs/heads/rename_source +++ /dev/null @@ -1,2 +0,0 @@ -0000000000000000000000000000000000000000 9075c06ff9cd736610dea688bca7e912903ff2d1 Jameson Miller 1398366267 -0400 branch: Created from rename -9075c06ff9cd736610dea688bca7e912903ff2d1 2bc71d0e8acfbb9fd1cc2d9d48c23dbf8aea52c9 Jameson Miller 1398366593 -0400 commit: rename and edit b.txt diff --git a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/09/55f5468a8e493c0116872fa3e1807559bd5bb2 b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/09/55f5468a8e493c0116872fa3e1807559bd5bb2 new file mode 100644 index 000000000..77e845bdc Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/09/55f5468a8e493c0116872fa3e1807559bd5bb2 differ diff --git a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/1e/b39b79963b9acb87bb1b76a218d862b689f4cd b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/1e/b39b79963b9acb87bb1b76a218d862b689f4cd new file mode 100644 index 000000000..81a42108b Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/1e/b39b79963b9acb87bb1b76a218d862b689f4cd differ diff --git a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/79/853dbb13f5e83a1e9e69bf747c5a667c21d420 b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/79/853dbb13f5e83a1e9e69bf747c5a667c21d420 new file mode 100644 index 000000000..5eba30ecd Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/79/853dbb13f5e83a1e9e69bf747c5a667c21d420 differ diff --git a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/96/adea09cb7c3e9e81e488fbf0bc29e61d8100be b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/96/adea09cb7c3e9e81e488fbf0bc29e61d8100be new file mode 100644 index 000000000..12b03deec Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/96/adea09cb7c3e9e81e488fbf0bc29e61d8100be differ diff --git a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/cf/2badd0f483ffe19755560e6f736f1b4621f737 b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/cf/2badd0f483ffe19755560e6f736f1b4621f737 new file mode 100644 index 000000000..501f4f309 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/cf/2badd0f483ffe19755560e6f736f1b4621f737 differ diff --git a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/e5/5b31220c73a5535ba58709791880f3035849d4 b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/e5/5b31220c73a5535ba58709791880f3035849d4 new file mode 100644 index 000000000..a10105db5 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/e5/5b31220c73a5535ba58709791880f3035849d4 differ diff --git a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/refs/heads/conflicts_spaces b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/refs/heads/conflicts_spaces new file mode 100644 index 000000000..49be02a92 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/refs/heads/conflicts_spaces @@ -0,0 +1 @@ +79853dbb13f5e83a1e9e69bf747c5a667c21d420 diff --git a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/refs/heads/conflicts_tabs b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/refs/heads/conflicts_tabs new file mode 100644 index 000000000..8a2211064 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/refs/heads/conflicts_tabs @@ -0,0 +1 @@ +e55b31220c73a5535ba58709791880f3035849d4 diff --git a/LibGit2Sharp.Tests/Resources/mergedrepo_wd/dot_git/config b/LibGit2Sharp.Tests/Resources/mergedrepo_wd/dot_git/config index 78387c50b..277bd5530 100644 --- a/LibGit2Sharp.Tests/Resources/mergedrepo_wd/dot_git/config +++ b/LibGit2Sharp.Tests/Resources/mergedrepo_wd/dot_git/config @@ -6,3 +6,4 @@ symlinks = false ignorecase = true hideDotFiles = dotGitOnly + autocrlf = false diff --git a/LibGit2Sharp.Tests/Resources/mergedrepo_wd/dot_git/description b/LibGit2Sharp.Tests/Resources/mergedrepo_wd/dot_git/description deleted file mode 100644 index 498b267a8..000000000 --- a/LibGit2Sharp.Tests/Resources/mergedrepo_wd/dot_git/description +++ /dev/null @@ -1 +0,0 @@ -Unnamed repository; edit this file 'description' to name the repository. diff --git a/LibGit2Sharp.Tests/Resources/mergedrepo_wd/dot_git/logs/HEAD b/LibGit2Sharp.Tests/Resources/mergedrepo_wd/dot_git/logs/HEAD deleted file mode 100644 index 839dfc811..000000000 --- a/LibGit2Sharp.Tests/Resources/mergedrepo_wd/dot_git/logs/HEAD +++ /dev/null @@ -1,15 +0,0 @@ -0000000000000000000000000000000000000000 9107b30fe11ff310fec93876469147b263fb958c Edward Thomson 1360632480 -0600 commit (initial): ancestor -9107b30fe11ff310fec93876469147b263fb958c 9107b30fe11ff310fec93876469147b263fb958c Edward Thomson 1360632483 -0600 checkout: moving from master to branch -9107b30fe11ff310fec93876469147b263fb958c d4c107581ff54a41fd496e0a1f1b7b3354752103 Edward Thomson 1360632833 -0600 commit: branch -d4c107581ff54a41fd496e0a1f1b7b3354752103 9107b30fe11ff310fec93876469147b263fb958c Edward Thomson 1360632835 -0600 checkout: moving from branch to master -9107b30fe11ff310fec93876469147b263fb958c 8b19fd77de5995e6e502e7c3b82657248f510325 Edward Thomson 1360633224 -0600 commit: ours -8b19fd77de5995e6e502e7c3b82657248f510325 91116bad97c50a2daa278ca6cffe16d0118befe5 Edward Thomson 1360633337 -0600 commit: ours -91116bad97c50a2daa278ca6cffe16d0118befe5 8b19fd77de5995e6e502e7c3b82657248f510325 Edward Thomson 1360633358 -0600 checkout: moving from master to 8b19fd7 -8b19fd77de5995e6e502e7c3b82657248f510325 9b2f98b1c086d3929eac387d39fae94425e0115f Edward Thomson 1360633359 -0600 rebase -i (squash): ours -9b2f98b1c086d3929eac387d39fae94425e0115f 9b2f98b1c086d3929eac387d39fae94425e0115f Edward Thomson 1360633362 -0600 rebase -i (finish): returning to refs/heads/master -9b2f98b1c086d3929eac387d39fae94425e0115f d4c107581ff54a41fd496e0a1f1b7b3354752103 Edward Thomson 1360633367 -0600 checkout: moving from master to branch -d4c107581ff54a41fd496e0a1f1b7b3354752103 493209f496e504d63fd78e8c5b800083b442ea34 Edward Thomson 1360633416 -0600 commit: branch -493209f496e504d63fd78e8c5b800083b442ea34 d4c107581ff54a41fd496e0a1f1b7b3354752103 Edward Thomson 1360633427 -0600 checkout: moving from branch to d4c1075 -d4c107581ff54a41fd496e0a1f1b7b3354752103 8eefb6f8061e4d10a7a59aac8771dc17958d93eb Edward Thomson 1360633429 -0600 rebase -i (squash): branch -8eefb6f8061e4d10a7a59aac8771dc17958d93eb 8eefb6f8061e4d10a7a59aac8771dc17958d93eb Edward Thomson 1360633436 -0600 rebase -i (finish): returning to refs/heads/branch -8eefb6f8061e4d10a7a59aac8771dc17958d93eb 9b2f98b1c086d3929eac387d39fae94425e0115f Edward Thomson 1360633440 -0600 checkout: moving from branch to master diff --git a/LibGit2Sharp.Tests/Resources/mergedrepo_wd/dot_git/logs/refs/heads/branch b/LibGit2Sharp.Tests/Resources/mergedrepo_wd/dot_git/logs/refs/heads/branch deleted file mode 100644 index 13819577f..000000000 --- a/LibGit2Sharp.Tests/Resources/mergedrepo_wd/dot_git/logs/refs/heads/branch +++ /dev/null @@ -1,4 +0,0 @@ -0000000000000000000000000000000000000000 9107b30fe11ff310fec93876469147b263fb958c Edward Thomson 1360632483 -0600 branch: Created from HEAD -9107b30fe11ff310fec93876469147b263fb958c d4c107581ff54a41fd496e0a1f1b7b3354752103 Edward Thomson 1360632833 -0600 commit: branch -d4c107581ff54a41fd496e0a1f1b7b3354752103 493209f496e504d63fd78e8c5b800083b442ea34 Edward Thomson 1360633416 -0600 commit: branch -493209f496e504d63fd78e8c5b800083b442ea34 8eefb6f8061e4d10a7a59aac8771dc17958d93eb Edward Thomson 1360633436 -0600 rebase -i (finish): refs/heads/branch onto d4c1075 diff --git a/LibGit2Sharp.Tests/Resources/mergedrepo_wd/dot_git/logs/refs/heads/master b/LibGit2Sharp.Tests/Resources/mergedrepo_wd/dot_git/logs/refs/heads/master deleted file mode 100644 index 167ae3e56..000000000 --- a/LibGit2Sharp.Tests/Resources/mergedrepo_wd/dot_git/logs/refs/heads/master +++ /dev/null @@ -1,4 +0,0 @@ -0000000000000000000000000000000000000000 9107b30fe11ff310fec93876469147b263fb958c Edward Thomson 1360632480 -0600 commit (initial): ancestor -9107b30fe11ff310fec93876469147b263fb958c 8b19fd77de5995e6e502e7c3b82657248f510325 Edward Thomson 1360633224 -0600 commit: ours -8b19fd77de5995e6e502e7c3b82657248f510325 91116bad97c50a2daa278ca6cffe16d0118befe5 Edward Thomson 1360633337 -0600 commit: ours -91116bad97c50a2daa278ca6cffe16d0118befe5 9b2f98b1c086d3929eac387d39fae94425e0115f Edward Thomson 1360633362 -0600 rebase -i (finish): refs/heads/master onto 8b19fd7 diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/logs/HEAD b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/logs/HEAD deleted file mode 100644 index 7b5293fd3..000000000 --- a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/logs/HEAD +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 bd593285fc7fe4ca18ccdbabf027f5d689101452 Edward Thomson 1365715620 -0500 moving to bd59328 diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/logs/refs/heads/master b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/logs/refs/heads/master deleted file mode 100644 index a46518da8..000000000 --- a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/logs/refs/heads/master +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 977c696519c5a3004c5f1d15d60c89dbeb8f235f Edward Thomson 1351875584 -0500 reset: moving to 977c696519c5a3004c5f1d15d60c89dbeb8f235f diff --git a/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/dot_git/HEAD b/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/dot_git/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/dot_git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/dot_git/config b/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/dot_git/config new file mode 100644 index 000000000..78387c50b --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/dot_git/config @@ -0,0 +1,8 @@ +[core] + repositoryformatversion = 0 + filemode = false + bare = false + logallrefupdates = true + symlinks = false + ignorecase = true + hideDotFiles = dotGitOnly diff --git a/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/dot_git/index b/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/dot_git/index new file mode 100644 index 000000000..1af67f2d4 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/dot_git/index differ diff --git a/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/dot_git/objects/87/2129051d644790636b416d1ef1ec830c5f6b90 b/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/dot_git/objects/87/2129051d644790636b416d1ef1ec830c5f6b90 new file mode 100644 index 000000000..3c800a797 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/dot_git/objects/87/2129051d644790636b416d1ef1ec830c5f6b90 @@ -0,0 +1,3 @@ +xI +B1]>'[ oU/Zni"5SQhC.n=? \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/dot_git/objects/88/e38705fdbd3608cddbe904b67c731f3234c45b b/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/dot_git/objects/88/e38705fdbd3608cddbe904b67c731f3234c45b new file mode 100644 index 000000000..783449ff9 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/dot_git/objects/88/e38705fdbd3608cddbe904b67c731f3234c45b differ diff --git a/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/dot_git/objects/cc/628ccd10742baea8241c5924df992b5c019f71 b/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/dot_git/objects/cc/628ccd10742baea8241c5924df992b5c019f71 new file mode 100644 index 000000000..6b011038d Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/dot_git/objects/cc/628ccd10742baea8241c5924df992b5c019f71 differ diff --git a/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/dot_git/objects/ce/013625030ba8dba906f756967f9e9ca394464a b/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/dot_git/objects/ce/013625030ba8dba906f756967f9e9ca394464a new file mode 100644 index 000000000..6802d4949 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/dot_git/objects/ce/013625030ba8dba906f756967f9e9ca394464a differ diff --git a/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/dot_git/refs/heads/master b/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/dot_git/refs/heads/master new file mode 100644 index 000000000..2ed6cd9fa --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/dot_git/refs/heads/master @@ -0,0 +1 @@ +872129051d644790636b416d1ef1ec830c5f6b90 diff --git a/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/hello.txt b/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/hello.txt new file mode 100644 index 000000000..e965047ad --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/hello.txt @@ -0,0 +1 @@ +Hello diff --git a/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/world.txt b/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/world.txt new file mode 100644 index 000000000..216e97ce0 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/world.txt @@ -0,0 +1 @@ +World diff --git a/LibGit2Sharp.Tests/Resources/submodule_small_wd/dot_git/description b/LibGit2Sharp.Tests/Resources/submodule_small_wd/dot_git/description deleted file mode 100644 index 498b267a8..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_small_wd/dot_git/description +++ /dev/null @@ -1 +0,0 @@ -Unnamed repository; edit this file 'description' to name the repository. diff --git a/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/description b/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/description deleted file mode 100644 index 498b267a8..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/description +++ /dev/null @@ -1 +0,0 @@ -Unnamed repository; edit this file 'description' to name the repository. diff --git a/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/logs/HEAD b/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/logs/HEAD deleted file mode 100644 index 0ecd1113f..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/logs/HEAD +++ /dev/null @@ -1,4 +0,0 @@ -0000000000000000000000000000000000000000 6b31c659545507c381e9cd34ec508f16c04e149e Russell Belfer 1342559662 -0700 commit (initial): Initial commit -6b31c659545507c381e9cd34ec508f16c04e149e 41bd4bc3df978de695f67ace64c560913da11653 Russell Belfer 1342559709 -0700 commit: Adding test file -41bd4bc3df978de695f67ace64c560913da11653 5e4963595a9774b90524d35a807169049de8ccad Russell Belfer 1342559726 -0700 commit: Updating test file -5e4963595a9774b90524d35a807169049de8ccad 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342559925 -0700 commit: One more update diff --git a/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/logs/refs/heads/master b/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/logs/refs/heads/master deleted file mode 100644 index 0ecd1113f..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/logs/refs/heads/master +++ /dev/null @@ -1,4 +0,0 @@ -0000000000000000000000000000000000000000 6b31c659545507c381e9cd34ec508f16c04e149e Russell Belfer 1342559662 -0700 commit (initial): Initial commit -6b31c659545507c381e9cd34ec508f16c04e149e 41bd4bc3df978de695f67ace64c560913da11653 Russell Belfer 1342559709 -0700 commit: Adding test file -41bd4bc3df978de695f67ace64c560913da11653 5e4963595a9774b90524d35a807169049de8ccad Russell Belfer 1342559726 -0700 commit: Updating test file -5e4963595a9774b90524d35a807169049de8ccad 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342559925 -0700 commit: One more update diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/config b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/config index d8535d3ac..7eada4ff5 100644 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/config +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/config @@ -18,3 +18,5 @@ url = ../submodule_target_wd [submodule "sm_added_and_uncommited"] url = ../submodule_target_wd +[submodule "sm_branch_only"] + url = ../submodule_target_wd diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/description b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/description deleted file mode 100644 index 498b267a8..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/description +++ /dev/null @@ -1 +0,0 @@ -Unnamed repository; edit this file 'description' to name the repository. diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/logs/HEAD b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/logs/HEAD deleted file mode 100644 index 2cf2ca74d..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/logs/HEAD +++ /dev/null @@ -1,4 +0,0 @@ -0000000000000000000000000000000000000000 14fe9ccf104058df25e0a08361c4494e167ef243 Russell Belfer 1342559771 -0700 commit (initial): Initial commit -14fe9ccf104058df25e0a08361c4494e167ef243 a9104bf89e911387244ef499413960ba472066d9 Russell Belfer 1342559831 -0700 commit: Adding a submodule -a9104bf89e911387244ef499413960ba472066d9 5901da4f1c67756eeadc5121d206bec2431f253b Russell Belfer 1342560036 -0700 commit: Updating submodule -5901da4f1c67756eeadc5121d206bec2431f253b 7484482eb8db738cafa696993664607500a3f2b9 Russell Belfer 1342560288 -0700 commit: Adding a bunch more test content diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/logs/refs/heads/master b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/logs/refs/heads/master deleted file mode 100644 index 2cf2ca74d..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/logs/refs/heads/master +++ /dev/null @@ -1,4 +0,0 @@ -0000000000000000000000000000000000000000 14fe9ccf104058df25e0a08361c4494e167ef243 Russell Belfer 1342559771 -0700 commit (initial): Initial commit -14fe9ccf104058df25e0a08361c4494e167ef243 a9104bf89e911387244ef499413960ba472066d9 Russell Belfer 1342559831 -0700 commit: Adding a submodule -a9104bf89e911387244ef499413960ba472066d9 5901da4f1c67756eeadc5121d206bec2431f253b Russell Belfer 1342560036 -0700 commit: Updating submodule -5901da4f1c67756eeadc5121d206bec2431f253b 7484482eb8db738cafa696993664607500a3f2b9 Russell Belfer 1342560288 -0700 commit: Adding a bunch more test content diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/config b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/config index 2d0583e99..e37936d26 100644 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/config +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/config @@ -7,7 +7,7 @@ ignorecase = true [remote "origin"] fetch = +refs/heads/*:refs/remotes/origin/* - url = /Users/rb/src/libgit2/tests-clar/resources/submod2_target + url = ../../../../submodule_target_wd [branch "master"] remote = origin merge = refs/heads/master diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/description b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/description deleted file mode 100644 index 498b267a8..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/description +++ /dev/null @@ -1 +0,0 @@ -Unnamed repository; edit this file 'description' to name the repository. diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/info/exclude b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/info/exclude deleted file mode 100644 index a5196d1be..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/info/exclude +++ /dev/null @@ -1,6 +0,0 @@ -# git ls-files --others --exclude-from=.git/info/exclude -# Lines that start with '#' are comments. -# For a project mostly in C, the following would be a good set of -# exclude patterns (uncomment them if you want to use them): -# *.[oa] -# *~ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/logs/HEAD b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/logs/HEAD deleted file mode 100644 index 53753e7dd..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/logs/HEAD +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560316 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/logs/refs/heads/master b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/logs/refs/heads/master deleted file mode 100644 index 53753e7dd..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/logs/refs/heads/master +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560316 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/logs/refs/remotes/origin/HEAD b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/logs/refs/remotes/origin/HEAD deleted file mode 100644 index 53753e7dd..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/logs/refs/remotes/origin/HEAD +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560316 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/HEAD b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/config b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/config new file mode 100644 index 000000000..d00ee8884 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/config @@ -0,0 +1,16 @@ +[core] + repositoryformatversion = 0 + filemode = false + bare = false + logallrefupdates = true + worktree = ../../../sm_branch_only + symlinks = false + ignorecase = true + hideDotFiles = dotGitOnly +[remote "origin"] + url = ../../../../submodule_target_wd + fetch = +refs/heads/*:refs/remotes/origin/* +[branch "master"] + remote = origin + merge = refs/heads/master + rebase = true diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/index b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/index new file mode 100644 index 000000000..a30232ec4 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/index differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 new file mode 100644 index 000000000..f4b7094c5 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 new file mode 100644 index 000000000..56c845e49 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 new file mode 100644 index 000000000..bd179b5f5 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/41/bd4bc3df978de695f67ace64c560913da11653 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/41/bd4bc3df978de695f67ace64c560913da11653 new file mode 100644 index 000000000..ccf49bd15 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/41/bd4bc3df978de695f67ace64c560913da11653 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 new file mode 100644 index 000000000..53029069a Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/5e/4963595a9774b90524d35a807169049de8ccad b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/5e/4963595a9774b90524d35a807169049de8ccad new file mode 100644 index 000000000..38c791eba Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/5e/4963595a9774b90524d35a807169049de8ccad differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/6b/31c659545507c381e9cd34ec508f16c04e149e b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/6b/31c659545507c381e9cd34ec508f16c04e149e new file mode 100644 index 000000000..a26d29993 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/6b/31c659545507c381e9cd34ec508f16c04e149e @@ -0,0 +1,2 @@ +xQ +!Evoy*_@g#hOh^9wSòf1*[Ic Ԥpk Α\S߇l@.^QpF(:D5zr~ en8 \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/73/ba924a80437097795ae839e66e187c55d3babf b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/73/ba924a80437097795ae839e66e187c55d3babf new file mode 100644 index 000000000..83d1ba481 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/73/ba924a80437097795ae839e66e187c55d3babf differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a new file mode 100644 index 000000000..6d27af8a8 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a @@ -0,0 +1,2 @@ +x-10 Fa0p(N-ӡғq]>ks*? |m“i@mV'`).-1 x +uxt(+ \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/78/9efbdadaa4a582778d4584385495559ea0994b b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/78/9efbdadaa4a582778d4584385495559ea0994b new file mode 100644 index 000000000..17458840b --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/78/9efbdadaa4a582778d4584385495559ea0994b @@ -0,0 +1,2 @@ +x 0 )?= NlOkj8&r +qJW7B<fK8#Q1C-"e̫>'@ \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e new file mode 100644 index 000000000..83cc29fb1 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 new file mode 100644 index 000000000..55bda40ef Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/packed-refs b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/packed-refs new file mode 100644 index 000000000..def303a5f --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/packed-refs @@ -0,0 +1,2 @@ +# pack-refs with: peeled fully-peeled +480095882d281ed676fe5b863569520e54a7d5c0 refs/remotes/origin/master diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/refs/heads/master b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/refs/heads/master new file mode 100644 index 000000000..e12c44d7a --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/refs/heads/master @@ -0,0 +1 @@ +480095882d281ed676fe5b863569520e54a7d5c0 diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/refs/remotes/origin/HEAD b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/refs/remotes/origin/HEAD new file mode 100644 index 000000000..6efe28fff --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/refs/remotes/origin/HEAD @@ -0,0 +1 @@ +ref: refs/remotes/origin/master diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/config b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/config index 10cc2508e..9812c64b3 100644 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/config +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/config @@ -7,7 +7,7 @@ ignorecase = true [remote "origin"] fetch = +refs/heads/*:refs/remotes/origin/* - url = /Users/rb/src/libgit2/tests-clar/resources/submod2_target + url = ../../../../submodule_target_wd [branch "master"] remote = origin merge = refs/heads/master diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/description b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/description deleted file mode 100644 index 498b267a8..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/description +++ /dev/null @@ -1 +0,0 @@ -Unnamed repository; edit this file 'description' to name the repository. diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/info/exclude b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/info/exclude deleted file mode 100644 index a5196d1be..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/info/exclude +++ /dev/null @@ -1,6 +0,0 @@ -# git ls-files --others --exclude-from=.git/info/exclude -# Lines that start with '#' are comments. -# For a project mostly in C, the following would be a good set of -# exclude patterns (uncomment them if you want to use them): -# *.[oa] -# *~ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/logs/HEAD b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/logs/HEAD deleted file mode 100644 index e5cb63f8d..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/logs/HEAD +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560173 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/logs/refs/heads/master b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/logs/refs/heads/master deleted file mode 100644 index e5cb63f8d..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/logs/refs/heads/master +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560173 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/logs/refs/remotes/origin/HEAD b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/logs/refs/remotes/origin/HEAD deleted file mode 100644 index e5cb63f8d..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/logs/refs/remotes/origin/HEAD +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560173 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/COMMIT_EDITMSG b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/COMMIT_EDITMSG deleted file mode 100644 index 6b8d1e3fc..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/COMMIT_EDITMSG +++ /dev/null @@ -1 +0,0 @@ -Making a change in a submodule diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/config b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/config index 7d002536a..6d46e37ac 100644 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/config +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/config @@ -7,7 +7,7 @@ ignorecase = true [remote "origin"] fetch = +refs/heads/*:refs/remotes/origin/* - url = /Users/rb/src/libgit2/tests-clar/resources/submod2_target + url = ../../../../submodule_target_wd [branch "master"] remote = origin merge = refs/heads/master diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/description b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/description deleted file mode 100644 index 498b267a8..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/description +++ /dev/null @@ -1 +0,0 @@ -Unnamed repository; edit this file 'description' to name the repository. diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/info/exclude b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/info/exclude deleted file mode 100644 index a5196d1be..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/info/exclude +++ /dev/null @@ -1,6 +0,0 @@ -# git ls-files --others --exclude-from=.git/info/exclude -# Lines that start with '#' are comments. -# For a project mostly in C, the following would be a good set of -# exclude patterns (uncomment them if you want to use them): -# *.[oa] -# *~ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/logs/HEAD b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/logs/HEAD deleted file mode 100644 index cabdeb2b5..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/logs/HEAD +++ /dev/null @@ -1,2 +0,0 @@ -0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560179 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target -480095882d281ed676fe5b863569520e54a7d5c0 3d9386c507f6b093471a3e324085657a3c2b4247 Russell Belfer 1342560431 -0700 commit: Making a change in a submodule diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/logs/refs/heads/master b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/logs/refs/heads/master deleted file mode 100644 index cabdeb2b5..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/logs/refs/heads/master +++ /dev/null @@ -1,2 +0,0 @@ -0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560179 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target -480095882d281ed676fe5b863569520e54a7d5c0 3d9386c507f6b093471a3e324085657a3c2b4247 Russell Belfer 1342560431 -0700 commit: Making a change in a submodule diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/logs/refs/remotes/origin/HEAD b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/logs/refs/remotes/origin/HEAD deleted file mode 100644 index 257ca21d1..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/logs/refs/remotes/origin/HEAD +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560179 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/config b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/config index 0274ff7e3..94490e330 100644 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/config +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/config @@ -7,7 +7,7 @@ ignorecase = true [remote "origin"] fetch = +refs/heads/*:refs/remotes/origin/* - url = /Users/rb/src/libgit2/tests-clar/resources/submod2_target + url = ../../../../submodule_target_wd [branch "master"] remote = origin merge = refs/heads/master diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/description b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/description deleted file mode 100644 index 498b267a8..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/description +++ /dev/null @@ -1 +0,0 @@ -Unnamed repository; edit this file 'description' to name the repository. diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/info/exclude b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/info/exclude deleted file mode 100644 index a5196d1be..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/info/exclude +++ /dev/null @@ -1,6 +0,0 @@ -# git ls-files --others --exclude-from=.git/info/exclude -# Lines that start with '#' are comments. -# For a project mostly in C, the following would be a good set of -# exclude patterns (uncomment them if you want to use them): -# *.[oa] -# *~ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/logs/HEAD b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/logs/HEAD deleted file mode 100644 index 80eb54102..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/logs/HEAD +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560175 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/logs/refs/heads/master b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/logs/refs/heads/master deleted file mode 100644 index 80eb54102..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/logs/refs/heads/master +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560175 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/logs/refs/remotes/origin/HEAD b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/logs/refs/remotes/origin/HEAD deleted file mode 100644 index 80eb54102..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/logs/refs/remotes/origin/HEAD +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560175 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/config b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/config index 7f2584476..e5ff0780a 100644 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/config +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/config @@ -7,7 +7,7 @@ ignorecase = true [remote "origin"] fetch = +refs/heads/*:refs/remotes/origin/* - url = /Users/rb/src/libgit2/tests-clar/resources/submod2_target + url = ../../../../submodule_target_wd [branch "master"] remote = origin merge = refs/heads/master diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/description b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/description deleted file mode 100644 index 498b267a8..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/description +++ /dev/null @@ -1 +0,0 @@ -Unnamed repository; edit this file 'description' to name the repository. diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/info/exclude b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/info/exclude deleted file mode 100644 index a5196d1be..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/info/exclude +++ /dev/null @@ -1,6 +0,0 @@ -# git ls-files --others --exclude-from=.git/info/exclude -# Lines that start with '#' are comments. -# For a project mostly in C, the following would be a good set of -# exclude patterns (uncomment them if you want to use them): -# *.[oa] -# *~ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/logs/HEAD b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/logs/HEAD deleted file mode 100644 index d1beafbd6..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/logs/HEAD +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560186 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/logs/refs/heads/master b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/logs/refs/heads/master deleted file mode 100644 index d1beafbd6..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/logs/refs/heads/master +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560186 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/logs/refs/remotes/origin/HEAD b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/logs/refs/remotes/origin/HEAD deleted file mode 100644 index d1beafbd6..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/logs/refs/remotes/origin/HEAD +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560186 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/config b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/config index 45fbb30cf..376a475b5 100644 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/config +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/config @@ -7,7 +7,7 @@ ignorecase = true [remote "origin"] fetch = +refs/heads/*:refs/remotes/origin/* - url = /Users/rb/src/libgit2/tests-clar/resources/submod2_target + url = ../../../../submodule_target_wd [branch "master"] remote = origin merge = refs/heads/master diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/description b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/description deleted file mode 100644 index 498b267a8..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/description +++ /dev/null @@ -1 +0,0 @@ -Unnamed repository; edit this file 'description' to name the repository. diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/info/exclude b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/info/exclude deleted file mode 100644 index a5196d1be..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/info/exclude +++ /dev/null @@ -1,6 +0,0 @@ -# git ls-files --others --exclude-from=.git/info/exclude -# Lines that start with '#' are comments. -# For a project mostly in C, the following would be a good set of -# exclude patterns (uncomment them if you want to use them): -# *.[oa] -# *~ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/logs/HEAD b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/logs/HEAD deleted file mode 100644 index ee08c9706..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/logs/HEAD +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 5e4963595a9774b90524d35a807169049de8ccad Russell Belfer 1342559796 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/logs/refs/heads/master b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/logs/refs/heads/master deleted file mode 100644 index ee08c9706..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/logs/refs/heads/master +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 5e4963595a9774b90524d35a807169049de8ccad Russell Belfer 1342559796 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/logs/refs/remotes/origin/HEAD b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/logs/refs/remotes/origin/HEAD deleted file mode 100644 index ee08c9706..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/logs/refs/remotes/origin/HEAD +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 5e4963595a9774b90524d35a807169049de8ccad Russell Belfer 1342559796 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/config b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/config index fc706c9dd..ea79f0e26 100644 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/config +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/config @@ -7,7 +7,7 @@ ignorecase = true [remote "origin"] fetch = +refs/heads/*:refs/remotes/origin/* - url = /Users/rb/src/libgit2/tests-clar/resources/submod2_target + url = ../../../../submodule_target_wd [branch "master"] remote = origin merge = refs/heads/master diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/description b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/description deleted file mode 100644 index 498b267a8..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/description +++ /dev/null @@ -1 +0,0 @@ -Unnamed repository; edit this file 'description' to name the repository. diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/info/exclude b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/info/exclude deleted file mode 100644 index a5196d1be..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/info/exclude +++ /dev/null @@ -1,6 +0,0 @@ -# git ls-files --others --exclude-from=.git/info/exclude -# Lines that start with '#' are comments. -# For a project mostly in C, the following would be a good set of -# exclude patterns (uncomment them if you want to use them): -# *.[oa] -# *~ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/logs/HEAD b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/logs/HEAD deleted file mode 100644 index 72653286a..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/logs/HEAD +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560169 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/logs/refs/heads/master b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/logs/refs/heads/master deleted file mode 100644 index 72653286a..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/logs/refs/heads/master +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560169 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/logs/refs/remotes/origin/HEAD b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/logs/refs/remotes/origin/HEAD deleted file mode 100644 index 72653286a..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/logs/refs/remotes/origin/HEAD +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560169 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/13/921096a46cb66610badba272f8211346eaf8f3 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/13/921096a46cb66610badba272f8211346eaf8f3 new file mode 100644 index 000000000..6f03cbf00 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/13/921096a46cb66610badba272f8211346eaf8f3 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/6b/94d06e586d4ed904d8c00a9de7d0afe0fc9c3c b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/6b/94d06e586d4ed904d8c00a9de7d0afe0fc9c3c new file mode 100644 index 000000000..21bbe6d2b Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/6b/94d06e586d4ed904d8c00a9de7d0afe0fc9c3c differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/87/aa3a079302a662a9226af3c6e7f444815e3faf b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/87/aa3a079302a662a9226af3c6e7f444815e3faf new file mode 100644 index 000000000..666341811 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/87/aa3a079302a662a9226af3c6e7f444815e3faf differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/a4/aab482be687d2facec638781ded4aa1a92687a b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/a4/aab482be687d2facec638781ded4aa1a92687a new file mode 100644 index 000000000..34675b82e Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/a4/aab482be687d2facec638781ded4aa1a92687a differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/cf/27c0500009f6d12fd82d841ecf6a17b18ff812 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/cf/27c0500009f6d12fd82d841ecf6a17b18ff812 new file mode 100644 index 000000000..d620874b9 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/cf/27c0500009f6d12fd82d841ecf6a17b18ff812 @@ -0,0 +1,2 @@ +xK `)HP“Cz{jS-?fHCf]\ jJ8'4ǝQJxG{U2&mr֙t׃挦x\-|4Yf +Mmej dНчNY4ڠ)c6"܍\9l}2X;7N?Y h \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/refs/heads/dev b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/refs/heads/dev new file mode 100644 index 000000000..9cbe3f817 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/refs/heads/dev @@ -0,0 +1 @@ +6b94d06e586d4ed904d8c00a9de7d0afe0fc9c3c diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/not_submodule/dot_git/description b/LibGit2Sharp.Tests/Resources/submodule_wd/not_submodule/dot_git/description deleted file mode 100644 index 498b267a8..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/not_submodule/dot_git/description +++ /dev/null @@ -1 +0,0 @@ -Unnamed repository; edit this file 'description' to name the repository. diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/not_submodule/dot_git/logs/HEAD b/LibGit2Sharp.Tests/Resources/submodule_wd/not_submodule/dot_git/logs/HEAD deleted file mode 100644 index 1749e7dff..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/not_submodule/dot_git/logs/HEAD +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 68e92c611b80ee1ed8f38314ff9577f0d15b2444 Russell Belfer 1342560358 -0700 commit (initial): Initial commit diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/not_submodule/dot_git/logs/refs/heads/master b/LibGit2Sharp.Tests/Resources/submodule_wd/not_submodule/dot_git/logs/refs/heads/master deleted file mode 100644 index 1749e7dff..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/not_submodule/dot_git/logs/refs/heads/master +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 68e92c611b80ee1ed8f38314ff9577f0d15b2444 Russell Belfer 1342560358 -0700 commit (initial): Initial commit diff --git a/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/description b/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/description deleted file mode 100644 index 893e5cad2..000000000 --- a/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/description +++ /dev/null @@ -1 +0,0 @@ -Unnamed repository; edit this file 'description' to name the repository. diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/1.txt b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/1.txt new file mode 100644 index 000000000..5626abf0f --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/1.txt @@ -0,0 +1 @@ +one diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/1/branch_file.txt b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/1/branch_file.txt new file mode 100644 index 000000000..45b983be3 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/1/branch_file.txt @@ -0,0 +1 @@ +hi diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/README b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/README new file mode 100644 index 000000000..a8233120f --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/README @@ -0,0 +1 @@ +hey there diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/branch_file.txt b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/branch_file.txt new file mode 100644 index 000000000..45b983be3 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/branch_file.txt @@ -0,0 +1 @@ +hi diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/COMMIT_EDITMSG b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/COMMIT_EDITMSG new file mode 100644 index 000000000..63ec8fdda --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/COMMIT_EDITMSG @@ -0,0 +1 @@ +Add "1.txt" file beside "1" folder diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/HEAD b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/HEAD new file mode 100644 index 000000000..cb4380516 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/config b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/config new file mode 100644 index 000000000..1599f0b76 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/config @@ -0,0 +1,23 @@ +[core] + repositoryformatversion = 0 + filemode = false + bare = false + logallrefupdates = true + symlinks = false + ignorecase = true + hideDotFiles = dotGitOnly +[remote "origin"] + fetch = +refs/heads/*:refs/remotes/origin/* + url = c:/GitHub/libgit2sharp/Resources/testrepo.git +[remote "no_url"] + url = + fetch = +refs/heads/*:refs/remotes/no_url/* +[branch "master"] + remote = origin + merge = refs/heads/master +[branch "track-local"] + remote = . + merge = refs/heads/master +[unittests] + longsetting = 15234 + intsetting = 2 diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/index b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/index new file mode 100644 index 000000000..1f5bd73a7 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/index differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/info/exclude b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/info/exclude similarity index 97% rename from LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/info/exclude rename to LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/info/exclude index a5196d1be..f00680973 100644 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/info/exclude +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/info/exclude @@ -1,6 +1,6 @@ -# git ls-files --others --exclude-from=.git/info/exclude -# Lines that start with '#' are comments. -# For a project mostly in C, the following would be a good set of -# exclude patterns (uncomment them if you want to use them): -# *.[oa] -# *~ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/logs/HEAD b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/logs/HEAD new file mode 100644 index 000000000..23375c60c --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/logs/HEAD @@ -0,0 +1,3 @@ +0000000000000000000000000000000000000000 4c062a6361ae6959e06292c1fa5e2822d9c96345 Tim Clem 1303768198 -0700 clone: from c:/GitHub/libgit2sharp/Resources/testrepo.git +4c062a6361ae6959e06292c1fa5e2822d9c96345 592d3c869dbc4127fc57c189cb94f2794fa84e7e Tim Clem 1303835722 -0700 commit: add more test files +592d3c869dbc4127fc57c189cb94f2794fa84e7e 32eab9cb1f450b5fe7ab663462b77d7f4b703344 nulltoken 1320047537 +0100 commit: Add "1.txt" file beside "1" folder diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/logs/refs/heads/logo b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/logs/refs/heads/logo new file mode 100644 index 000000000..e3e3e53ff --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/logs/refs/heads/logo @@ -0,0 +1,2 @@ +0000000000000000000000000000000000000000 4c062a6361ae6959e06292c1fa5e2822d9c96345 nulltoken 1359021419 +0100 branch: Created from 4c062a6361ae6959e06292c1fa5e2822d9c96345 +4c062a6361ae6959e06292c1fa5e2822d9c96345 a447ba2ca8fffd46dece72f7db6faf324afb8fcd nulltoken 1359021433 +0100 commit: Add logo diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/logs/refs/heads/master b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/logs/refs/heads/master new file mode 100644 index 000000000..23375c60c --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/logs/refs/heads/master @@ -0,0 +1,3 @@ +0000000000000000000000000000000000000000 4c062a6361ae6959e06292c1fa5e2822d9c96345 Tim Clem 1303768198 -0700 clone: from c:/GitHub/libgit2sharp/Resources/testrepo.git +4c062a6361ae6959e06292c1fa5e2822d9c96345 592d3c869dbc4127fc57c189cb94f2794fa84e7e Tim Clem 1303835722 -0700 commit: add more test files +592d3c869dbc4127fc57c189cb94f2794fa84e7e 32eab9cb1f450b5fe7ab663462b77d7f4b703344 nulltoken 1320047537 +0100 commit: Add "1.txt" file beside "1" folder diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/04/c9b35f51fbff2338d5cdc959b23a93a14c5063 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/04/c9b35f51fbff2338d5cdc959b23a93a14c5063 new file mode 100644 index 000000000..a8660a9ea Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/04/c9b35f51fbff2338d5cdc959b23a93a14c5063 differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/0a/99448e920a3615f33273047412949d09015ff8 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/0a/99448e920a3615f33273047412949d09015ff8 new file mode 100644 index 000000000..560fa54b9 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/0a/99448e920a3615f33273047412949d09015ff8 differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08 new file mode 100644 index 000000000..cedb2a22e Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08 differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/15/d2ecc60893449f4fe4593dd51a4706dec212f5 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/15/d2ecc60893449f4fe4593dd51a4706dec212f5 new file mode 100644 index 000000000..ec005aa90 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/15/d2ecc60893449f4fe4593dd51a4706dec212f5 differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/16/bdf1dece5c56c92a9187550fafe0270a03a93a b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/16/bdf1dece5c56c92a9187550fafe0270a03a93a new file mode 100644 index 000000000..6f579f741 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/16/bdf1dece5c56c92a9187550fafe0270a03a93a @@ -0,0 +1,2 @@ +x ̱ +1EQjm\,Dl,'Ä:"큛3^47uو \0yVg(Wϝ XmL?2ʍjK=yPK™I #Y \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/18/1037049a54a1eb5fab404658a3a250b44335d7 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/18/1037049a54a1eb5fab404658a3a250b44335d7 new file mode 100644 index 000000000..93a16f146 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/18/1037049a54a1eb5fab404658a3a250b44335d7 differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/18/10dff58d8a660512d4832e740f692884338ccd b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/18/10dff58d8a660512d4832e740f692884338ccd new file mode 100644 index 000000000..ba0bfb30c Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/18/10dff58d8a660512d4832e740f692884338ccd differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/32/eab9cb1f450b5fe7ab663462b77d7f4b703344 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/32/eab9cb1f450b5fe7ab663462b77d7f4b703344 new file mode 100644 index 000000000..9690a6ad0 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/32/eab9cb1f450b5fe7ab663462b77d7f4b703344 @@ -0,0 +1 @@ +x]j!)} ?!dm+qq]3䥠>Z&0M1h}VvY+.a9z4O *LGxxdVðc^Zg(e-<n-Qo߀kBgORE5%6r %bx7GP diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/33/a9574ff4dca3fbf68c6785859b80895c6b77b1 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/33/a9574ff4dca3fbf68c6785859b80895c6b77b1 new file mode 100644 index 000000000..95151430c Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/33/a9574ff4dca3fbf68c6785859b80895c6b77b1 differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/37/d22f870ffe688c0d1220fbbf1f06629c64142c b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/37/d22f870ffe688c0d1220fbbf1f06629c64142c new file mode 100644 index 000000000..0598edb69 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/37/d22f870ffe688c0d1220fbbf1f06629c64142c differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 new file mode 100644 index 000000000..7ca4ceed5 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/46/25a3628cb78970c57e23a2fe2574514ba403c7 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/46/25a3628cb78970c57e23a2fe2574514ba403c7 new file mode 100644 index 000000000..500ff4036 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/46/25a3628cb78970c57e23a2fe2574514ba403c7 differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045 new file mode 100644 index 000000000..8953b6cef --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045 @@ -0,0 +1,2 @@ +xQ +0D)6ͦ "xO-FbEo0 Ǥ,ske[Pn8R,EpD?g}^3 <GhYK8ЖDA);gݧjp4-r;sGA4ۺ=(in7IKFE \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/4b/e51d6fc0943aa42b635c762145ca209cf39771 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/4b/e51d6fc0943aa42b635c762145ca209cf39771 new file mode 100644 index 000000000..e1ab3daf0 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/4b/e51d6fc0943aa42b635c762145ca209cf39771 @@ -0,0 +1,2 @@ +xK +1D] C tg&`&QVkQFgauxved0Sr.yJ!'C^! @`2,@ ({Oi_eO\VRag):w>R-&BEy \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/4c/062a6361ae6959e06292c1fa5e2822d9c96345 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/4c/062a6361ae6959e06292c1fa5e2822d9c96345 new file mode 100644 index 000000000..4d86b3208 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/4c/062a6361ae6959e06292c1fa5e2822d9c96345 differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/4e/935b73cf0fe06c513267d517fc2e65fc0d100e b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/4e/935b73cf0fe06c513267d517fc2e65fc0d100e new file mode 100644 index 000000000..c48084358 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/4e/935b73cf0fe06c513267d517fc2e65fc0d100e differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/50/9d02afef0632c7f733ddcd62500b0538d9157f b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/50/9d02afef0632c7f733ddcd62500b0538d9157f new file mode 100644 index 000000000..b3fac2ef1 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/50/9d02afef0632c7f733ddcd62500b0538d9157f differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/56/05472eb48cb4e60b5aa8810cc5ec8138026fad b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/56/05472eb48cb4e60b5aa8810cc5ec8138026fad new file mode 100644 index 000000000..ecaf0c6fe Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/56/05472eb48cb4e60b5aa8810cc5ec8138026fad differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/56/26abf0f72e58d7a153368ba57db4c673c0e171 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/56/26abf0f72e58d7a153368ba57db4c673c0e171 new file mode 100644 index 000000000..4d5447467 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/56/26abf0f72e58d7a153368ba57db4c673c0e171 differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/58/0c2111be43802dab11328176d94c391f1deae9 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/58/0c2111be43802dab11328176d94c391f1deae9 new file mode 100644 index 000000000..81671a754 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/58/0c2111be43802dab11328176d94c391f1deae9 @@ -0,0 +1,2 @@ +xAj1 E)t{d[6EvG ep}B{>B0I \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/58/1f9824ecaf824221bd36edf5430f2739a7c4f5 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/58/1f9824ecaf824221bd36edf5430f2739a7c4f5 new file mode 100644 index 000000000..2ae137844 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/58/1f9824ecaf824221bd36edf5430f2739a7c4f5 differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/59/2d3c869dbc4127fc57c189cb94f2794fa84e7e b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/59/2d3c869dbc4127fc57c189cb94f2794fa84e7e new file mode 100644 index 000000000..33d011c41 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/59/2d3c869dbc4127fc57c189cb94f2794fa84e7e differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644 new file mode 100644 index 000000000..c1f22c54f --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644 @@ -0,0 +1,2 @@ +x 1ENi@k2 "X$YW0YcÅszMD08!s Xgd::@X0Pw"F/RUzmZZV}|/o5I!1z:vUim}/> +F- \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/67/b8324ec2fefc01fd9d31d328116df0474e7acd b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/67/b8324ec2fefc01fd9d31d328116df0474e7acd new file mode 100644 index 000000000..8c4a65994 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/67/b8324ec2fefc01fd9d31d328116df0474e7acd differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/6b/53f5d357f29607605ce2e612d2fda6693ff8d7 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/6b/53f5d357f29607605ce2e612d2fda6693ff8d7 new file mode 100644 index 000000000..c0685b971 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/6b/53f5d357f29607605ce2e612d2fda6693ff8d7 differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/72/52fe2da2c4dd6d85231a150d0485ec46abaa7a b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/72/52fe2da2c4dd6d85231a150d0485ec46abaa7a new file mode 100644 index 000000000..15682ca4d Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/72/52fe2da2c4dd6d85231a150d0485ec46abaa7a differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/74/9a42f6ef33405e5ac16687963aebab8b78abd1 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/74/9a42f6ef33405e5ac16687963aebab8b78abd1 new file mode 100644 index 000000000..dbebba8e4 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/74/9a42f6ef33405e5ac16687963aebab8b78abd1 differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a new file mode 100644 index 000000000..2ef4faa0f Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/79/09961ae96accd75b6813d32e0fc1d6d52ec941 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/79/09961ae96accd75b6813d32e0fc1d6d52ec941 new file mode 100644 index 000000000..a1f7d97f3 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/79/09961ae96accd75b6813d32e0fc1d6d52ec941 differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/7b/4384978d2493e851f9cca7858815fac9b10980 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/7b/4384978d2493e851f9cca7858815fac9b10980 new file mode 100644 index 000000000..23c462f34 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/7b/4384978d2493e851f9cca7858815fac9b10980 differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/7f/76480d939dc401415927ea7ef25c676b8ddb8f b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/7f/76480d939dc401415927ea7ef25c676b8ddb8f new file mode 100644 index 000000000..7018c7f77 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/7f/76480d939dc401415927ea7ef25c676b8ddb8f differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/81/4889a078c031f61ed08ab5fa863aea9314344d b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/81/4889a078c031f61ed08ab5fa863aea9314344d new file mode 100644 index 000000000..2f9b6b6e3 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/81/4889a078c031f61ed08ab5fa863aea9314344d differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/84/96071c1b46c854b31185ea97743be6a8774479 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/84/96071c1b46c854b31185ea97743be6a8774479 new file mode 100644 index 000000000..5df58dda5 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/84/96071c1b46c854b31185ea97743be6a8774479 differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/84/9f67c87f926a81af895fc037c04ad85549d73f b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/84/9f67c87f926a81af895fc037c04ad85549d73f new file mode 100644 index 000000000..eb5f7b230 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/84/9f67c87f926a81af895fc037c04ad85549d73f differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/89/657cd6da3ada7bfef880e6dfdb9732f28c272b b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/89/657cd6da3ada7bfef880e6dfdb9732f28c272b new file mode 100644 index 000000000..89e7566cd --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/89/657cd6da3ada7bfef880e6dfdb9732f28c272b @@ -0,0 +1,2 @@ +xA + E/cJ=8TA#]M ϭQvL0I?!4Z=!צ8F!rsQy9]$D&l6A>jFWҵ IKNiZ%S + U~̽>' w [ DGڡQ-M>dO}\8g_ШoYr \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd new file mode 100644 index 000000000..d0d7e736e Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6 new file mode 100644 index 000000000..18a7f61c2 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6 differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/ab/38987d12dc243c103a432608648c78fc6651a1 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/ab/38987d12dc243c103a432608648c78fc6651a1 new file mode 100644 index 000000000..649e9bbfa --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/ab/38987d12dc243c103a432608648c78fc6651a1 @@ -0,0 +1,2 @@ +xM +0F] eDzq@@ oo {Rré 1EȀKƀ<|V~ƛ|L|ܞ>I-Ws@Ԡ\"KZ Gw: \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/b2/5fa35b38051e4ae45d4222e795f9df2e43f1d1 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/b2/5fa35b38051e4ae45d4222e795f9df2e43f1d1 new file mode 100644 index 000000000..f460f2547 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/b2/5fa35b38051e4ae45d4222e795f9df2e43f1d1 @@ -0,0 +1,2 @@ +xA +0a9I p'1Ѷv\x{cVpvWgǎ0x[ ]"g#{rD Cot N U $?9-p+1^Qx9O\C m'D {mV(+l, \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/b3/e375c923d50c589b11b6da4a769bdd7f6502e3 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/b3/e375c923d50c589b11b6da4a769bdd7f6502e3 new file mode 100644 index 000000000..511e72c6a Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/b3/e375c923d50c589b11b6da4a769bdd7f6502e3 differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/b5/9b86c5f4874aea5255bf14d67a5ce13c80265f b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/b5/9b86c5f4874aea5255bf14d67a5ce13c80265f new file mode 100644 index 000000000..eed20b8e3 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/b5/9b86c5f4874aea5255bf14d67a5ce13c80265f differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/b7/58c5bc1c8117c2a4c545dae2903e36360501c5 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/b7/58c5bc1c8117c2a4c545dae2903e36360501c5 new file mode 100644 index 000000000..ee6419e03 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/b7/58c5bc1c8117c2a4c545dae2903e36360501c5 differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/be/3563ae3f795b2b4353bcce3a527ad0a4f7f644 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/be/3563ae3f795b2b4353bcce3a527ad0a4f7f644 new file mode 100644 index 000000000..0817229bc --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/be/3563ae3f795b2b4353bcce3a527ad0a4f7f644 @@ -0,0 +1,3 @@ +xKj1D)zUB-0uV9<#+W e^7t:wo܂ p@.=..nD"JHqDV1tUeޕi n afu9FkcOe׿*qk9rL^"!ay%_2fw3G_K \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/e8/953ab38d30b11c45b5ac7229fcef0ab4d603c6 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/e8/953ab38d30b11c45b5ac7229fcef0ab4d603c6 new file mode 100644 index 000000000..16eca526d --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/e8/953ab38d30b11c45b5ac7229fcef0ab4d603c6 @@ -0,0 +1 @@ +x A@0P[!N#ϋz]ld uE/DnDT$hXϼzp0(=bhj73|e~#[ \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/ec/9e401198937e33a8617be9f235a449728d9f6d b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/ec/9e401198937e33a8617be9f235a449728d9f6d new file mode 100644 index 000000000..9ba063ec3 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/ec/9e401198937e33a8617be9f235a449728d9f6d @@ -0,0 +1,4 @@ +xA +0E]$ fL#1 +>ǃ?ScU`=J'DdQ)xFDGު'WD¨0x2L-Z#qbm-> +n呶ے9=+hG7B3jDuaZuO-[WcT_FHn \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/f2/e41136eac73c39554dede1fd7e67b12502d577 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/f2/e41136eac73c39554dede1fd7e67b12502d577 new file mode 100644 index 000000000..1cccf6543 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/f2/e41136eac73c39554dede1fd7e67b12502d577 differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1 new file mode 100644 index 000000000..03770969a Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1 differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/f7/05abffe7015f2beacf2abe7a36583ebee3487e b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/f7/05abffe7015f2beacf2abe7a36583ebee3487e new file mode 100644 index 000000000..7490425a2 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/f7/05abffe7015f2beacf2abe7a36583ebee3487e @@ -0,0 +1 @@ +x B!D=S h؅Bb;XGc|/Kdz-FѲDXy) Y1X4z.rdv4Mbst+ҌS/zkuk}I\qVOlm QΣCPp1J \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/f8/d44d712e0680d942a4015058dd84e382879fe2 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/f8/d44d712e0680d942a4015058dd84e382879fe2 new file mode 100644 index 000000000..639e89094 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/f8/d44d712e0680d942a4015058dd84e382879fe2 differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/fa/49b077972391ad58037050f2a75f74e3671e92 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/fa/49b077972391ad58037050f2a75f74e3671e92 new file mode 100644 index 000000000..112998d42 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/fa/49b077972391ad58037050f2a75f74e3671e92 differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/fd/093bff70906175335656e6ce6ae05783708765 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/fd/093bff70906175335656e6ce6ae05783708765 new file mode 100644 index 000000000..12bf5f3e3 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/fd/093bff70906175335656e6ce6ae05783708765 differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idx b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idx new file mode 100644 index 000000000..5068f2818 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idx differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.pack b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.pack new file mode 100644 index 000000000..a6a1f3020 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.pack differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idx b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idx new file mode 100644 index 000000000..94c3c71da Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idx differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.pack b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.pack new file mode 100644 index 000000000..74c7fe4f3 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.pack differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.idx b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.idx new file mode 100644 index 000000000..555cfa977 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.idx differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.pack b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.pack new file mode 100644 index 000000000..4d539ed0a Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.pack differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/packed-refs b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/packed-refs new file mode 100644 index 000000000..71a7668ba --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/packed-refs @@ -0,0 +1,10 @@ +# pack-refs with: peeled +b25fa35b38051e4ae45d4222e795f9df2e43f1d1 refs/tags/test +^e90810b8df3e80c413d903f631643c716887138d +e90810b8df3e80c413d903f631643c716887138d refs/tags/lw +7b4384978d2493e851f9cca7858815fac9b10980 refs/tags/e90810b +^e90810b8df3e80c413d903f631643c716887138d +e90810b8df3e80c413d903f631643c716887138d refs/remotes/origin/test +4a202b346bb0fb0db7eff3cffeb3c70babbd2045 refs/remotes/origin/packed-test +580c2111be43802dab11328176d94c391f1deae9 refs/remotes/origin/master +a4a7dce85cf63874e984719f4fdd239f5145052f refs/remotes/origin/br2 diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/refs/heads/diff-test-cases b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/refs/heads/diff-test-cases new file mode 100644 index 000000000..f385e58ba --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/refs/heads/diff-test-cases @@ -0,0 +1 @@ +e7039e6d0e7dd4d4c1e2e8e5aa5306b2776436ca diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/refs/heads/i-do-numbers b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/refs/heads/i-do-numbers new file mode 100644 index 000000000..882969dfc --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/refs/heads/i-do-numbers @@ -0,0 +1 @@ +7252fe2da2c4dd6d85231a150d0485ec46abaa7a diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/refs/heads/logo b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/refs/heads/logo new file mode 100644 index 000000000..2bbca050f --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/refs/heads/logo @@ -0,0 +1 @@ +a447ba2ca8fffd46dece72f7db6faf324afb8fcd diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/refs/heads/master b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/refs/heads/master new file mode 100644 index 000000000..bca334acf --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/refs/heads/master @@ -0,0 +1 @@ +32eab9cb1f450b5fe7ab663462b77d7f4b703344 diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/refs/heads/track-local b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/refs/heads/track-local new file mode 100644 index 000000000..99098dc82 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/refs/heads/track-local @@ -0,0 +1 @@ +580c2111be43802dab11328176d94c391f1deae9 diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/refs/heads/treesame_as_32eab b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/refs/heads/treesame_as_32eab new file mode 100644 index 000000000..2f412c398 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/refs/heads/treesame_as_32eab @@ -0,0 +1 @@ +f705abffe7015f2beacf2abe7a36583ebee3487e diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/refs/remotes/origin/HEAD b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/refs/remotes/origin/HEAD new file mode 100644 index 000000000..b827f0c47 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/refs/remotes/origin/HEAD @@ -0,0 +1 @@ +ref: refs/remotes/origin/master diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/i-do-numbers/HEAD b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/i-do-numbers/HEAD new file mode 100644 index 000000000..43d92a0f8 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/i-do-numbers/HEAD @@ -0,0 +1 @@ +ref: refs/heads/i-do-numbers diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/i-do-numbers/ORIG_HEAD b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/i-do-numbers/ORIG_HEAD new file mode 100644 index 000000000..882969dfc --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/i-do-numbers/ORIG_HEAD @@ -0,0 +1 @@ +7252fe2da2c4dd6d85231a150d0485ec46abaa7a diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/i-do-numbers/commondir b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/i-do-numbers/commondir new file mode 100644 index 000000000..aab0408ce --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/i-do-numbers/commondir @@ -0,0 +1 @@ +../.. diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/i-do-numbers/gitdir b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/i-do-numbers/gitdir new file mode 100644 index 000000000..3644e42d1 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/i-do-numbers/gitdir @@ -0,0 +1 @@ +../../../../worktrees/i-do-numbers/.git diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/i-do-numbers/index b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/i-do-numbers/index new file mode 100644 index 000000000..d8a77a021 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/i-do-numbers/index differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/i-do-numbers/logs/HEAD b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/i-do-numbers/logs/HEAD new file mode 100644 index 000000000..f08b3ba25 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/i-do-numbers/logs/HEAD @@ -0,0 +1 @@ +7252fe2da2c4dd6d85231a150d0485ec46abaa7a 7252fe2da2c4dd6d85231a150d0485ec46abaa7a Mike Minns 1513714384 +0000 reset: moving to HEAD diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/logo/HEAD b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/logo/HEAD new file mode 100644 index 000000000..846e685a7 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/logo/HEAD @@ -0,0 +1 @@ +ref: refs/heads/logo diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/logo/ORIG_HEAD b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/logo/ORIG_HEAD new file mode 100644 index 000000000..2bbca050f --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/logo/ORIG_HEAD @@ -0,0 +1 @@ +a447ba2ca8fffd46dece72f7db6faf324afb8fcd diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/logo/commondir b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/logo/commondir new file mode 100644 index 000000000..aab0408ce --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/logo/commondir @@ -0,0 +1 @@ +../.. diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/logo/gitdir b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/logo/gitdir new file mode 100644 index 000000000..ad8863ee6 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/logo/gitdir @@ -0,0 +1 @@ +../../../../worktrees/logo/.git diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/logo/index b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/logo/index new file mode 100644 index 000000000..2b8b35b77 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/logo/index differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/logo/locked b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/logo/locked new file mode 100644 index 000000000..9f0b8bf32 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/logo/locked @@ -0,0 +1 @@ +Test lock reason diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/logo/logs/HEAD b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/logo/logs/HEAD new file mode 100644 index 000000000..ab8778340 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/logo/logs/HEAD @@ -0,0 +1 @@ +a447ba2ca8fffd46dece72f7db6faf324afb8fcd a447ba2ca8fffd46dece72f7db6faf324afb8fcd Mike Minns 1513713776 +0000 reset: moving to HEAD diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/modified_staged_file.txt b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/modified_staged_file.txt new file mode 100644 index 000000000..e68bcc7b5 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/modified_staged_file.txt @@ -0,0 +1,2 @@ +a change +more files! diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/modified_unstaged_file.txt b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/modified_unstaged_file.txt new file mode 100644 index 000000000..da6fd6537 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/modified_unstaged_file.txt @@ -0,0 +1,2 @@ +some more text +more files! more files! diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/new.txt b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/new.txt new file mode 100644 index 000000000..a71586c1d --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/new.txt @@ -0,0 +1 @@ +my new file diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/new_tracked_file.txt b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/new_tracked_file.txt new file mode 100644 index 000000000..935a81d39 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/new_tracked_file.txt @@ -0,0 +1 @@ +a new file diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/new_untracked_file.txt b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/new_untracked_file.txt new file mode 100644 index 000000000..d95f3ad14 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/new_untracked_file.txt @@ -0,0 +1 @@ +content diff --git a/LibGit2Sharp.Tests/Resources/worktree/worktrees/i-do-numbers/dot_git b/LibGit2Sharp.Tests/Resources/worktree/worktrees/i-do-numbers/dot_git new file mode 100644 index 000000000..c14c3a26c --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/worktrees/i-do-numbers/dot_git @@ -0,0 +1 @@ +gitdir: ../../testrepo_wd/.git/worktrees/i-do-numbers diff --git a/LibGit2Sharp.Tests/Resources/worktree/worktrees/i-do-numbers/numbers.txt b/LibGit2Sharp.Tests/Resources/worktree/worktrees/i-do-numbers/numbers.txt new file mode 100644 index 000000000..85e1bcbc0 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/worktrees/i-do-numbers/numbers.txt @@ -0,0 +1,17 @@ +1 +2 +3 +4 +5 +6 +7 +7.2 +8 +9 +10 +11 +12 +13 +14 +15 +16 diff --git a/LibGit2Sharp.Tests/Resources/worktree/worktrees/i-do-numbers/super-file.txt b/LibGit2Sharp.Tests/Resources/worktree/worktrees/i-do-numbers/super-file.txt new file mode 100644 index 000000000..f9ff5589e --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/worktrees/i-do-numbers/super-file.txt @@ -0,0 +1,5 @@ +That's a terrible name! +I don't like it. +People look down at me and laugh. :-( +Really!!!! +Yeah! Better! diff --git a/LibGit2Sharp.Tests/Resources/worktree/worktrees/logo/1/branch_file.txt b/LibGit2Sharp.Tests/Resources/worktree/worktrees/logo/1/branch_file.txt new file mode 100644 index 000000000..edf0effbb --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/worktrees/logo/1/branch_file.txt @@ -0,0 +1 @@ +hi diff --git a/LibGit2Sharp.Tests/Resources/worktree/worktrees/logo/README b/LibGit2Sharp.Tests/Resources/worktree/worktrees/logo/README new file mode 100644 index 000000000..ca8c64728 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/worktrees/logo/README @@ -0,0 +1 @@ +hey there diff --git a/LibGit2Sharp.Tests/Resources/worktree/worktrees/logo/branch_file.txt b/LibGit2Sharp.Tests/Resources/worktree/worktrees/logo/branch_file.txt new file mode 100644 index 000000000..edf0effbb --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/worktrees/logo/branch_file.txt @@ -0,0 +1 @@ +hi diff --git a/LibGit2Sharp.Tests/Resources/worktree/worktrees/logo/dot_git b/LibGit2Sharp.Tests/Resources/worktree/worktrees/logo/dot_git new file mode 100644 index 000000000..8295ccb37 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/worktrees/logo/dot_git @@ -0,0 +1 @@ +gitdir: ../../testrepo_wd/.git/worktrees/logo diff --git a/LibGit2Sharp.Tests/Resources/worktree/worktrees/logo/new.txt b/LibGit2Sharp.Tests/Resources/worktree/worktrees/logo/new.txt new file mode 100644 index 000000000..8e0884e36 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/worktrees/logo/new.txt @@ -0,0 +1 @@ +my new file diff --git a/LibGit2Sharp.Tests/Resources/worktree/worktrees/logo/square-logo.png b/LibGit2Sharp.Tests/Resources/worktree/worktrees/logo/square-logo.png new file mode 100644 index 000000000..b758c5bc1 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/worktrees/logo/square-logo.png differ diff --git a/LibGit2Sharp.Tests/RevertFixture.cs b/LibGit2Sharp.Tests/RevertFixture.cs index d01fb6a77..b0f12b9dc 100644 --- a/LibGit2Sharp.Tests/RevertFixture.cs +++ b/LibGit2Sharp.Tests/RevertFixture.cs @@ -21,7 +21,7 @@ public void CanRevert() using (var repo = new Repository(path)) { // Checkout the revert branch. - Branch branch = repo.Checkout(revertBranchName); + Branch branch = Commands.Checkout(repo, revertBranchName); Assert.NotNull(branch); // Revert tip commit. @@ -67,10 +67,8 @@ public void CanRevertAndNotCommit() string path = SandboxRevertTestRepo(); using (var repo = new Repository(path)) { - string modifiedFileFullPath = Path.Combine(repo.Info.WorkingDirectory, revertedFile); - // Checkout the revert branch. - Branch branch = repo.Checkout(revertBranchName); + Branch branch = Commands.Checkout(repo, revertBranchName); Assert.NotNull(branch); // Revert tip commit. @@ -112,7 +110,7 @@ public void RevertWithConflictDoesNotCommit() using (var repo = new Repository(path)) { // Checkout the revert branch. - Branch branch = repo.Checkout(revertBranchName); + Branch branch = Commands.Checkout(repo, revertBranchName); Assert.NotNull(branch); // The commit to revert - we know that reverting this @@ -150,7 +148,7 @@ public void RevertWithFileConflictStrategyOption(CheckoutFileConflictStrategy co using (var repo = new Repository(path)) { // Checkout the revert branch. - Branch branch = repo.Checkout(revertBranchName); + Branch branch = Commands.Checkout(repo, revertBranchName); Assert.NotNull(branch); // Specify FileConflictStrategy. @@ -160,6 +158,7 @@ public void RevertWithFileConflictStrategyOption(CheckoutFileConflictStrategy co }; 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); @@ -202,7 +201,7 @@ public void RevertReportsCheckoutProgress() using (var repo = new Repository(repoPath)) { // Checkout the revert branch. - Branch branch = repo.Checkout(revertBranchName); + Branch branch = Commands.Checkout(repo, revertBranchName); Assert.NotNull(branch); bool wasCalled = false; @@ -227,7 +226,7 @@ public void RevertReportsCheckoutNotification() using (var repo = new Repository(repoPath)) { // Checkout the revert branch. - Branch branch = repo.Checkout(revertBranchName); + Branch branch = Commands.Checkout(repo, revertBranchName); Assert.NotNull(branch); bool wasCalled = false; @@ -242,6 +241,7 @@ public void RevertReportsCheckoutNotification() repo.Revert(repo.Head.Tip, Constants.Signature, options); Assert.True(wasCalled); + Assert.Equal(CheckoutNotifyFlags.Updated, actualNotifyFlags); } } @@ -266,7 +266,7 @@ public void RevertFindsRenames(bool? findRenames) string repoPath = SandboxRevertTestRepo(); using (var repo = new Repository(repoPath)) { - Branch currentBranch = repo.Checkout(revertBranchName); + Branch currentBranch = Commands.Checkout(repo, revertBranchName); Assert.NotNull(currentBranch); Commit commitToRevert = repo.Lookup(commitIdToRevert); @@ -323,7 +323,7 @@ public void CanRevertMergeCommit(int mainline, string expectedId) string repoPath = SandboxRevertTestRepo(); using (var repo = new Repository(repoPath)) { - Branch branch = repo.Checkout(revertBranchName); + Branch branch = Commands.Checkout(repo, revertBranchName); Assert.NotNull(branch); Commit commitToRevert = repo.Lookup(commitIdToRevert); @@ -382,7 +382,7 @@ public void CanNotRevertAMergeCommitWithoutSpecifyingTheMainlineBranch() string repoPath = SandboxRevertTestRepo(); using (var repo = new Repository(repoPath)) { - Branch branch = repo.Checkout(revertBranchName); + Branch branch = Commands.Checkout(repo, revertBranchName); Assert.NotNull(branch); var commitToRevert = repo.Lookup(commitIdToRevert); @@ -404,7 +404,7 @@ public void RevertWithNothingToRevert(bool commitOnSuccess) using (var repo = new Repository(path)) { // Checkout the revert branch. - Branch branch = repo.Checkout(revertBranchName); + Branch branch = Commands.Checkout(repo, revertBranchName); Assert.NotNull(branch); Commit commitToRevert = repo.Head.Tip; @@ -421,7 +421,7 @@ public void RevertWithNothingToRevert(bool commitOnSuccess) new RevertOptions() { CommitOnSuccess = commitOnSuccess }); Assert.NotNull(result); - Assert.Equal(null, result.Commit); + Assert.Null(result.Commit); Assert.Equal(RevertStatus.NothingToRevert, result.Status); if (commitOnSuccess) @@ -445,7 +445,7 @@ public void RevertOrphanedBranchThrows() using (var repo = new Repository(path)) { // Checkout the revert branch. - Branch branch = repo.Checkout(revertBranchName); + Branch branch = Commands.Checkout(repo, revertBranchName); Assert.NotNull(branch); Commit commitToRevert = repo.Head.Tip; @@ -458,5 +458,70 @@ public void RevertOrphanedBranchThrows() Assert.Throws(() => repo.Revert(commitToRevert, Constants.Signature)); } } + + [Fact] + public void RevertWithNothingToRevertInObjectDatabaseSucceeds() + { + // The branch name to perform the revert on + const string revertBranchName = "refs/heads/revert"; + + string path = SandboxRevertTestRepo(); + using (var repo = new Repository(path)) + { + // Checkout the revert branch. + Branch branch = Commands.Checkout(repo, revertBranchName); + Assert.NotNull(branch); + + Commit commitToRevert = repo.Head.Tip; + + // Revert tip commit. + RevertResult result = repo.Revert(commitToRevert, Constants.Signature); + Assert.NotNull(result); + Assert.Equal(RevertStatus.Reverted, result.Status); + + var revertResult = repo.ObjectDatabase.RevertCommit(commitToRevert, repo.Branches[revertBranchName].Tip, 0, null); + + Assert.NotNull(revertResult); + Assert.Equal(MergeTreeStatus.Succeeded, revertResult.Status); + } + } + + [Fact] + public void RevertWithConflictReportsConflict() + { + // The branch name to perform the revert on, + // and the file whose contents we expect to be reverted. + const string revertBranchName = "refs/heads/revert"; + + string path = SandboxRevertTestRepo(); + using (var repo = new Repository(path)) + { + // The commit to revert - we know that reverting this + // specific commit will generate conflicts. + Commit commitToRevert = repo.Lookup("cb4f7f0eca7a0114cdafd8537332aa17de36a4e9"); + Assert.NotNull(commitToRevert); + + // Perform the revert and verify there were conflicts. + var result = repo.ObjectDatabase.RevertCommit(commitToRevert, repo.Branches[revertBranchName].Tip, 0, null); + Assert.NotNull(result); + Assert.Equal(MergeTreeStatus.Conflicts, result.Status); + Assert.Null(result.Tree); + } + } + + [Fact] + public void CanRevertInObjectDatabase() + { + // The branch name to perform the revert on + const string revertBranchName = "refs/heads/revert"; + + string path = SandboxRevertTestRepo(); + using (var repo = new Repository(path)) + { + // Revert tip commit. + var result = repo.ObjectDatabase.RevertCommit(repo.Branches[revertBranchName].Tip, repo.Branches[revertBranchName].Tip, 0, null); + Assert.Equal(MergeTreeStatus.Succeeded, result.Status); + } + } } } diff --git a/LibGit2Sharp.Tests/SetErrorFixture.cs b/LibGit2Sharp.Tests/SetErrorFixture.cs index 62bdeab86..e7e1dbed4 100644 --- a/LibGit2Sharp.Tests/SetErrorFixture.cs +++ b/LibGit2Sharp.Tests/SetErrorFixture.cs @@ -42,14 +42,18 @@ public void FormatExceptionWithInnerException() 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); @@ -104,7 +108,7 @@ private string IndentString(int level) return new string(' ', level * 4); } - #region ThrowingOdbBackend +#region ThrowingOdbBackend private class ThrowingOdbBackend : OdbBackend { @@ -176,7 +180,7 @@ public override int ForEach(ForEachCallback callback) } } - #endregion +#endregion } } diff --git a/LibGit2Sharp.Tests/StageFixture.cs b/LibGit2Sharp.Tests/StageFixture.cs index 534383953..51cb31a51 100644 --- a/LibGit2Sharp.Tests/StageFixture.cs +++ b/LibGit2Sharp.Tests/StageFixture.cs @@ -25,7 +25,7 @@ public void CanStage(string relativePath, FileStatus currentStatus, bool doesCur Assert.Equal(doesCurrentlyExistInTheIndex, (repo.Index[relativePath] != null)); Assert.Equal(currentStatus, repo.RetrieveStatus(relativePath)); - repo.Stage(relativePath); + Commands.Stage(repo, relativePath); Assert.Equal(count + expectedIndexCountVariation, repo.Index.Count); Assert.Equal(doesExistInTheIndexOnceStaged, (repo.Index[relativePath] != null)); @@ -33,6 +33,24 @@ public void CanStage(string relativePath, FileStatus currentStatus, bool doesCur } } + [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() { @@ -48,7 +66,7 @@ public void CanStageTheUpdationOfAStagedFile() Touch(repo.Info.WorkingDirectory, filename, "brand new content"); Assert.Equal(FileStatus.NewInIndex | FileStatus.ModifiedInWorkdir, repo.RetrieveStatus(filename)); - repo.Stage(filename); + Commands.Stage(repo, filename); IndexEntry newBlob = repo.Index[filename]; Assert.Equal(count, repo.Index.Count); @@ -68,7 +86,7 @@ public void StagingAnUnknownFileThrowsIfExplicitPath(string relativePath, FileSt Assert.Null(repo.Index[relativePath]); Assert.Equal(status, repo.RetrieveStatus(relativePath)); - Assert.Throws(() => repo.Stage(relativePath, new StageOptions { ExplicitPathsOptions = new ExplicitPathsOptions() })); + Assert.Throws(() => Commands.Stage(repo, relativePath, new StageOptions { ExplicitPathsOptions = new ExplicitPathsOptions() })); } } @@ -83,8 +101,8 @@ public void CanStageAnUnknownFileWithLaxUnmatchedExplicitPathsValidation(string Assert.Null(repo.Index[relativePath]); Assert.Equal(status, repo.RetrieveStatus(relativePath)); - Assert.DoesNotThrow(() => repo.Stage(relativePath)); - Assert.DoesNotThrow(() => repo.Stage(relativePath, new StageOptions { ExplicitPathsOptions = new ExplicitPathsOptions { ShouldFailOnUnmatchedPath = false } })); + Commands.Stage(repo, relativePath); + Commands.Stage(repo, relativePath, new StageOptions { ExplicitPathsOptions = new ExplicitPathsOptions { ShouldFailOnUnmatchedPath = false } }); Assert.Equal(status, repo.RetrieveStatus(relativePath)); } @@ -101,8 +119,8 @@ public void StagingAnUnknownFileWithLaxExplicitPathsValidationDoesntThrow(string Assert.Null(repo.Index[relativePath]); Assert.Equal(status, repo.RetrieveStatus(relativePath)); - repo.Stage(relativePath); - repo.Stage(relativePath, new StageOptions { ExplicitPathsOptions = new ExplicitPathsOptions { ShouldFailOnUnmatchedPath = false } }); + Commands.Stage(repo, relativePath); + Commands.Stage(repo, relativePath, new StageOptions { ExplicitPathsOptions = new ExplicitPathsOptions { ShouldFailOnUnmatchedPath = false } }); } } @@ -121,7 +139,7 @@ public void CanStageTheRemovalOfAStagedFile() File.Delete(Path.Combine(repo.Info.WorkingDirectory, filename)); Assert.Equal(FileStatus.NewInIndex | FileStatus.DeletedFromWorkdir, repo.RetrieveStatus(filename)); - repo.Stage(filename); + Commands.Stage(repo, filename); Assert.Null(repo.Index[filename]); Assert.Equal(count - 1, repo.Index.Count); @@ -145,7 +163,7 @@ public void CanStageANewFileInAPersistentManner(string filename) Assert.Equal(FileStatus.NewInWorkdir, repo.RetrieveStatus(filename)); Assert.Null(repo.Index[filename]); - repo.Stage(filename); + Commands.Stage(repo, filename); Assert.NotNull(repo.Index[filename]); Assert.Equal(FileStatus.NewInIndex, repo.RetrieveStatus(filename)); } @@ -193,7 +211,7 @@ private static void AssertStage(bool? ignorecase, IRepository repo, string path) { try { - repo.Stage(path); + Commands.Stage(repo, path); Assert.Equal(FileStatus.NewInIndex, repo.RetrieveStatus(path)); repo.Index.Replace(repo.Head.Tip); Assert.Equal(FileStatus.NewInWorkdir, repo.RetrieveStatus(path)); @@ -216,13 +234,13 @@ public void CanStageANewFileWithARelativePathContainingNativeDirectorySeparatorC Touch(repo.Info.WorkingDirectory, file, "With backward slash on Windows!"); - repo.Stage(file); + Commands.Stage(repo, file); Assert.Equal(count + 1, repo.Index.Count); const string posixifiedPath = "Project/a_file.txt"; Assert.NotNull(repo.Index[posixifiedPath]); - Assert.Equal(file, repo.Index[posixifiedPath].Path); + Assert.Equal(posixifiedPath, repo.Index[posixifiedPath].Path); } } @@ -235,7 +253,7 @@ public void StagingANewFileWithAFullPathWhichEscapesOutOfTheWorkingDirThrows() { string fullPath = Touch(scd.RootedDirectoryPath, "unit_test.txt", "some contents"); - Assert.Throws(() => repo.Stage(fullPath)); + Assert.Throws(() => Commands.Stage(repo, fullPath)); } } @@ -245,10 +263,10 @@ public void StagingFileWithBadParamsThrows() var path = SandboxStandardTestRepoGitDir(); using (var repo = new Repository(path)) { - Assert.Throws(() => repo.Stage(string.Empty)); - Assert.Throws(() => repo.Stage((string)null)); - Assert.Throws(() => repo.Stage(new string[] { })); - Assert.Throws(() => repo.Stage(new string[] { null })); + Assert.Throws(() => Commands.Stage(repo, string.Empty)); + Assert.Throws(() => Commands.Stage(repo, (string)null)); + Assert.Throws(() => Commands.Stage(repo, new string[] { })); + Assert.Throws(() => Commands.Stage(repo, new string[] { null })); } } @@ -284,7 +302,7 @@ public void CanStageWithPathspec(string relativePath, int expectedIndexCountVari { int count = repo.Index.Count; - repo.Stage(relativePath); + Commands.Stage(repo, relativePath); Assert.Equal(count + expectedIndexCountVariation, repo.Index.Count); } @@ -297,7 +315,7 @@ public void CanStageWithMultiplePathspecs() { int count = repo.Index.Count; - repo.Stage(new string[] { "*", "u*" }); + Commands.Stage(repo, new string[] { "*", "u*" }); Assert.Equal(count, repo.Index.Count); // 1 added file, 1 deleted file, so same count } @@ -314,7 +332,7 @@ public void CanIgnoreIgnoredPaths(string path) Touch(repo.Info.WorkingDirectory, path, "This file is ignored."); Assert.Equal(FileStatus.Ignored, repo.RetrieveStatus(path)); - repo.Stage("*"); + Commands.Stage(repo, "*"); Assert.Equal(FileStatus.Ignored, repo.RetrieveStatus(path)); } } @@ -330,9 +348,66 @@ public void CanStageIgnoredPaths(string path) Touch(repo.Info.WorkingDirectory, path, "This file is ignored."); Assert.Equal(FileStatus.Ignored, repo.RetrieveStatus(path)); - repo.Stage(path, new StageOptions { IncludeIgnored = true }); + 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 c630d5746..7ba379621 100644 --- a/LibGit2Sharp.Tests/StashFixture.cs +++ b/LibGit2Sharp.Tests/StashFixture.cs @@ -69,7 +69,7 @@ public void CanAddAndRemoveStash() //Remove one stash repo.Stashes.Remove(0); - Assert.Equal(1, repo.Stashes.Count()); + Assert.Single(repo.Stashes); Stash newTopStash = repo.Stashes.First(); Assert.Equal("stash@{0}", newTopStash.CanonicalName); Assert.Equal(stash.WorkTree.Sha, newTopStash.WorkTree.Sha); @@ -139,7 +139,7 @@ public void CanStashWithoutOptions() const string staged = "staged_file_path.txt"; Touch(repo.Info.WorkingDirectory, staged, "I'm staged\n"); - repo.Stage(staged); + Commands.Stage(repo, staged); Stash stash = repo.Stashes.Add(stasher, "Stash with default options", StashModifiers.Default); @@ -165,7 +165,7 @@ public void CanStashAndKeepIndex() const string filename = "staged_file_path.txt"; Touch(repo.Info.WorkingDirectory, filename, "I'm staged\n"); - repo.Stage(filename); + Commands.Stage(repo, filename); Stash stash = repo.Stashes.Add(stasher, "This stash will keep index", StashModifiers.KeepIndex); @@ -186,7 +186,7 @@ public void CanStashIgnoredFiles() const string ignoredFilename = "ignored_file.txt"; Touch(repo.Info.WorkingDirectory, gitIgnore, ignoredFilename); - repo.Stage(gitIgnore); + Commands.Stage(repo, gitIgnore); repo.Commit("Modify gitignore", Constants.Signature, Constants.Signature); Touch(repo.Info.WorkingDirectory, ignoredFilename, "I'm ignored\n"); @@ -204,6 +204,156 @@ public void CanStashIgnoredFiles() } } + [Fact] + public void CanStashAndApplyWithOptions() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + var stasher = Constants.Signature; + + const string filename = "staged_file_path.txt"; + Touch(repo.Info.WorkingDirectory, filename, "I'm staged\n"); + Commands.Stage(repo, filename); + + repo.Stashes.Add(stasher, "This stash with default options"); + Assert.Equal(StashApplyStatus.Applied, repo.Stashes.Apply(0)); + + Assert.Equal(FileStatus.NewInIndex, repo.RetrieveStatus(filename)); + Assert.Single(repo.Stashes); + + Commands.Stage(repo, filename); + + repo.Stashes.Add(stasher, "This stash with default options"); + Assert.Equal(StashApplyStatus.Applied, repo.Stashes.Apply( + 0, + new StashApplyOptions + { + ApplyModifiers = StashApplyModifiers.ReinstateIndex, + })); + + Assert.Equal(FileStatus.NewInIndex, repo.RetrieveStatus(filename)); + Assert.Equal(2, repo.Stashes.Count()); + } + } + + [Fact] + public void CanStashAndPop() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + var stasher = Constants.Signature; + + Assert.Empty(repo.Stashes); + + const string filename = "staged_file_path.txt"; + const string contents = "I'm staged"; + Touch(repo.Info.WorkingDirectory, filename, contents); + Commands.Stage(repo, filename); + + repo.Stashes.Add(stasher, "This stash with default options"); + Assert.Single(repo.Stashes); + + Assert.Equal(StashApplyStatus.Applied, repo.Stashes.Pop(0)); + Assert.Empty(repo.Stashes); + + Assert.Equal(FileStatus.NewInIndex, repo.RetrieveStatus(filename)); + Assert.Equal(contents, File.ReadAllText(Path.Combine(repo.Info.WorkingDirectory, filename))); + } + } + + [Fact] + public void StashFailsWithUncommittedChangesIntheIndex() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + var stasher = Constants.Signature; + + const string filename = "staged_file_path.txt"; + const string originalContents = "I'm pre-stash."; + const string filename2 = "unstaged_file_path.txt"; + const string newContents = "I'm post-stash."; + + Touch(repo.Info.WorkingDirectory, filename, originalContents); + Commands.Stage(repo, filename); + Touch(repo.Info.WorkingDirectory, filename2, originalContents); + + repo.Stashes.Add(stasher, "This stash with default options"); + + Touch(repo.Info.WorkingDirectory, filename, newContents); + Commands.Stage(repo, filename); + Touch(repo.Info.WorkingDirectory, filename2, newContents); + + Assert.Equal(StashApplyStatus.UncommittedChanges, repo.Stashes.Pop(0, new StashApplyOptions + { + ApplyModifiers = StashApplyModifiers.ReinstateIndex, + })); + Assert.Single(repo.Stashes); + Assert.Equal(newContents, File.ReadAllText(Path.Combine(repo.Info.WorkingDirectory, filename))); + Assert.Equal(newContents, File.ReadAllText(Path.Combine(repo.Info.WorkingDirectory, filename2))); + } + } + + [Fact] + public void StashCallsTheCallback() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + var stasher = Constants.Signature; + bool called; + + const string filename = "staged_file_path.txt"; + const string filename2 = "unstaged_file_path.txt"; + const string originalContents = "I'm pre-stash."; + + Touch(repo.Info.WorkingDirectory, filename, originalContents); + Commands.Stage(repo, filename); + Touch(repo.Info.WorkingDirectory, filename2, originalContents); + + repo.Stashes.Add(stasher, "This stash with default options"); + + called = false; + repo.Stashes.Apply(0, new StashApplyOptions + { + ProgressHandler = (progress) => { called = true; return true; } + }); + + Assert.True(called); + + repo.Reset(ResetMode.Hard); + + called = false; + repo.Stashes.Pop(0, new StashApplyOptions + { + ProgressHandler = (progress) => { called = true; return true; } + }); + + Assert.True(called); + } + } + + [Fact] + public void StashApplyReportsNotFound() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + var stasher = Constants.Signature; + + const string filename = "unstaged_file_path.txt"; + Touch(repo.Info.WorkingDirectory, filename, "I'm unstaged\n"); + + repo.Stashes.Add(stasher, "This stash with default options", StashModifiers.IncludeUntracked); + Touch(repo.Info.WorkingDirectory, filename, "I'm another unstaged\n"); + + Assert.Equal(StashApplyStatus.NotFound, repo.Stashes.Pop(1)); + Assert.Throws(() => repo.Stashes.Pop(-1)); + } + } + [Theory] [InlineData(-1)] [InlineData(-42)] diff --git a/LibGit2Sharp.Tests/StatusFixture.cs b/LibGit2Sharp.Tests/StatusFixture.cs index 773eb7e46..698639aa4 100644 --- a/LibGit2Sharp.Tests/StatusFixture.cs +++ b/LibGit2Sharp.Tests/StatusFixture.cs @@ -49,7 +49,7 @@ public void CanLimitStatusToIndexOnly(StatusShowOption show, FileStatus expected using (var repo = new Repository(clone)) { Touch(repo.Info.WorkingDirectory, "file.txt", "content"); - repo.Stage("file.txt"); + Commands.Stage(repo, "file.txt"); RepositoryStatus status = repo.RetrieveStatus(new StatusOptions() { Show = show }); Assert.Equal(expected, status["file.txt"].State); @@ -99,7 +99,7 @@ public void RetrievingTheStatusOfADirectoryThrows() var path = SandboxStandardTestRepoGitDir(); using (var repo = new Repository(path)) { - Assert.Throws(() => { FileStatus status = repo.RetrieveStatus("1"); }); + Assert.Throws(() => { repo.RetrieveStatus("1"); }); } } @@ -161,7 +161,7 @@ public void CanRetrieveTheStatusOfRenamedFilesInWorkDir() "This is a file with enough data to trigger similarity matching.\r\n" + "This is a file with enough data to trigger similarity matching.\r\n"); - repo.Stage("old_name.txt"); + Commands.Stage(repo, "old_name.txt"); File.Move(Path.Combine(repo.Info.WorkingDirectory, "old_name.txt"), Path.Combine(repo.Info.WorkingDirectory, "rename_target.txt")); @@ -188,8 +188,8 @@ public void CanRetrieveTheStatusOfRenamedFilesInIndex() Path.Combine(repo.Info.WorkingDirectory, "1.txt"), Path.Combine(repo.Info.WorkingDirectory, "rename_target.txt")); - repo.Stage("1.txt"); - repo.Stage("rename_target.txt"); + Commands.Stage(repo, "1.txt"); + Commands.Stage(repo, "rename_target.txt"); RepositoryStatus status = repo.RetrieveStatus(); @@ -210,7 +210,7 @@ public void CanDetectedVariousKindsOfRenaming() "This is a file with enough data to trigger similarity matching.\r\n" + "This is a file with enough data to trigger similarity matching.\r\n"); - repo.Stage("file.txt"); + Commands.Stage(repo, "file.txt"); repo.Commit("Initial commit", Constants.Signature, Constants.Signature); File.Move(Path.Combine(repo.Info.WorkingDirectory, "file.txt"), @@ -227,8 +227,8 @@ public void CanDetectedVariousKindsOfRenaming() // This passes as expected Assert.Equal(FileStatus.RenamedInWorkdir, status.Single().State); - repo.Stage("file.txt"); - repo.Stage("renamed.txt"); + Commands.Stage(repo, "file.txt"); + Commands.Stage(repo, "renamed.txt"); status = repo.RetrieveStatus(opts); @@ -255,20 +255,20 @@ public void CanRetrieveTheStatusOfANewRepository(bool includeUnaltered) { RepositoryStatus status = repo.RetrieveStatus(new StatusOptions() { IncludeUnaltered = includeUnaltered }); Assert.NotNull(status); - Assert.Equal(0, status.Count()); + Assert.Empty(status); Assert.False(status.IsDirty); - Assert.Equal(0, status.Untracked.Count()); - Assert.Equal(0, status.Modified.Count()); - Assert.Equal(0, status.Missing.Count()); - Assert.Equal(0, status.Added.Count()); - Assert.Equal(0, status.Staged.Count()); - Assert.Equal(0, status.Removed.Count()); + Assert.Empty(status.Untracked); + Assert.Empty(status.Modified); + Assert.Empty(status.Missing); + Assert.Empty(status.Added); + Assert.Empty(status.Staged); + Assert.Empty(status.Removed); } } [Fact] - public void RetrievingTheStatusOfARepositoryReturnNativeFilePaths() + public void RetrievingTheStatusOfARepositoryReturnsGitPaths() { // Build relative path string relFilePath = Path.Combine("directory", "Testfile.txt"); @@ -281,15 +281,15 @@ public void RetrievingTheStatusOfARepositoryReturnNativeFilePaths() Touch(repo.Info.WorkingDirectory, relFilePath, "Anybody out there?"); // Add the file to the index - repo.Stage(relFilePath); + Commands.Stage(repo, relFilePath); // Get the repository status RepositoryStatus repoStatus = repo.RetrieveStatus(); - Assert.Equal(1, repoStatus.Count()); + Assert.Single(repoStatus); StatusEntry statusEntry = repoStatus.Single(); - Assert.Equal(relFilePath, statusEntry.FilePath); + Assert.Equal(relFilePath.Replace('\\', '/'), statusEntry.FilePath); Assert.Equal(statusEntry.FilePath, repoStatus.Added.Select(s => s.FilePath).Single()); } @@ -310,7 +310,7 @@ public void RetrievingTheStatusOfAnEmptyRepositoryHonorsTheGitIgnoreDirectives() Touch(repo.Info.WorkingDirectory, ".gitignore", "*.txt" + Environment.NewLine); - RepositoryStatus newStatus = repo.RetrieveStatus(); + 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)); @@ -318,6 +318,29 @@ public void RetrievingTheStatusOfAnEmptyRepositoryHonorsTheGitIgnoreDirectives() } } + [Fact] + public void RetrievingTheStatusWithoutIncludeIgnoredIgnoresButDoesntInclude() + { + string repoPath = InitNewRepository(); + + using (var repo = new Repository(repoPath)) + { + const string relativePath = "look-ma.txt"; + Touch(repo.Info.WorkingDirectory, relativePath, "I'm going to be ignored!"); + var opt = new StatusOptions { IncludeIgnored = false }; + Assert.False(opt.IncludeIgnored); + RepositoryStatus status = repo.RetrieveStatus(opt); + Assert.Equal(new[] { relativePath }, status.Untracked.Select(s => s.FilePath)); + + Touch(repo.Info.WorkingDirectory, ".gitignore", "*.txt" + Environment.NewLine); + + RepositoryStatus newStatus = repo.RetrieveStatus(opt); + Assert.Equal(".gitignore", newStatus.Untracked.Select(s => s.FilePath).Single()); + + Assert.False(newStatus.Ignored.Any()); + } + } + [Fact] public void RetrievingTheStatusOfTheRepositoryHonorsTheGitIgnoreDirectives() { @@ -360,6 +383,7 @@ public void RetrievingTheStatusOfTheRepositoryHonorsTheGitIgnoreDirectives() RepositoryStatus status = repo.RetrieveStatus(); + relativePath = relativePath.Replace('\\', '/'); Assert.Equal(new[] { relativePath, "new_untracked_file.txt" }, status.Untracked.Select(s => s.FilePath)); Touch(repo.Info.WorkingDirectory, ".gitignore", "*.txt" + Environment.NewLine); @@ -399,7 +423,7 @@ public void RetrievingTheStatusOfTheRepositoryHonorsTheGitIgnoreDirectives() * # new_untracked_file.txt */ - RepositoryStatus newStatus = repo.RetrieveStatus(); + 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)); @@ -412,12 +436,12 @@ public void RetrievingTheStatusOfTheRepositoryHonorsTheGitIgnoreDirectives() [InlineData(false, FileStatus.DeletedFromWorkdir, FileStatus.NewInWorkdir)] public void RetrievingTheStatusOfAFilePathHonorsTheIgnoreCaseConfigurationSetting( bool shouldIgnoreCase, - FileStatus expectedlowerCasedFileStatus, - FileStatus expectedCamelCasedFileStatus + FileStatus expectedLowercaseFileStatus, + FileStatus expectedUppercaseFileStatus ) { - string lowerCasedPath; - const string lowercasedFilename = "plop"; + string lowercasePath; + const string lowercaseFileName = "plop"; string repoPath = InitNewRepository(); @@ -425,24 +449,28 @@ FileStatus expectedCamelCasedFileStatus { repo.Config.Set("core.ignorecase", shouldIgnoreCase); - lowerCasedPath = Touch(repo.Info.WorkingDirectory, lowercasedFilename); + lowercasePath = Touch(repo.Info.WorkingDirectory, lowercaseFileName); - repo.Stage(lowercasedFilename); + Commands.Stage(repo, lowercaseFileName); repo.Commit("initial", Constants.Signature, Constants.Signature); } using (var repo = new Repository(repoPath)) { - const string upercasedFilename = "Plop"; + const string uppercaseFileName = "PLOP"; + + string uppercasePath = Path.Combine(repo.Info.WorkingDirectory, uppercaseFileName); - string camelCasedPath = Path.Combine(repo.Info.WorkingDirectory, upercasedFilename); - File.Move(lowerCasedPath, camelCasedPath); + //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(expectedlowerCasedFileStatus, repo.RetrieveStatus(lowercasedFilename)); - Assert.Equal(expectedCamelCasedFileStatus, repo.RetrieveStatus(upercasedFilename)); + Assert.Equal(expectedLowercaseFileStatus, repo.RetrieveStatus(lowercaseFileName)); + Assert.Equal(expectedUppercaseFileStatus, repo.RetrieveStatus(uppercaseFileName)); - AssertStatus(shouldIgnoreCase, expectedlowerCasedFileStatus, repo, camelCasedPath.ToLowerInvariant()); - AssertStatus(shouldIgnoreCase, expectedCamelCasedFileStatus, repo, camelCasedPath.ToUpperInvariant()); + AssertStatus(shouldIgnoreCase, expectedLowercaseFileStatus, repo, uppercasePath.ToLowerInvariant()); + AssertStatus(shouldIgnoreCase, expectedUppercaseFileStatus, repo, uppercasePath.ToUpperInvariant()); } } @@ -461,8 +489,6 @@ private static void AssertStatus(bool shouldIgnoreCase, FileStatus expectedFileS [Fact] public void RetrievingTheStatusOfTheRepositoryHonorsTheGitIgnoreDirectivesThroughoutDirectories() { - char dirSep = Path.DirectorySeparatorChar; - string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { @@ -475,8 +501,8 @@ public void RetrievingTheStatusOfTheRepositoryHonorsTheGitIgnoreDirectivesThroug 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(); - Assert.Equal(new[] { "bin" + dirSep }, newStatus.Ignored.Select(s => s.FilePath)); + RepositoryStatus newStatus = repo.RetrieveStatus(new StatusOptions { IncludeIgnored = true }); + Assert.Equal(new[] { "bin/" }, newStatus.Ignored.Select(s => s.FilePath)); var sb = new StringBuilder(); sb.AppendLine("bin/*"); @@ -486,10 +512,10 @@ public void RetrievingTheStatusOfTheRepositoryHonorsTheGitIgnoreDirectivesThroug Assert.Equal(FileStatus.Ignored, repo.RetrieveStatus("bin/look-ma.txt")); Assert.Equal(FileStatus.NewInWorkdir, repo.RetrieveStatus("bin/what-about-me.txt")); - newStatus = repo.RetrieveStatus(); + newStatus = repo.RetrieveStatus(new StatusOptions { IncludeIgnored = true }); - Assert.Equal(new[] { "bin" + dirSep + "look-ma.txt" }, newStatus.Ignored.Select(s => s.FilePath)); - Assert.True(newStatus.Untracked.Select(s => s.FilePath).Contains("bin" + dirSep + "what-about-me.txt")); + Assert.Equal(new[] { "bin/look-ma.txt" }, newStatus.Ignored.Select(s => s.FilePath)); + Assert.Contains("bin/what-about-me.txt", newStatus.Untracked.Select(s => s.FilePath)); } } @@ -545,8 +571,8 @@ public void CanRetrieveTheStatusOfARelativeWorkingDirectory() Assert.Equal(2, status.Untracked.Count()); status = repo.RetrieveStatus(new StatusOptions() { PathSpec = new[] { "just_a_dir/another_dir" } }); - Assert.Equal(1, status.Count()); - Assert.Equal(1, status.Untracked.Count()); + Assert.Single(status); + Assert.Single(status.Untracked); } } @@ -603,7 +629,7 @@ public void CanIncludeStatusOfUnalteredFiles() var path = SandboxStandardTestRepo(); string[] unalteredPaths = { "1.txt", - "1" + Path.DirectorySeparatorChar + "branch_file.txt", + "1/branch_file.txt", "branch_file.txt", "new.txt", "README", @@ -614,7 +640,7 @@ public void CanIncludeStatusOfUnalteredFiles() RepositoryStatus status = repo.RetrieveStatus(new StatusOptions() { IncludeUnaltered = true }); Assert.Equal(unalteredPaths.Length, status.Unaltered.Count()); - Assert.Equal(unalteredPaths, status.Unaltered.OrderBy(s => s.FilePath).Select(s => s.FilePath).ToArray()); + Assert.Equal(unalteredPaths, status.Unaltered.OrderBy(s => s.FilePath, StringComparer.OrdinalIgnoreCase).Select(s => s.FilePath).ToArray()); } } @@ -630,7 +656,7 @@ public void UnalteredFilesDontMarkIndexAsDirty() RepositoryStatus status = repo.RetrieveStatus(new StatusOptions() { IncludeUnaltered = true }); - Assert.Equal(false, status.IsDirty); + Assert.False(status.IsDirty); Assert.Equal(9, status.Count()); } } diff --git a/LibGit2Sharp.Tests/SubmoduleFixture.cs b/LibGit2Sharp.Tests/SubmoduleFixture.cs index d60e61f3e..735bfd938 100644 --- a/LibGit2Sharp.Tests/SubmoduleFixture.cs +++ b/LibGit2Sharp.Tests/SubmoduleFixture.cs @@ -20,6 +20,26 @@ public void RetrievingSubmoduleForNormalDirectoryReturnsNull() } } + [Fact] + public void RetrievingSubmoduleInBranchShouldWork() + { + var path = SandboxSubmoduleTestRepo(); + using (var repo = new Repository(path)) + { + var submodule = repo.Submodules["sm_branch_only"]; + Assert.Null(submodule); + + Commands.Checkout(repo, "dev", new CheckoutOptions { CheckoutModifiers = CheckoutModifiers.Force }); + submodule = repo.Submodules["sm_branch_only"]; + Assert.NotNull(submodule); + Assert.NotEqual(SubmoduleStatus.Unmodified, submodule.RetrieveStatus()); + + Commands.Checkout(repo, "master", new CheckoutOptions { CheckoutModifiers = CheckoutModifiers.Force }); + submodule = repo.Submodules["sm_branch_only"]; + Assert.Null(submodule); + } + } + [Theory] [InlineData("sm_added_and_uncommited", SubmoduleStatus.InConfig | SubmoduleStatus.InIndex | SubmoduleStatus.InWorkDir | SubmoduleStatus.IndexAdded)] [InlineData("sm_changed_file", SubmoduleStatus.InConfig | SubmoduleStatus.InHead | SubmoduleStatus.InIndex | SubmoduleStatus.InWorkDir | SubmoduleStatus.WorkDirFilesModified)] @@ -29,12 +49,20 @@ public void RetrievingSubmoduleForNormalDirectoryReturnsNull() [InlineData("sm_gitmodules_only", SubmoduleStatus.InConfig)] [InlineData("sm_missing_commits", SubmoduleStatus.InConfig | SubmoduleStatus.InHead | SubmoduleStatus.InIndex | SubmoduleStatus.InWorkDir | SubmoduleStatus.WorkDirModified)] [InlineData("sm_unchanged", SubmoduleStatus.InConfig | SubmoduleStatus.InHead | SubmoduleStatus.InIndex | SubmoduleStatus.InWorkDir)] - public void CanRetrieveTheStatusOfASubmodule(string name, SubmoduleStatus expectedStatus) + [InlineData("sm_branch_only", null)] + public void CanRetrieveTheStatusOfASubmodule(string name, SubmoduleStatus? expectedStatus) { var path = 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); @@ -105,12 +133,10 @@ public void CanEnumerateRepositorySubmodules() } [Theory] - [InlineData("sm_changed_head", false)] - [InlineData("sm_changed_head", true)] - public void CanStageChangeInSubmoduleViaIndexStage(string submodulePath, bool appendPathSeparator) + [InlineData("sm_changed_head")] + [InlineData("sm_changed_head/")] + public void CanStageChangeInSubmoduleViaIndexStage(string submodulePath) { - submodulePath += appendPathSeparator ? Path.DirectorySeparatorChar : default(char?); - var path = SandboxSubmoduleTestRepo(); using (var repo = new Repository(path)) { @@ -120,7 +146,7 @@ public void CanStageChangeInSubmoduleViaIndexStage(string submodulePath, bool ap var statusBefore = submodule.RetrieveStatus(); Assert.Equal(SubmoduleStatus.WorkDirModified, statusBefore & SubmoduleStatus.WorkDirModified); - repo.Stage(submodulePath); + Commands.Stage(repo, submodulePath); var statusAfter = submodule.RetrieveStatus(); Assert.Equal(SubmoduleStatus.IndexModified, statusAfter & SubmoduleStatus.IndexModified); @@ -128,12 +154,10 @@ public void CanStageChangeInSubmoduleViaIndexStage(string submodulePath, bool ap } [Theory] - [InlineData("sm_changed_head", false)] - [InlineData("sm_changed_head", true)] - public void CanStageChangeInSubmoduleViaIndexStageWithOtherPaths(string submodulePath, bool appendPathSeparator) + [InlineData("sm_changed_head")] + [InlineData("sm_changed_head/")] + public void CanStageChangeInSubmoduleViaIndexStageWithOtherPaths(string submodulePath) { - submodulePath += appendPathSeparator ? Path.DirectorySeparatorChar : default(char?); - var path = SandboxSubmoduleTestRepo(); using (var repo = new Repository(path)) { @@ -145,7 +169,7 @@ public void CanStageChangeInSubmoduleViaIndexStageWithOtherPaths(string submodul Touch(repo.Info.WorkingDirectory, "new-file.txt"); - repo.Stage(new[] { "new-file.txt", submodulePath, "does-not-exist.txt" }); + Commands.Stage(repo, new[] { "new-file.txt", submodulePath, "does-not-exist.txt" }); var statusAfter = submodule.RetrieveStatus(); Assert.Equal(SubmoduleStatus.IndexModified, statusAfter & SubmoduleStatus.IndexModified); @@ -272,7 +296,7 @@ public void CanUpdateSubmoduleAfterCheckout() Assert.True(submodule.RetrieveStatus().HasFlag(SubmoduleStatus.InWorkDir)); - repo.Checkout("alternate"); + Commands.Checkout(repo, "alternate"); Assert.True(submodule.RetrieveStatus().HasFlag(SubmoduleStatus.WorkDirModified)); submodule = repo.Submodules[submoduleName]; diff --git a/LibGit2Sharp.Tests/TagFixture.cs b/LibGit2Sharp.Tests/TagFixture.cs index 48284a021..82a940640 100644 --- a/LibGit2Sharp.Tests/TagFixture.cs +++ b/LibGit2Sharp.Tests/TagFixture.cs @@ -13,7 +13,7 @@ public class TagFixture : BaseFixture private readonly string[] expectedTags = new[] { "e90810b", "lw", "point_to_blob", "tag_without_tagger", "test", }; private static readonly Signature signatureTim = new Signature("Tim Clem", "timothy.clem@gmail.com", TruncateSubSeconds(DateTimeOffset.UtcNow)); - private static readonly Signature signatureNtk = new Signature("nulltoken", "emeric.fermas@gmail.com", Epoch.ToDateTimeOffset(1300557894, 60)); + private static readonly Signature signatureNtk = new Signature("nulltoken", "emeric.fermas@gmail.com", DateTimeOffset.FromUnixTimeSeconds(1300557894).ToOffset(TimeSpan.FromMinutes(60))); private const string tagTestSha = "b25fa35b38051e4ae45d4222e795f9df2e43f1d1"; private const string commitE90810BSha = "e90810b8df3e80c413d903f631643c716887138d"; private const string tagE90810BSha = "7b4384978d2493e851f9cca7858815fac9b10980"; @@ -297,7 +297,7 @@ public void CanAddATagForImplicitHeadInDetachedState() string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { - repo.Checkout(repo.Head.Tip); + Commands.Checkout(repo, repo.Head.Tip); Assert.True(repo.Info.IsHeadDetached); @@ -606,12 +606,12 @@ public void RemovingATagDecreasesTheTagsCount() const string tagName = "e90810b"; List tags = repo.Tags.Select(r => r.FriendlyName).ToList(); - Assert.True(tags.Contains(tagName)); + Assert.Contains(tagName, tags); repo.Tags.Remove(tagName); List tags2 = repo.Tags.Select(r => r.FriendlyName).ToList(); - Assert.False(tags2.Contains(tagName)); + Assert.DoesNotContain(tagName, tags2); Assert.Equal(tags.Count - 1, tags2.Count); } @@ -661,7 +661,7 @@ public void CanListAllTagsInAEmptyRepository() using (var repo = new Repository(repoPath)) { Assert.True(repo.Info.IsHeadUnborn); - Assert.Equal(0, repo.Tags.Count()); + Assert.Empty(repo.Tags); } } diff --git a/LibGit2Sharp.Tests/TestHelpers/BaseFixture.cs b/LibGit2Sharp.Tests/TestHelpers/BaseFixture.cs index 017404760..51496c2f5 100644 --- a/LibGit2Sharp.Tests/TestHelpers/BaseFixture.cs +++ b/LibGit2Sharp.Tests/TestHelpers/BaseFixture.cs @@ -16,12 +16,14 @@ public class BaseFixture : IPostTestDirectoryRemover, IDisposable { private readonly List directories = new List(); -#if LEAKS_IDENTIFYING public BaseFixture() { + BuildFakeConfigs(this); + +#if LEAKS_IDENTIFYING LeaksContainer.Clear(); - } #endif + } static BaseFixture() { @@ -41,6 +43,9 @@ static BaseFixture() 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; } @@ -48,34 +53,78 @@ static BaseFixture() protected static DateTimeOffset TruncateSubSeconds(DateTimeOffset dto) { - int seconds = dto.ToSecondsSinceEpoch(); - return Epoch.ToDateTimeOffset(seconds, (int) dto.Offset.TotalMinutes); + var seconds = dto.ToUnixTimeSeconds(); + return DateTimeOffset.FromUnixTimeSeconds(seconds).ToOffset(dto.Offset); } private static void SetUpTestEnvironment() { IsFileSystemCaseSensitive = IsFileSystemCaseSensitiveInternal(); - const string sourceRelativePath = @"../../Resources"; - ResourcesDirectory = new DirectoryInfo(sourceRelativePath); + var resourcesPath = Environment.GetEnvironmentVariable("LIBGIT2SHARP_RESOURCES"); + + if (resourcesPath == null) + { + resourcesPath = Path.Combine(Directory.GetParent(new Uri(typeof(BaseFixture).GetTypeInfo().Assembly.CodeBase).LocalPath).FullName, "Resources"); + } + + ResourcesDirectory = new DirectoryInfo(resourcesPath); // Setup standard paths to our test repositories - BareTestRepoPath = Path.Combine(sourceRelativePath, "testrepo.git"); - StandardTestRepoWorkingDirPath = Path.Combine(sourceRelativePath, "testrepo_wd"); + BareTestRepoPath = Path.Combine(ResourcesDirectory.FullName, "testrepo.git"); + StandardTestRepoWorkingDirPath = Path.Combine(ResourcesDirectory.FullName, "testrepo_wd"); StandardTestRepoPath = Path.Combine(StandardTestRepoWorkingDirPath, "dot_git"); - ShallowTestRepoPath = Path.Combine(sourceRelativePath, "shallow.git"); - MergedTestRepoWorkingDirPath = Path.Combine(sourceRelativePath, "mergedrepo_wd"); - MergeRenamesTestRepoWorkingDirPath = Path.Combine(sourceRelativePath, "mergerenames_wd"); - MergeTestRepoWorkingDirPath = Path.Combine(sourceRelativePath, "merge_testrepo_wd"); - RevertTestRepoWorkingDirPath = Path.Combine(sourceRelativePath, "revert_testrepo_wd"); - SubmoduleTestRepoWorkingDirPath = Path.Combine(sourceRelativePath, "submodule_wd"); - SubmoduleTargetTestRepoWorkingDirPath = Path.Combine(sourceRelativePath, "submodule_target_wd"); - AssumeUnchangedRepoWorkingDirPath = Path.Combine(sourceRelativePath, "assume_unchanged_wd"); - SubmoduleSmallTestRepoWorkingDirPath = Path.Combine(sourceRelativePath, "submodule_small_wd"); + 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) @@ -172,6 +221,16 @@ public string SandboxSubmoduleSmallTestRepo() return path; } + public string SandboxWorktreeTestRepo() + { + return Sandbox(WorktreeTestRepoWorkingDirPath, WorktreeTestRepoWorktreesDirPath); + } + + protected string SandboxPackBuilderTestRepo() + { + return Sandbox(PackBuilderTestRepoPath); + } + protected string Sandbox(string sourceDirectoryPath, params string[] additionalSourcePaths) { var scd = BuildSelfCleaningDirectory(); @@ -198,14 +257,6 @@ protected string InitNewRepository(bool isBare = false) return Repository.Init(scd.DirectoryPath, isBare); } - protected Repository InitIsolatedRepository(string path = null, bool isBare = false, RepositoryOptions options = null) - { - path = path ?? InitNewRepository(isBare); - options = BuildFakeConfigs(BuildSelfCleaningDirectory(), options); - - return new Repository(path, options); - } - public void Register(string directoryPath) { directories.Add(directoryPath); @@ -225,7 +276,7 @@ public virtual void Dispose() if (LeaksContainer.TypeNames.Any()) { Assert.False(true, 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 SafeHandleBase.cs{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(", ", LeaksContainer.TypeNames), Environment.NewLine)); } #endif @@ -258,7 +309,7 @@ protected void RequiresDotNetOrMonoGreaterThanOrEqualTo(System.Version minimumVe throw new InvalidOperationException("Cannot access Mono.RunTime.GetDisplayName() method."); } - var version = (string) displayName.Invoke(null, null); + var version = (string)displayName.Invoke(null, null); System.Version current; @@ -284,88 +335,59 @@ protected static void AssertValueInConfigFile(string configFilePath, string rege Assert.True(r.Success, text); } - public RepositoryOptions BuildFakeConfigs(SelfCleaningDirectory scd, RepositoryOptions options = null) - { - options = BuildFakeRepositoryOptions(scd, options); - - StringBuilder sb = new StringBuilder() - .AppendFormat("[Woot]{0}", Environment.NewLine) - .AppendFormat("this-rocks = global{0}", Environment.NewLine) - .AppendFormat("[Wow]{0}", Environment.NewLine) - .AppendFormat("Man-I-am-totally-global = 42{0}", Environment.NewLine); - File.WriteAllText(options.GlobalConfigurationLocation, sb.ToString()); - - sb = new StringBuilder() - .AppendFormat("[Woot]{0}", Environment.NewLine) - .AppendFormat("this-rocks = system{0}", Environment.NewLine); - File.WriteAllText(options.SystemConfigurationLocation, sb.ToString()); - - sb = new StringBuilder() - .AppendFormat("[Woot]{0}", Environment.NewLine) - .AppendFormat("this-rocks = xdg{0}", Environment.NewLine); - File.WriteAllText(options.XdgConfigurationLocation, sb.ToString()); - - return options; - } - - private static RepositoryOptions BuildFakeRepositoryOptions(SelfCleaningDirectory scd, RepositoryOptions options = null) + private static void BuildFakeRepositoryOptions(SelfCleaningDirectory scd, out string global, out string xdg, out string system, out string programData) { - options = options ?? new RepositoryOptions(); - string confs = Path.Combine(scd.DirectoryPath, "confs"); Directory.CreateDirectory(confs); - options.GlobalConfigurationLocation = Path.Combine(confs, "my-global-config"); - options.XdgConfigurationLocation = Path.Combine(confs, "my-xdg-config"); - options.SystemConfigurationLocation = Path.Combine(confs, "my-system-config"); - - return options; + global = Path.Combine(confs, "my-global-config"); + Directory.CreateDirectory(global); + xdg = Path.Combine(confs, "my-xdg-config"); + Directory.CreateDirectory(xdg); + system = Path.Combine(confs, "my-system-config"); + Directory.CreateDirectory(system); + programData = Path.Combine(confs, "my-programdata-config"); + Directory.CreateDirectory(programData); } /// /// Creates a configuration file with user.name and user.email set to signature /// /// The configuration file will be removed automatically when the tests are finished - /// The signature to use for user.name and user.email + /// The identity to use for user.name and user.email /// The path to the configuration file - protected string CreateConfigurationWithDummyUser(Signature signature) + protected void CreateConfigurationWithDummyUser(Repository repo, Identity identity) { - return CreateConfigurationWithDummyUser(signature.Name, signature.Email); + CreateConfigurationWithDummyUser(repo, identity.Name, identity.Email); } - protected string CreateConfigurationWithDummyUser(string name, string email) + protected void CreateConfigurationWithDummyUser(Repository repo, string name, string email) { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); - Directory.CreateDirectory(scd.DirectoryPath); - string configFilePath = Path.Combine(scd.DirectoryPath, "global-config"); - - using (Configuration config = new Configuration(configFilePath)) + Configuration config = repo.Config; { if (name != null) { - config.Set("user.name", name, ConfigurationLevel.Global); + config.Set("user.name", name); } if (email != null) { - config.Set("user.email", email, ConfigurationLevel.Global); + config.Set("user.email", email); } } - - return configFilePath; } /// /// Asserts that the commit has been authored and committed by the specified signature /// /// The commit - /// The signature to compare author and commiter to - protected void AssertCommitSignaturesAre(Commit commit, Signature signature) + /// The identity to compare author and commiter to + protected void AssertCommitIdentitiesAre(Commit commit, Identity identity) { - Assert.Equal(signature.Name, commit.Author.Name); - Assert.Equal(signature.Email, commit.Author.Email); - Assert.Equal(signature.Name, commit.Committer.Name); - Assert.Equal(signature.Email, commit.Committer.Email); + Assert.Equal(identity.Name, commit.Author.Name); + Assert.Equal(identity.Email, commit.Author.Email); + Assert.Equal(identity.Name, commit.Committer.Name); + Assert.Equal(identity.Email, commit.Committer.Email); } protected static string Touch(string parent, string file, string content = null, Encoding encoding = null) @@ -374,10 +396,19 @@ protected static string Touch(string parent, string file, string content = null, string dir = Path.GetDirectoryName(filePath); Debug.Assert(dir != null); + var newFile = !File.Exists(filePath); + Directory.CreateDirectory(dir); File.WriteAllText(filePath, content ?? string.Empty, encoding ?? Encoding.ASCII); + //Workaround for .NET Core 1.x behavior where all newly created files have execute permissions set. + //https://github.com/dotnet/corefx/issues/13342 + if (Constants.IsRunningOnUnix && newFile) + { + RemoveExecutePermissions(filePath, newFile); + } + return filePath; } @@ -389,6 +420,8 @@ protected static string Touch(string parent, string file, Stream stream) string dir = Path.GetDirectoryName(filePath); Debug.Assert(dir != null); + var newFile = !File.Exists(filePath); + Directory.CreateDirectory(dir); using (var fs = File.Open(filePath, FileMode.Create)) @@ -397,9 +430,22 @@ protected static string Touch(string parent, string file, Stream stream) fs.Flush(); } + //Work around .NET Core 1.x behavior where all newly created files have execute permissions set. + //https://github.com/dotnet/corefx/issues/13342 + if (Constants.IsRunningOnUnix && newFile) + { + RemoveExecutePermissions(filePath, newFile); + } + return filePath; } + private static void RemoveExecutePermissions(string filePath, bool newFile) + { + var process = Process.Start("chmod", $"644 {filePath}"); + process.WaitForExit(); + } + protected string Expected(string filename) { return File.ReadAllText(Path.Combine(ResourcesDirectory.FullName, "expected/" + filename)); @@ -412,7 +458,7 @@ protected string Expected(string filenameFormat, params object[] args) protected static void AssertRefLogEntry(IRepository repo, string canonicalName, string message, ObjectId @from, ObjectId to, - Identity committer, DateTimeOffset when) + Identity committer, DateTimeOffset before) { var reflogEntry = repo.Refs.Log(canonicalName).First(); @@ -421,7 +467,11 @@ protected static void AssertRefLogEntry(IRepository repo, string canonicalName, Assert.Equal(@from ?? ObjectId.Zero, reflogEntry.From); Assert.Equal(committer.Email, reflogEntry.Committer.Email); - Assert.InRange(reflogEntry.Committer.When, when - TimeSpan.FromSeconds(5), when); + + // When verifying the timestamp range, give a little more room on the 'before' side. + // 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 + Assert.InRange(reflogEntry.Committer.When, before - TimeSpan.FromSeconds(1), DateTimeOffset.Now); } protected static void EnableRefLog(IRepository repository, bool enable = true) @@ -460,5 +510,10 @@ public void AssertBelongsToARepository(IRepository repo, T instance) { Assert.Same(repo, ((IBelongToARepository)instance).Repository); } + + protected void CreateAttributesFile(IRepository repo, string attributeEntry) + { + Touch(repo.Info.WorkingDirectory, ".gitattributes", attributeEntry); + } } } diff --git a/LibGit2Sharp.Tests/TestHelpers/Constants.cs b/LibGit2Sharp.Tests/TestHelpers/Constants.cs index 85f95c0fc..b5cd96d7e 100644 --- a/LibGit2Sharp.Tests/TestHelpers/Constants.cs +++ b/LibGit2Sharp.Tests/TestHelpers/Constants.cs @@ -2,6 +2,7 @@ using System.Diagnostics; using System.IO; using System.Reflection; +using System.Security; using LibGit2Sharp.Core; namespace LibGit2Sharp.Tests.TestHelpers @@ -11,7 +12,10 @@ public static class Constants public static readonly string TemporaryReposPath = BuildPath(); public const string UnknownSha = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"; 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 @@ -51,27 +55,25 @@ public static string BuildPath() { string tempPath = null; - if (IsRunningOnUnix) + 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)) { - // We're running on Mono/*nix. Let's unwrap the path - tempPath = UnwrapUnixTempPath(); + Trace.TraceInformation("Using default test path value"); + tempPath = Path.GetTempPath(); } - else + + //workaround macOS symlinking its temp folder + if (tempPath.StartsWith("/var")) { - 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(); - } + tempPath = "/private" + tempPath; } string testWorkingDirectory = Path.Combine(tempPath, "LibGit2Sharp-TestRepos"); @@ -79,22 +81,12 @@ public static string BuildPath() return testWorkingDirectory; } - private static string UnwrapUnixTempPath() - { - var type = Type.GetType("Mono.Unix.UnixPath, Mono.Posix, Version=2.0.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756"); - - return (string)type.InvokeMember("GetCompleteRealPath", - BindingFlags.Static | BindingFlags.FlattenHierarchy | - BindingFlags.InvokeMethod | BindingFlags.Public, - null, type, new object[] { Path.GetTempPath() }); - } - // To help with creating secure strings to test with. - private static System.Security.SecureString StringToSecureString(string str) + internal static SecureString StringToSecureString(string str) { var chars = str.ToCharArray(); - var secure = new System.Security.SecureString(); + var secure = new SecureString(); for (var i = 0; i < chars.Length; i++) { secure.AppendChar(chars[i]); 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 66c1d594a..636d4f198 100644 --- a/LibGit2Sharp.Tests/TestHelpers/DirectoryHelper.cs +++ b/LibGit2Sharp.Tests/TestHelpers/DirectoryHelper.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.IO; using System.Linq; +using System.Reflection; using System.Threading; namespace LibGit2Sharp.Tests.TestHelpers @@ -78,7 +79,7 @@ private static void DeleteDirectory(string directoryPath, int maxAttempts, int i { var caughtExceptionType = ex.GetType(); - if (!whitelist.Any(knownExceptionType => knownExceptionType.IsAssignableFrom(caughtExceptionType))) + if (!whitelist.Any(knownExceptionType => knownExceptionType.GetTypeInfo().IsAssignableFrom(caughtExceptionType))) { throw; } diff --git a/LibGit2Sharp.Tests/TestHelpers/FileExportFilter.cs b/LibGit2Sharp.Tests/TestHelpers/FileExportFilter.cs new file mode 100644 index 000000000..b03b8d2c1 --- /dev/null +++ b/LibGit2Sharp.Tests/TestHelpers/FileExportFilter.cs @@ -0,0 +1,96 @@ +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace LibGit2Sharp.Tests.TestHelpers +{ + class FileExportFilter : Filter + { + public int CleanCalledCount = 0; + public int CompleteCalledCount = 0; + public int SmudgeCalledCount = 0; + public readonly HashSet FilesFiltered; + + private bool clean; + + public FileExportFilter(string name, IEnumerable attributes) + : base(name, attributes) + { + FilesFiltered = new HashSet(); + } + + protected override void Create(string path, string root, FilterMode mode) + { + if (mode == FilterMode.Clean) + { + string filename = Path.GetFileName(path); + string cachePath = Path.Combine(root, ".git", filename); + + if (File.Exists(cachePath)) + { + File.Delete(cachePath); + } + } + } + + protected override void Clean(string path, string root, Stream input, Stream output) + { + CleanCalledCount++; + + string filename = Path.GetFileName(path); + string cachePath = Path.Combine(root, ".git", filename); + + using (var file = File.Exists(cachePath) ? File.Open(cachePath, FileMode.Append, FileAccess.Write, FileShare.None) : File.Create(cachePath)) + { + input.CopyTo(file); + } + + clean = true; + } + + protected override void Complete(string path, string root, Stream output) + { + CompleteCalledCount++; + + string filename = Path.GetFileName(path); + string cachePath = Path.Combine(root, ".git", filename); + + if (clean) + { + byte[] bytes = Encoding.UTF8.GetBytes(path); + output.Write(bytes, 0, bytes.Length); + FilesFiltered.Add(path); + } + else + { + if (File.Exists(cachePath)) + { + using (var file = File.Open(cachePath, FileMode.OpenOrCreate, FileAccess.Read, FileShare.None)) + { + file.CopyTo(output); + } + } + } + } + + protected override void Smudge(string path, string root, Stream input, Stream output) + { + SmudgeCalledCount++; + + StringBuilder text = new StringBuilder(); + + byte[] buffer = new byte[64 * 1024]; + int read; + while ((read = input.Read(buffer, 0, buffer.Length)) > 0) + { + string decoded = Encoding.UTF8.GetString(buffer, 0, read); + text.Append(decoded); + } + + if (!FilesFiltered.Contains(text.ToString())) + throw new FileNotFoundException(); + + clean = false; + } + } +} diff --git a/LibGit2Sharp.Tests/TestHelpers/ProcessHelper.cs b/LibGit2Sharp.Tests/TestHelpers/ProcessHelper.cs new file mode 100644 index 000000000..7c2855528 --- /dev/null +++ b/LibGit2Sharp.Tests/TestHelpers/ProcessHelper.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace LibGit2Sharp.Tests +{ + public static class ProcessHelper + { + public static (string, int) RunProcess(string fileName, string arguments, string workingDirectory = null) + { + var process = new Process + { + StartInfo = new ProcessStartInfo(fileName, arguments) + { + RedirectStandardError = true, + RedirectStandardOutput = true, + CreateNoWindow = true, + UseShellExecute = false, + WorkingDirectory = workingDirectory ?? string.Empty + } + }; + + var output = new StringBuilder(); + + process.OutputDataReceived += (_, e) => output.AppendLine(e.Data); + process.ErrorDataReceived += (_, e) => output.AppendLine(e.Data); + + process.Start(); + + process.WaitForExit(); + + return (output.ToString(), process.ExitCode); + } + } +} diff --git a/LibGit2Sharp.Tests/TestHelpers/SkippableFactAttribute.cs b/LibGit2Sharp.Tests/TestHelpers/SkippableFactAttribute.cs deleted file mode 100644 index b9904dba3..000000000 --- a/LibGit2Sharp.Tests/TestHelpers/SkippableFactAttribute.cs +++ /dev/null @@ -1,144 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Xml; -using Xunit; -using Xunit.Extensions; -using Xunit.Sdk; -//********************************************************************** -//* 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. -//********************************************************************** - -namespace LibGit2Sharp.Tests.TestHelpers -{ - class SkippableFactAttribute : FactAttribute - { - protected override IEnumerable EnumerateTestCommands(IMethodInfo method) - { - return base.EnumerateTestCommands(method).Select(SkippableTestCommand.Wrap(method)); - } - } - - class SkippableTheoryAttribute : TheoryAttribute - { - protected override IEnumerable EnumerateTestCommands(IMethodInfo method) - { - return base.EnumerateTestCommands(method).Select(SkippableTestCommand.Wrap(method)); - } - } - - class SkippableTestCommand : ITestCommand - { - public static Func Wrap(IMethodInfo method) - { - return c => new SkippableTestCommand(method, c); - } - - private readonly IMethodInfo method; - private readonly ITestCommand inner; - - private SkippableTestCommand(IMethodInfo method, ITestCommand inner) - { - this.method = method; - this.inner = inner; - } - - public MethodResult Execute(object testClass) - { - try - { - return inner.Execute(testClass); - } - catch (SkipException e) - { - return new SkipResult(method, DisplayName, e.Reason); - } - } - - public XmlNode ToStartXml() - { - return inner.ToStartXml(); - } - - public string DisplayName - { - get { return inner.DisplayName; } - } - - public bool ShouldCreateInstance - { - get { return inner.ShouldCreateInstance; } - } - - public int Timeout - { - get { return inner.Timeout; } - } - } - - class SkipException : Exception - { - public SkipException(string reason) - { - Reason = reason; - } - - public string Reason { get; private set; } - } -} diff --git a/LibGit2Sharp.Tests/TestHelpers/SubstitutionCipherFilter.cs b/LibGit2Sharp.Tests/TestHelpers/SubstitutionCipherFilter.cs new file mode 100644 index 000000000..2cba06d49 --- /dev/null +++ b/LibGit2Sharp.Tests/TestHelpers/SubstitutionCipherFilter.cs @@ -0,0 +1,47 @@ +using System.Collections.Generic; +using System.IO; + +namespace LibGit2Sharp.Tests.TestHelpers +{ + public class SubstitutionCipherFilter : Filter + { + public int CleanCalledCount = 0; + public int SmudgeCalledCount = 0; + + public SubstitutionCipherFilter(string name, IEnumerable attributes) + : base(name, attributes) + { + } + + protected override void Clean(string path, string root, Stream input, Stream output) + { + CleanCalledCount++; + RotateByThirteenPlaces(input, output); + } + + protected override void Smudge(string path, string root, Stream input, Stream output) + { + SmudgeCalledCount++; + RotateByThirteenPlaces(input, output); + } + + public static void RotateByThirteenPlaces(Stream input, Stream output) + { + int value; + + while ((value = input.ReadByte()) != -1) + { + if ((value >= 'a' && value <= 'm') || (value >= 'A' && value <= 'M')) + { + value += 13; + } + else if ((value >= 'n' && value <= 'z') || (value >= 'N' && value <= 'Z')) + { + value -= 13; + } + + output.WriteByte((byte)value); + } + } + } +} diff --git a/LibGit2Sharp.Tests/TestHelpers/TestRemoteRefs.cs b/LibGit2Sharp.Tests/TestHelpers/TestRemoteRefs.cs new file mode 100644 index 000000000..a3e1e58c4 --- /dev/null +++ b/LibGit2Sharp.Tests/TestHelpers/TestRemoteRefs.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; + +namespace LibGit2Sharp.Tests.TestHelpers +{ + public class TestRemoteRefs + { + /* + * git ls-remote http://github.com/libgit2/TestGitRepository + * 49322bb17d3acc9146f98c97d078513228bbf3c0 HEAD + * 0966a434eb1a025db6b71485ab63a3bfbea520b6 refs/heads/first-merge + * 49322bb17d3acc9146f98c97d078513228bbf3c0 refs/heads/master + * 42e4e7c5e507e113ebbb7801b16b52cf867b7ce1 refs/heads/no-parent + * d96c4e80345534eccee5ac7b07fc7603b56124cb refs/tags/annotated_tag + * c070ad8c08840c8116da865b2d65593a6bb9cd2a refs/tags/annotated_tag^{} + * 55a1a760df4b86a02094a904dfa511deb5655905 refs/tags/blob + * 8f50ba15d49353813cc6e20298002c0d17b0a9ee refs/tags/commit_tree + * 6e0c7bdb9b4ed93212491ee778ca1c65047cab4e refs/tags/nearly-dangling + */ + /// + /// Expected references on http://github.com/libgit2/TestGitRepository + /// + public static List> ExpectedRemoteRefs = new List>() + { + new Tuple("HEAD", "49322bb17d3acc9146f98c97d078513228bbf3c0"), + new Tuple("refs/heads/first-merge", "0966a434eb1a025db6b71485ab63a3bfbea520b6"), + new Tuple("refs/heads/master", "49322bb17d3acc9146f98c97d078513228bbf3c0"), + new Tuple("refs/heads/no-parent", "42e4e7c5e507e113ebbb7801b16b52cf867b7ce1"), + new Tuple("refs/tags/annotated_tag", "d96c4e80345534eccee5ac7b07fc7603b56124cb"), + new Tuple("refs/tags/annotated_tag^{}", "c070ad8c08840c8116da865b2d65593a6bb9cd2a"), + new Tuple("refs/tags/blob", "55a1a760df4b86a02094a904dfa511deb5655905"), + new Tuple("refs/tags/commit_tree", "8f50ba15d49353813cc6e20298002c0d17b0a9ee"), + new Tuple("refs/tags/nearly-dangling", "6e0c7bdb9b4ed93212491ee778ca1c65047cab4e"), + }; + } +} diff --git a/LibGit2Sharp.Tests/TreeDefinitionFixture.cs b/LibGit2Sharp.Tests/TreeDefinitionFixture.cs index 2a7aba07c..c31bd588e 100644 --- a/LibGit2Sharp.Tests/TreeDefinitionFixture.cs +++ b/LibGit2Sharp.Tests/TreeDefinitionFixture.cs @@ -142,6 +142,17 @@ public void CanAddAnExistingGitLinkTreeEntryDefinition() } } + private const string StringOf40Chars = "0123456789012345678901234567890123456789"; + + /// + /// Used to verify that windows path limitation to 260 chars is not limiting the size of + /// the keys present in the object database. + /// + private const string StringOf600Chars = + StringOf40Chars + StringOf40Chars + StringOf40Chars + StringOf40Chars + StringOf40Chars + + StringOf40Chars + StringOf40Chars + StringOf40Chars + StringOf40Chars + StringOf40Chars + + StringOf40Chars + StringOf40Chars + StringOf40Chars + StringOf40Chars + StringOf40Chars; + [Theory] [InlineData("README", "README_TOO")] [InlineData("README", "1/README")] @@ -152,7 +163,10 @@ public void CanAddAnExistingGitLinkTreeEntryDefinition() [InlineData("1/branch_file.txt", "1/2/3/another_one.txt")] [InlineData("1", "2")] [InlineData("1", "2/3")] - public void CanAddAnExistingTreeEntry(string sourcePath, string targetPath) + [InlineData("1", "C:\\/10")] + [InlineData("1", " : * ? \" < > |")] + [InlineData("1", StringOf600Chars)] + public void CanAddAndRemoveAnExistingTreeEntry(string sourcePath, string targetPath) { string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) @@ -168,6 +182,36 @@ public void CanAddAnExistingTreeEntry(string sourcePath, string targetPath) Assert.NotNull(fetched); Assert.Equal(te.Target.Id, fetched.TargetId); + + // Ensuring that the object database can handle uncommon paths. + var newTree = repo.ObjectDatabase.CreateTree(td); + Assert.Equal(newTree[targetPath].Target.Id, te.Target.Id); + + td.Remove(targetPath); + Assert.Null(td[targetPath]); + } + } + + [Theory] + [InlineData("C:\\")] + [InlineData(" : * ? \" \n < > |")] + [InlineData("a\\b")] + [InlineData("\\\\b\a")] + [InlineData("éàµ")] + [InlineData(StringOf600Chars)] + public void TreeNamesCanContainCharsForbiddenOnSomeOS(string targetName) + { + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) + { + var pointedItem = repo.Head.Tip.Tree; + + var td = new TreeDefinition(); + td.Add(targetName, pointedItem); + + var newTree = repo.ObjectDatabase.CreateTree(td); + Assert.Equal(newTree[targetName].Target.Sha, pointedItem.Sha); + Assert.Equal(newTree[targetName].Name, targetName); } } @@ -222,6 +266,49 @@ 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() { diff --git a/LibGit2Sharp.Tests/TreeFixture.cs b/LibGit2Sharp.Tests/TreeFixture.cs index f57f14063..31ca85c2d 100644 --- a/LibGit2Sharp.Tests/TreeFixture.cs +++ b/LibGit2Sharp.Tests/TreeFixture.cs @@ -82,7 +82,7 @@ public void CanEnumerateSubTrees() .Select(e => e.Target) .Cast(); - Assert.Equal(1, subTrees.Count()); + Assert.Single(subTrees); } } @@ -168,6 +168,18 @@ public void TreeDataIsPresent() } } + [Fact] + public void TreeUsesPosixStylePaths() + { + using (var repo = new Repository(BareTestRepoPath)) + { + /* From a commit tree */ + var commitTree = repo.Lookup("4c062a6").Tree; + Assert.NotNull(commitTree["1/branch_file.txt"]); + Assert.Null(commitTree["1\\branch_file.txt"]); + } + } + [Fact] public void CanRetrieveTreeEntryPath() { @@ -180,7 +192,7 @@ public void CanRetrieveTreeEntryPath() 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); diff --git a/LibGit2Sharp.Tests/UnstageFixture.cs b/LibGit2Sharp.Tests/UnstageFixture.cs index ac73cf381..a5dc143d3 100644 --- a/LibGit2Sharp.Tests/UnstageFixture.cs +++ b/LibGit2Sharp.Tests/UnstageFixture.cs @@ -25,12 +25,12 @@ public void StagingANewVersionOfAFileThenUnstagingItRevertsTheBlobToTheVersionOf string fullpath = Path.Combine(repo.Info.WorkingDirectory, filename); File.AppendAllText(fullpath, "Is there there anybody out there?"); - repo.Stage(filename); + Commands.Stage(repo, filename); Assert.Equal(count, repo.Index.Count); Assert.NotEqual((blobId), repo.Index[posixifiedFileName].Id); - repo.Unstage(posixifiedFileName); + Commands.Unstage(repo, posixifiedFileName); Assert.Equal(count, repo.Index.Count); Assert.Equal(blobId, repo.Index[posixifiedFileName].Id); @@ -50,10 +50,10 @@ public void CanStageAndUnstageAnIgnoredFile() Assert.Equal(FileStatus.Ignored, repo.RetrieveStatus(relativePath)); - repo.Stage(relativePath, new StageOptions { IncludeIgnored = true }); + Commands.Stage(repo, relativePath, new StageOptions { IncludeIgnored = true }); Assert.Equal(FileStatus.NewInIndex, repo.RetrieveStatus(relativePath)); - repo.Unstage(relativePath); + Commands.Unstage(repo, relativePath); Assert.Equal(FileStatus.Ignored, repo.RetrieveStatus(relativePath)); } } @@ -76,7 +76,7 @@ public void CanUnstage( Assert.Equal(doesCurrentlyExistInTheIndex, (repo.Index[relativePath] != null)); Assert.Equal(currentStatus, repo.RetrieveStatus(relativePath)); - repo.Unstage(relativePath); + Commands.Unstage(repo, relativePath); Assert.Equal(count + expectedIndexCountVariation, repo.Index.Count); Assert.Equal(doesExistInTheIndexOnceStaged, (repo.Index[relativePath] != null)); @@ -84,6 +84,25 @@ public void CanUnstage( } } + + [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)] @@ -93,7 +112,7 @@ public void UnstagingUnknownPathsWithStrictUnmatchedExplicitPathsValidationThrow { Assert.Equal(currentStatus, repo.RetrieveStatus(relativePath)); - Assert.Throws(() => repo.Unstage(relativePath, new ExplicitPathsOptions())); + Assert.Throws(() => Commands.Unstage(repo, relativePath, new ExplicitPathsOptions())); } } @@ -106,7 +125,8 @@ public void CanUnstageUnknownPathsWithLaxUnmatchedExplicitPathsValidation(string { Assert.Equal(currentStatus, repo.RetrieveStatus(relativePath)); - Assert.DoesNotThrow(() => repo.Unstage(relativePath, new ExplicitPathsOptions() { ShouldFailOnUnmatchedPath = false })); + Commands.Unstage(repo, relativePath, new ExplicitPathsOptions() { ShouldFailOnUnmatchedPath = false }); + Assert.Equal(currentStatus, repo.RetrieveStatus(relativePath)); } } @@ -126,7 +146,7 @@ public void CanUnstageTheRemovalOfAFile() Assert.Equal(FileStatus.DeletedFromIndex, repo.RetrieveStatus(filename)); - repo.Unstage(filename); + Commands.Unstage(repo, filename); Assert.Equal(count + 1, repo.Index.Count); Assert.Equal(FileStatus.DeletedFromWorkdir, repo.RetrieveStatus(filename)); @@ -143,14 +163,14 @@ public void CanUnstageUntrackedFileAgainstAnOrphanedHead() const string relativePath = "a.txt"; Touch(repo.Info.WorkingDirectory, relativePath, "hello test file\n"); - repo.Stage(relativePath); + Commands.Stage(repo, relativePath); - repo.Unstage(relativePath); + Commands.Unstage(repo, relativePath); RepositoryStatus status = repo.RetrieveStatus(); - Assert.Equal(0, status.Staged.Count()); - Assert.Equal(1, status.Untracked.Count()); + Assert.Empty(status.Staged); + Assert.Single(status.Untracked); - Assert.Throws(() => repo.Unstage("i-dont-exist", new ExplicitPathsOptions())); + Assert.Throws(() => Commands.Unstage(repo, "i-dont-exist", new ExplicitPathsOptions())); } } @@ -166,7 +186,7 @@ public void UnstagingUnknownPathsAgainstAnOrphanedHeadWithStrictUnmatchedExplici Assert.Equal(currentStatus, repo.RetrieveStatus(relativePath)); - Assert.Throws(() => repo.Unstage(relativePath, new ExplicitPathsOptions())); + Assert.Throws(() => Commands.Unstage(repo, relativePath, new ExplicitPathsOptions())); } } @@ -182,8 +202,9 @@ public void CanUnstageUnknownPathsAgainstAnOrphanedHeadWithLaxUnmatchedExplicitP Assert.Equal(currentStatus, repo.RetrieveStatus(relativePath)); - Assert.DoesNotThrow(() => repo.Unstage(relativePath)); - Assert.DoesNotThrow(() => repo.Unstage(relativePath, new ExplicitPathsOptions { ShouldFailOnUnmatchedPath = false })); + Commands.Unstage(repo, relativePath); + Commands.Unstage(repo, relativePath, new ExplicitPathsOptions { ShouldFailOnUnmatchedPath = false }); + Assert.Equal(currentStatus, repo.RetrieveStatus(relativePath)); } } @@ -200,7 +221,7 @@ public void UnstagingANewFileWithAFullPathWhichEscapesOutOfTheWorkingDirThrows() const string filename = "unit_test.txt"; string fullPath = Touch(di.FullName, filename, "some contents"); - Assert.Throws(() => repo.Unstage(fullPath)); + Assert.Throws(() => Commands.Unstage(repo, fullPath)); } } @@ -218,7 +239,7 @@ public void UnstagingANewFileWithAFullPathWhichEscapesOutOfTheWorkingDirAgainstA const string filename = "unit_test.txt"; string fullPath = Touch(di.FullName, filename, "some contents"); - Assert.Throws(() => repo.Unstage(fullPath)); + Assert.Throws(() => Commands.Unstage(repo, fullPath)); } } @@ -228,10 +249,10 @@ public void UnstagingFileWithBadParamsThrows() var path = SandboxStandardTestRepoGitDir(); using (var repo = new Repository(path)) { - Assert.Throws(() => repo.Unstage(string.Empty)); - Assert.Throws(() => repo.Unstage((string)null)); - Assert.Throws(() => repo.Unstage(new string[] { })); - Assert.Throws(() => repo.Unstage(new string[] { null })); + Assert.Throws(() => Commands.Unstage(repo, string.Empty)); + Assert.Throws(() => Commands.Unstage(repo, (string)null)); + Assert.Throws(() => Commands.Unstage(repo, new string[] { })); + Assert.Throws(() => Commands.Unstage(repo, new string[] { null })); } } @@ -240,17 +261,17 @@ public void CanUnstageSourceOfARename() { using (var repo = new Repository(SandboxStandardTestRepo())) { - repo.Move("branch_file.txt", "renamed_branch_file.txt"); + Commands.Move(repo, "branch_file.txt", "renamed_branch_file.txt"); RepositoryStatus oldStatus = repo.RetrieveStatus(); - Assert.Equal(1, oldStatus.RenamedInIndex.Count()); + Assert.Single(oldStatus.RenamedInIndex); Assert.Equal(FileStatus.Nonexistent, oldStatus["branch_file.txt"].State); Assert.Equal(FileStatus.RenamedInIndex, oldStatus["renamed_branch_file.txt"].State); - repo.Unstage(new string[] { "branch_file.txt" }); + Commands.Unstage(repo, new string[] { "branch_file.txt" }); RepositoryStatus newStatus = repo.RetrieveStatus(); - Assert.Equal(0, newStatus.RenamedInIndex.Count()); + Assert.Empty(newStatus.RenamedInIndex); Assert.Equal(FileStatus.DeletedFromWorkdir, newStatus["branch_file.txt"].State); Assert.Equal(FileStatus.NewInIndex, newStatus["renamed_branch_file.txt"].State); } @@ -261,16 +282,16 @@ public void CanUnstageTargetOfARename() { using (var repo = new Repository(SandboxStandardTestRepo())) { - repo.Move("branch_file.txt", "renamed_branch_file.txt"); + Commands.Move(repo, "branch_file.txt", "renamed_branch_file.txt"); RepositoryStatus oldStatus = repo.RetrieveStatus(); - Assert.Equal(1, oldStatus.RenamedInIndex.Count()); + Assert.Single(oldStatus.RenamedInIndex); Assert.Equal(FileStatus.RenamedInIndex, oldStatus["renamed_branch_file.txt"].State); - repo.Unstage(new string[] { "renamed_branch_file.txt" }); + Commands.Unstage(repo, new string[] { "renamed_branch_file.txt" }); RepositoryStatus newStatus = repo.RetrieveStatus(); - Assert.Equal(0, newStatus.RenamedInIndex.Count()); + Assert.Empty(newStatus.RenamedInIndex); Assert.Equal(FileStatus.NewInWorkdir, newStatus["renamed_branch_file.txt"].State); Assert.Equal(FileStatus.DeletedFromIndex, newStatus["branch_file.txt"].State); } @@ -281,8 +302,8 @@ public void CanUnstageBothSidesOfARename() { using (var repo = new Repository(SandboxStandardTestRepo())) { - repo.Move("branch_file.txt", "renamed_branch_file.txt"); - repo.Unstage(new string[] { "branch_file.txt", "renamed_branch_file.txt" }); + Commands.Move(repo, "branch_file.txt", "renamed_branch_file.txt"); + Commands.Unstage(repo, new string[] { "branch_file.txt", "renamed_branch_file.txt" }); RepositoryStatus status = repo.RetrieveStatus(); Assert.Equal(FileStatus.DeletedFromWorkdir, status["branch_file.txt"].State); diff --git a/LibGit2Sharp.Tests/VisualStudio.Tests.targets b/LibGit2Sharp.Tests/VisualStudio.Tests.targets deleted file mode 100644 index 53e10341f..000000000 --- a/LibGit2Sharp.Tests/VisualStudio.Tests.targets +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/LibGit2Sharp.Tests/WorktreeFixture.cs b/LibGit2Sharp.Tests/WorktreeFixture.cs new file mode 100644 index 000000000..5a706515b --- /dev/null +++ b/LibGit2Sharp.Tests/WorktreeFixture.cs @@ -0,0 +1,298 @@ +using LibGit2Sharp.Tests.TestHelpers; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +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() + { + 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()); + } + } + + [Fact] + public void CanAddLockedWorktree() + { + 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()); + } + } + + [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()); + } + } + } +} diff --git a/LibGit2Sharp.Tests/app.config b/LibGit2Sharp.Tests/app.config deleted file mode 100644 index 5ab38dff7..000000000 --- a/LibGit2Sharp.Tests/app.config +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/LibGit2Sharp.Tests/ShadowCopyFixture.cs b/LibGit2Sharp.Tests/desktop/ShadowCopyFixture.cs similarity index 91% rename from LibGit2Sharp.Tests/ShadowCopyFixture.cs rename to LibGit2Sharp.Tests/desktop/ShadowCopyFixture.cs index f394e987e..d9618c06c 100644 --- a/LibGit2Sharp.Tests/ShadowCopyFixture.cs +++ b/LibGit2Sharp.Tests/desktop/ShadowCopyFixture.cs @@ -11,6 +11,7 @@ namespace LibGit2Sharp.Tests public class ShadowCopyFixture : BaseFixture { [Fact] + [Trait("TestCategory", "FailsWhileInstrumented")] public void CanProbeForNativeBinariesFromAShadowCopiedAssembly() { Type type = typeof(Wrapper); @@ -55,18 +56,18 @@ public void CanProbeForNativeBinariesFromAShadowCopiedAssembly() // ...that the assembly in the other domain is stored in the shadow copy cache... string cachedAssembliesPath = Path.Combine(setup.CachePath, setup.ApplicationName); - Assert.True(cachedAssemblyLocation.StartsWith(cachedAssembliesPath)); + Assert.StartsWith(cachedAssembliesPath, cachedAssemblyLocation); if (!Constants.IsRunningOnUnix) { - // ...that this cache doesn't contain the `NativeBinaries` folder + // ...that this cache doesn't contain the `lib` folder string cachedAssemblyParentPath = Path.GetDirectoryName(cachedAssemblyLocation); - Assert.False(Directory.Exists(Path.Combine(cachedAssemblyParentPath, "NativeBinaries"))); + Assert.False(Directory.Exists(Path.Combine(cachedAssemblyParentPath, "lib"))); - // ...whereas `NativeBinaries` of course exists next to the source assembly + // ...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, "NativeBinaries"))); + Assert.True(Directory.Exists(Path.Combine(sourceAssemblyParentPath, "lib"))); } AppDomain.Unload(domain); diff --git a/LibGit2Sharp.Tests/SmartSubtransportFixture.cs b/LibGit2Sharp.Tests/desktop/SmartSubtransportFixture.cs similarity index 72% rename from LibGit2Sharp.Tests/SmartSubtransportFixture.cs rename to LibGit2Sharp.Tests/desktop/SmartSubtransportFixture.cs index 9d71d3f3a..e72c0d7c1 100644 --- a/LibGit2Sharp.Tests/SmartSubtransportFixture.cs +++ b/LibGit2Sharp.Tests/desktop/SmartSubtransportFixture.cs @@ -4,6 +4,7 @@ using System.Net; using System.Net.Security; using LibGit2Sharp.Tests.TestHelpers; +using LibGit2Sharp.Core; using Xunit; using Xunit.Extensions; @@ -38,9 +39,9 @@ public void CustomSmartSubtransportTest(string scheme, string url) registration = GlobalSettings.RegisterSmartSubtransport(scheme); Assert.NotNull(registration); - using (var repo = new Repository(scd.DirectoryPath)) + using (var repo = new Repository(repoPath)) { - Remote remote = repo.Network.Remotes.Add(remoteName, url); + repo.Network.Remotes.Add(remoteName, url); // Set up structures for the expected results // and verifying the RemoteUpdateTips callback. @@ -62,7 +63,62 @@ public void CustomSmartSubtransportTest(string scheme, string url) } // Perform the actual fetch - repo.Network.Fetch(remote, new FetchOptions { OnUpdateTips = expectedFetchState.RemoteUpdateTipsHandler, TagFetchMode = TagFetchMode.Auto }); + Commands.Fetch(repo, remoteName, new string[0], + 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); @@ -203,6 +259,8 @@ public override int Write(Stream dataStream, long length) private static HttpWebRequest CreateWebRequest(string endpointUrl, bool isPost, string contentType) { + ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; + HttpWebRequest webRequest = (HttpWebRequest)HttpWebRequest.Create(endpointUrl); webRequest.UserAgent = "git/1.0 (libgit2 custom transport)"; webRequest.ServicePoint.Expect100Continue = false; @@ -234,7 +292,31 @@ private HttpWebResponse GetResponseWithRedirects() } } - response = (HttpWebResponse)request.GetResponse(); + try + { + response = (HttpWebResponse)request.GetResponse(); + } + catch (WebException ex) + { + response = ex.Response as HttpWebResponse; + if (response.StatusCode == HttpStatusCode.Unauthorized) + { + Credentials cred; + int ret = SmartTransport.AcquireCredentials(out cred, null, typeof(UsernamePasswordCredentials)); + if (ret != 0) + { + throw new InvalidOperationException("dunno"); + } + + request = CreateWebRequest(EndpointUrl, IsPost, ContentType); + UsernamePasswordCredentials userpass = (UsernamePasswordCredentials)cred; + request.Credentials = new NetworkCredential(userpass.Username, userpass.Password); + continue; + } + + // rethrow if it's not 401 + throw ex; + } if (response.StatusCode == HttpStatusCode.Moved || response.StatusCode == HttpStatusCode.Redirect) { @@ -242,6 +324,7 @@ private HttpWebResponse GetResponseWithRedirects() continue; } + break; } diff --git a/LibGit2Sharp.Tests/packages.config b/LibGit2Sharp.Tests/packages.config deleted file mode 100644 index 973dbbaa8..000000000 --- a/LibGit2Sharp.Tests/packages.config +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - 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 765e8dd01..8c3d7f46f 100644 --- a/LibGit2Sharp.sln +++ b/LibGit2Sharp.sln @@ -1,15 +1,23 @@ - -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 16 +VisualStudioVersion = 16.0.28803.202 +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}") = ".nuget", ".nuget", "{19D079A4-A273-4630-B2D2-79EADE3E7CA1}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0CA739FD-DA4D-4F64-9834-DA14A3ECD04B}" ProjectSection(SolutionItems) = preProject - .nuget\packages.config = .nuget\packages.config + .gitignore = .gitignore + Targets\CodeGenerator.targets = Targets\CodeGenerator.targets + Directory.Build.props = Directory.Build.props + Targets\GenerateNativeDllName.targets = Targets\GenerateNativeDllName.targets + nuget.config = nuget.config + version.json = version.json EndProjectSection 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 @@ -24,33 +32,15 @@ Global {286E63EB-04DD-4ADE-88D6-041B57800761}.Debug|Any CPU.Build.0 = Debug|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 + {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 - Policies = $0 - $0.TextStylePolicy = $1 - $1.inheritsSet = null - $1.scope = text/x-csharp - $0.CSharpFormattingPolicy = $2 - $2.IndentSwitchBody = True - $2.BeforeMethodCallParentheses = False - $2.BeforeMethodDeclarationParentheses = False - $2.BeforeConstructorDeclarationParentheses = False - $2.BeforeDelegateDeclarationParentheses = False - $2.NewParentheses = False - $2.inheritsSet = Mono - $2.inheritsScope = text/x-csharp - $2.scope = text/x-csharp - $0.StandardHeader = $3 - $3.Text = - $3.inheritsSet = Apache2License - $0.TextStylePolicy = $4 - $4.FileWidth = 120 - $4.RemoveTrailingWhitespace = True - $4.inheritsSet = VisualStudio - $4.inheritsScope = text/plain - $4.scope = text/plain + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {9BD5F77D-E47D-4621-9AA0-8598766902B9} EndGlobalSection EndGlobal diff --git a/LibGit2Sharp.sln.DotSettings b/LibGit2Sharp.sln.DotSettings deleted file mode 100644 index 8bc2282a8..000000000 --- a/LibGit2Sharp.sln.DotSettings +++ /dev/null @@ -1,17 +0,0 @@ - - <?xml version="1.0" encoding="utf-16"?><Profile name="LibGit2Sharp"><CSArrangeThisQualifier>True</CSArrangeThisQualifier><RemoveCodeRedundancies>True</RemoveCodeRedundancies><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode></Profile> - TOGETHER - ALWAYS_ADD - ALWAYS_ADD - ALWAYS_ADD - ALWAYS_ADD - ALWAYS_ADD - True - False - True - True - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - True - True - True - diff --git a/LibGit2Sharp/AfterRebaseStepInfo.cs b/LibGit2Sharp/AfterRebaseStepInfo.cs new file mode 100644 index 000000000..8e6e78e2d --- /dev/null +++ b/LibGit2Sharp/AfterRebaseStepInfo.cs @@ -0,0 +1,61 @@ +namespace LibGit2Sharp +{ + /// + /// Information about a rebase step that was just completed. + /// + public class AfterRebaseStepInfo + { + /// + /// Needed for mocking. + /// + protected AfterRebaseStepInfo() + { } + + internal AfterRebaseStepInfo(RebaseStepInfo stepInfo, Commit commit, long completedStepIndex, long totalStepCount) + { + StepInfo = stepInfo; + Commit = commit; + WasPatchAlreadyApplied = false; + CompletedStepIndex = completedStepIndex; + TotalStepCount = totalStepCount; + } + + /// + /// Constructor to call when the patch has already been applied for this step. + /// + /// + /// + /// + internal AfterRebaseStepInfo(RebaseStepInfo stepInfo, long completedStepIndex, long totalStepCount) + : this (stepInfo, null, completedStepIndex, totalStepCount) + { + WasPatchAlreadyApplied = true; + } + + /// + /// The info on the completed step. + /// + public virtual RebaseStepInfo StepInfo { get; private set; } + + /// + /// The commit generated by the step, if any. + /// + public virtual Commit Commit { get; private set; } + + /// + /// Was the changes for this step already applied. If so, + /// will be null. + /// + public virtual bool WasPatchAlreadyApplied { get; private set; } + + /// + /// The index of the step that was just completed. + /// + public virtual long CompletedStepIndex { get; private set; } + + /// + /// The total number of steps in the rebase operation. + /// + public virtual long TotalStepCount { get; private set; } + } +} diff --git a/LibGit2Sharp/AmbiguousSpecificationException.cs b/LibGit2Sharp/AmbiguousSpecificationException.cs index 3b9024cf8..16c77f6df 100644 --- a/LibGit2Sharp/AmbiguousSpecificationException.cs +++ b/LibGit2Sharp/AmbiguousSpecificationException.cs @@ -1,3 +1,4 @@ +using LibGit2Sharp.Core; using System; using System.Runtime.Serialization; @@ -7,14 +8,13 @@ namespace LibGit2Sharp /// The exception that is thrown when the provided specification cannot uniquely identify a reference, an object or a path. /// [Serializable] - public class AmbiguousSpecificationException : LibGit2SharpException + public class AmbiguousSpecificationException : NativeException { /// /// Initializes a new instance of the class. /// public AmbiguousSpecificationException() - { - } + { } /// /// Initializes a new instance of the class with a specified error message. @@ -22,6 +22,15 @@ public AmbiguousSpecificationException() /// A message that describes the error. public AmbiguousSpecificationException(string message) : base(message) + { } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// A composite format string for use in . + /// An object array that contains zero or more objects to format. + public AmbiguousSpecificationException(string format, params object[] args) + : base(String.Format(format, args)) { } @@ -32,8 +41,7 @@ public AmbiguousSpecificationException(string message) /// The exception that is the cause of the current exception. If the parameter is not a null reference, the current exception is raised in a catch block that handles the inner exception. public AmbiguousSpecificationException(string message, Exception innerException) : base(message, innerException) - { - } + { } /// /// Initializes a new instance of the class with a serialized data. @@ -42,7 +50,14 @@ public AmbiguousSpecificationException(string message, Exception innerException) /// The that contains contextual information about the source or destination. protected AmbiguousSpecificationException(SerializationInfo info, StreamingContext context) : base(info, context) + { } + + internal override GitErrorCode ErrorCode { + get + { + return GitErrorCode.Ambiguous; + } } } } diff --git a/LibGit2Sharp/AuthenticationException.cs b/LibGit2Sharp/AuthenticationException.cs new file mode 100644 index 000000000..acbf331ff --- /dev/null +++ b/LibGit2Sharp/AuthenticationException.cs @@ -0,0 +1,55 @@ +using System; +using System.Runtime.Serialization; +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + /// + /// The exception that is thrown when an operation which requires an + /// authentication fails. + /// + [Serializable] + public class AuthenticationException : LibGit2SharpException + { + /// + /// Initializes a new instance of the class. + /// + public AuthenticationException() + { + } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// A message that describes the error. + public AuthenticationException(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 AuthenticationException(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 AuthenticationException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + internal AuthenticationException(string message, GitErrorCode code, GitErrorCategory category) + : base(message, code, category) + { + } + } +} diff --git a/LibGit2Sharp/BareRepositoryException.cs b/LibGit2Sharp/BareRepositoryException.cs index 00b61a04b..7ee830a0c 100644 --- a/LibGit2Sharp/BareRepositoryException.cs +++ b/LibGit2Sharp/BareRepositoryException.cs @@ -9,14 +9,13 @@ namespace LibGit2Sharp /// working directory is performed against a bare repository. /// [Serializable] - public class BareRepositoryException : LibGit2SharpException + public class BareRepositoryException : NativeException { /// /// Initializes a new instance of the class. /// public BareRepositoryException() - { - } + { } /// /// Initializes a new instance of the class with a specified error message. @@ -24,8 +23,16 @@ public BareRepositoryException() /// A message that describes the error. public BareRepositoryException(string message) : base(message) - { - } + { } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// A composite format string for use in . + /// An object array that contains zero or more objects to format. + public BareRepositoryException(string format, params object[] args) + : base(format, args) + { } /// /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. @@ -34,8 +41,7 @@ public BareRepositoryException(string message) /// The exception that is the cause of the current exception. If the parameter is not a null reference, the current exception is raised in a catch block that handles the inner exception. public BareRepositoryException(string message, Exception innerException) : base(message, innerException) - { - } + { } /// /// Initializes a new instance of the class with a serialized data. @@ -44,12 +50,18 @@ public BareRepositoryException(string message, Exception innerException) /// The that contains contextual information about the source or destination. protected BareRepositoryException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } + { } + + internal BareRepositoryException(string message, GitErrorCategory category) + : base(message, category) + { } - internal BareRepositoryException(string message, GitErrorCode code, GitErrorCategory category) - : base(message, code, category) + internal override GitErrorCode ErrorCode { + get + { + return GitErrorCode.BareRepo; + } } } } diff --git a/LibGit2Sharp/BeforeRebaseStepInfo.cs b/LibGit2Sharp/BeforeRebaseStepInfo.cs new file mode 100644 index 000000000..e01175c08 --- /dev/null +++ b/LibGit2Sharp/BeforeRebaseStepInfo.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace LibGit2Sharp +{ + /// + /// Information about a rebase step that is about to be performed. + /// + public class BeforeRebaseStepInfo + { + /// + /// Needed for mocking. + /// + protected BeforeRebaseStepInfo() + { } + + internal BeforeRebaseStepInfo(RebaseStepInfo stepInfo, long stepIndex, long totalStepCount) + { + StepInfo = stepInfo; + StepIndex = stepIndex; + TotalStepCount = totalStepCount; + } + + /// + /// Information on the step that is about to be performed. + /// + public virtual RebaseStepInfo StepInfo { get; private set; } + + /// + /// The index of the step that is to be run. + /// + public virtual long StepIndex { get; private set; } + + /// + /// The total number of steps in the rebase operation. + /// + public virtual long TotalStepCount { get; private set; } + } +} diff --git a/LibGit2Sharp/BlameHunk.cs b/LibGit2Sharp/BlameHunk.cs index 79dbf3945..553efb14e 100644 --- a/LibGit2Sharp/BlameHunk.cs +++ b/LibGit2Sharp/BlameHunk.cs @@ -19,37 +19,43 @@ public class BlameHunk : IEquatable x => x.InitialSignature, x => x.InitialCommit); - - internal BlameHunk(IRepository repository, GitBlameHunk rawHunk) + internal unsafe BlameHunk(IRepository repository, git_blame_hunk* rawHunk) { - finalCommit = new Lazy(() => repository.Lookup(rawHunk.FinalCommitId)); - origCommit = new Lazy(() => repository.Lookup(rawHunk.OrigCommitId)); + var origId = ObjectId.BuildFromPtr(&rawHunk->orig_commit_id); + var finalId = ObjectId.BuildFromPtr(&rawHunk->final_commit_id); + + finalCommit = new Lazy(() => repository.Lookup(finalId)); + origCommit = new Lazy(() => repository.Lookup(origId)); + - if (rawHunk.OrigPath != IntPtr.Zero) + if (rawHunk->orig_path != null) { - InitialPath = LaxUtf8Marshaler.FromNative(rawHunk.OrigPath); + InitialPath = LaxUtf8Marshaler.FromNative(rawHunk->orig_path); } - LineCount = rawHunk.LinesInHunk; + + LineCount = (int)rawHunk->lines_in_hunk.ToUInt32(); // Libgit2's line numbers are 1-based - FinalStartLineNumber = rawHunk.FinalStartLineNumber - 1; - InitialStartLineNumber = rawHunk.OrigStartLineNumber - 1; + FinalStartLineNumber = (int)rawHunk->final_start_line_number.ToUInt32() - 1; + InitialStartLineNumber = (int)rawHunk->orig_start_line_number.ToUInt32() - 1; // Signature objects need to have ownership of their native pointers - if (rawHunk.FinalSignature != IntPtr.Zero) + if (rawHunk->final_signature != null) { - FinalSignature = new Signature(Proxy.git_signature_dup(rawHunk.FinalSignature)); + FinalSignature = new Signature(rawHunk->final_signature); } - if (rawHunk.OrigSignature != IntPtr.Zero) + + if (rawHunk->orig_signature != null) { - InitialSignature = new Signature(Proxy.git_signature_dup(rawHunk.OrigSignature)); + InitialSignature = new Signature(rawHunk->orig_signature); } } /// /// For easier mocking /// - protected BlameHunk() { } + protected BlameHunk() + { } /// /// Determine if this hunk contains a given line. diff --git a/LibGit2Sharp/BlameHunkCollection.cs b/LibGit2Sharp/BlameHunkCollection.cs index f487915cd..70b2f89b4 100644 --- a/LibGit2Sharp/BlameHunkCollection.cs +++ b/LibGit2Sharp/BlameHunkCollection.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Runtime.InteropServices; using LibGit2Sharp.Core; using LibGit2Sharp.Core.Handles; @@ -20,24 +21,42 @@ public class BlameHunkCollection : IEnumerable /// protected BlameHunkCollection() { } - internal BlameHunkCollection(Repository repo, RepositorySafeHandle repoHandle, string path, BlameOptions options) + internal unsafe BlameHunkCollection(Repository repo, RepositoryHandle repoHandle, string path, BlameOptions options) { this.repo = repo; - var rawopts = new GitBlameOptions + var rawopts = new git_blame_options { version = 1, + FindOptions = new GitDiffFindOptions { + Version = 1, + }, flags = options.Strategy.ToGitBlameOptionFlags(), - MinLine = (uint)options.MinLine, - MaxLine = (uint)options.MaxLine, + min_line = new UIntPtr((uint)options.MinLine), + max_line = new UIntPtr((uint)options.MaxLine), }; + + if (options.FindNoRenames) + rawopts.FindOptions.Flags = GitDiffFindFlags.GIT_DIFF_FIND_NO_RENAMES; + else if (options.FindExactRenames) + rawopts.FindOptions.Flags = GitDiffFindFlags.GIT_DIFF_FIND_EXACT_MATCH_ONLY; + else + rawopts.FindOptions.Flags = GitDiffFindFlags.GIT_DIFF_FIND_RENAMES; + if (options.StartingAt != null) { - rawopts.NewestCommit = repo.Committish(options.StartingAt).Oid; + fixed (byte* p = rawopts.newest_commit.Id) + { + Marshal.Copy(repo.Committish(options.StartingAt).Oid.Id, 0, new IntPtr(p), git_oid.Size); + } } + if (options.StoppingAt != null) { - rawopts.OldestCommit = repo.Committish(options.StoppingAt).Oid; + fixed (byte* p = rawopts.oldest_commit.Id) + { + Marshal.Copy(repo.Committish(options.StoppingAt).Oid.Id, 0, new IntPtr(p), git_oid.Size); + } } using (var blameHandle = Proxy.git_blame_file(repoHandle, path, rawopts)) diff --git a/LibGit2Sharp/BlameOptions.cs b/LibGit2Sharp/BlameOptions.cs index c39a3f536..dfd0f41da 100644 --- a/LibGit2Sharp/BlameOptions.cs +++ b/LibGit2Sharp/BlameOptions.cs @@ -6,7 +6,7 @@ public enum BlameStrategy { /// - /// Track renames of the file, but no block movement. + /// Track renames of the file using diff rename detection, but no block movement. /// Default, @@ -58,5 +58,15 @@ public sealed class BlameOptions /// If this is set to 0, blame ends with the last line in the file. /// public int MaxLine { get; set; } + + /// + /// Disables rename heuristics, only matching renames on unmodified files. + /// + public bool FindExactRenames { get; set; } + + /// + /// Fully disable rename checking. + /// + public bool FindNoRenames { get; set; } } } diff --git a/LibGit2Sharp/Blob.cs b/LibGit2Sharp/Blob.cs index 187532b7f..9b14cb50f 100644 --- a/LibGit2Sharp/Blob.cs +++ b/LibGit2Sharp/Blob.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Text; using LibGit2Sharp.Core; namespace LibGit2Sharp @@ -28,7 +29,7 @@ internal Blob(Repository repo, ObjectId id) /// /// 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.ObjectMetadata.RetrieveObjectMetadata(Blob.Id).Size + /// In order to read blob size from header, Repository.ObjectDatabase.RetrieveObjectMetadata(Blob.Id).Size /// can be used. /// /// @@ -55,7 +56,64 @@ public virtual Stream GetContentStream() 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. + 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. + 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. + public virtual string GetContentText(FilteringOptions filteringOptions) + { + return GetContentText(filteringOptions, null); + } + + /// + /// Gets the blob content as it would be checked out to the + /// working directory, decoded with the specified encoding, + /// or according to byte order marks, with UTF8 as fallback, + /// if is null. + /// + /// Parameter controlling content filtering behavior + /// The encoding of the text. (default: detected or UTF8) + /// Blob content as text. + public virtual string GetContentText(FilteringOptions filteringOptions, Encoding encoding) + { + Ensure.ArgumentNotNull(filteringOptions, "filteringOptions"); + + return ReadToEnd(GetContentStream(filteringOptions), encoding); + } + + private static string ReadToEnd(Stream stream, Encoding encoding) + { + using (var reader = new StreamReader(stream, encoding ?? LaxUtf8Marshaler.Encoding, encoding == null)) + { + return reader.ReadToEnd(); + } + } } } diff --git a/LibGit2Sharp/BlobExtensions.cs b/LibGit2Sharp/BlobExtensions.cs deleted file mode 100644 index 7dade3ced..000000000 --- a/LibGit2Sharp/BlobExtensions.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System.IO; -using System.Text; -using LibGit2Sharp.Core; - -namespace LibGit2Sharp -{ - /// - /// Provides helper overloads to a . - /// - public static class BlobExtensions - { - /// - /// Gets the blob content, decoded with UTF8 encoding if the encoding cannot be detected from the byte order mark - /// - /// The blob for which the content will be returned. - /// Blob content as text. - public static string GetContentText(this Blob blob) - { - Ensure.ArgumentNotNull(blob, "blob"); - - return ReadToEnd(blob.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 blob for which the content will be returned. - /// The encoding of the text to use, if it cannot be detected - /// Blob content as text. - public static string GetContentText(this Blob blob, Encoding encoding) - { - Ensure.ArgumentNotNull(blob, "blob"); - Ensure.ArgumentNotNull(encoding, "encoding"); - - return ReadToEnd(blob.GetContentStream(), encoding); - } - - /// - /// Gets the blob content, decoded with UTF8 encoding if the encoding cannot be detected - /// - /// The blob for which the content will be returned. - /// Parameter controlling content filtering behavior - /// Blob content as text. - public static string GetContentText(this Blob blob, FilteringOptions filteringOptions) - { - return blob.GetContentText(filteringOptions, null); - } - - /// - /// Gets the blob content as it would be checked out to the - /// working directory, decoded with the specified encoding, - /// or according to byte order marks, with UTF8 as fallback, - /// if is null. - /// - /// The blob for which the content will be returned. - /// Parameter controlling content filtering behavior - /// The encoding of the text. (default: detected or UTF8) - /// Blob content as text. - public static string GetContentText(this Blob blob, FilteringOptions filteringOptions, Encoding encoding) - { - Ensure.ArgumentNotNull(blob, "blob"); - Ensure.ArgumentNotNull(filteringOptions, "filteringOptions"); - - return ReadToEnd(blob.GetContentStream(filteringOptions), encoding); - } - - private static string ReadToEnd(Stream stream, Encoding encoding) - { - using (var reader = new StreamReader(stream, encoding ?? LaxUtf8Marshaler.Encoding, encoding == null)) - { - return reader.ReadToEnd(); - } - } - } -} diff --git a/LibGit2Sharp/Branch.cs b/LibGit2Sharp/Branch.cs index bb8c45c67..d023e6153 100644 --- a/LibGit2Sharp/Branch.cs +++ b/LibGit2Sharp/Branch.cs @@ -25,8 +25,7 @@ protected Branch() /// The full name of the reference internal Branch(Repository repo, Reference reference, string canonicalName) : this(repo, reference, _ => canonicalName) - { - } + { } /// /// Initializes a new instance of an orphaned class. @@ -38,8 +37,7 @@ internal Branch(Repository repo, Reference reference, string canonicalName) /// The reference. internal Branch(Repository repo, Reference reference) : this(repo, reference, r => r.TargetIdentifier) - { - } + { } private Branch(Repository repo, Reference reference, Func canonicalNameSelector) : base(repo, reference, canonicalNameSelector) @@ -108,7 +106,15 @@ public virtual BranchTrackingDetails TrackingDetails /// public virtual bool IsCurrentRepositoryHead { - get { return repo.Head == this; } + get + { + if (this is DetachedHead) + { + return repo.Head.Reference.TargetIdentifier == this.Reference.TargetIdentifier; + } + + return repo.Head.Reference.TargetIdentifier == this.CanonicalName; + } } /// @@ -124,7 +130,7 @@ public virtual Commit Tip /// public virtual ICommitLog Commits { - get { return repo.Commits.QueryBy(new CommitFilter { Since = this }); } + get { return repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = this }); } } /// @@ -140,7 +146,10 @@ public virtual string UpstreamBranchCanonicalName { if (IsRemote) { - return Remote.FetchSpecTransformToSource(CanonicalName); + using (var remote = repo.Network.Remotes.RemoteForName(RemoteName)) + { + return remote.FetchSpecTransformToSource(CanonicalName); + } } return UpstreamBranchCanonicalNameFromLocalBranch(); @@ -148,35 +157,22 @@ public virtual string UpstreamBranchCanonicalName } /// - /// Get the remote for the branch. + /// Get the name of the remote for the branch. /// /// If this is a local branch, this will return the configured /// to fetch from and push to. If this is a - /// remote-tracking branch, this will return the remote containing - /// the tracked branch. + /// remote-tracking branch, this will return the name of the remote + /// containing the tracked branch. If there no tracking information + /// this will return null. /// /// - public virtual Remote Remote + public virtual string RemoteName { get { - string remoteName; - - if (IsRemote) - { - remoteName = RemoteNameFromRemoteTrackingBranch(); - } - else - { - remoteName = RemoteNameFromLocalBranch(); - } - - if (remoteName == null) - { - return null; - } - - return repo.Network.Remotes[remoteName]; + return IsRemote + ? RemoteNameFromRemoteTrackingBranch() + : RemoteNameFromLocalBranch(); } } @@ -264,9 +260,9 @@ protected override string Shorten() return CanonicalName.Substring(Reference.RemoteTrackingBranchPrefix.Length); } - throw new ArgumentException( - string.Format(CultureInfo.InvariantCulture, - "'{0}' does not look like a valid branch name.", CanonicalName)); + throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, + "'{0}' does not look like a valid branch name.", + CanonicalName)); } } } diff --git a/LibGit2Sharp/BranchCollection.cs b/LibGit2Sharp/BranchCollection.cs index 42834d042..d81a48177 100644 --- a/LibGit2Sharp/BranchCollection.cs +++ b/LibGit2Sharp/BranchCollection.cs @@ -92,7 +92,8 @@ private Branch BuildFromReferenceName(string canonicalName) public virtual IEnumerator GetEnumerator() { return Proxy.git_branch_iterator(repo, GitBranchType.GIT_BRANCH_ALL) - .ToList().GetEnumerator(); + .ToList() + .GetEnumerator(); } /// @@ -117,6 +118,31 @@ 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.ArgumentNotNull(commit, "commit"); + + return Add(name, commit.Sha, allowOverwrite); + } + /// /// Create a new local branch with the specified name /// @@ -129,12 +155,42 @@ 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)) { } + using (Proxy.git_branch_create_from_annotated(repo.Handle, name, committish, allowOverwrite)) + { } var branch = this[ShortToLocalName(name)]; return branch; } + /// + /// Deletes the branch with the specified name. + /// + /// The name of the branch to delete. + public virtual void Remove(string name) + { + Remove(name, false); + } + + /// + /// Deletes the branch with the specified name. + /// + /// The name of the branch to delete. + /// True if the provided is the name of a remote branch, false otherwise. + public virtual void Remove(string name, bool isRemote) + { + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + + string branchName = isRemote ? Reference.RemoteTrackingBranchPrefix + name : name; + + Branch branch = this[branchName]; + + if (branch == null) + { + return; + } + + Remove(branch); + } /// /// Deletes the specified branch. /// @@ -143,12 +199,45 @@ public virtual void Remove(Branch branch) { Ensure.ArgumentNotNull(branch, "branch"); - using (ReferenceSafeHandle referencePtr = repo.Refs.RetrieveReferencePtr(branch.CanonicalName)) + using (ReferenceHandle referencePtr = repo.Refs.RetrieveReferencePtr(branch.CanonicalName)) { Proxy.git_branch_delete(referencePtr); } } + /// + /// Rename an existing local branch, 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 /// @@ -174,16 +263,14 @@ public virtual Branch Rename(Branch branch, string newName, bool allowOverwrite) if (branch.IsRemote) { - throw new LibGit2SharpException( - string.Format(CultureInfo.InvariantCulture, - "Cannot rename branch '{0}'. It's a remote tracking branch.", branch.FriendlyName)); + throw new LibGit2SharpException("Cannot rename branch '{0}'. It's a remote tracking branch.", + branch.FriendlyName); } - using (ReferenceSafeHandle referencePtr = repo.Refs.RetrieveReferencePtr(Reference.LocalBranchPrefix + branch.FriendlyName)) + using (ReferenceHandle referencePtr = repo.Refs.RetrieveReferencePtr(Reference.LocalBranchPrefix + branch.FriendlyName)) { using (Proxy.git_branch_move(referencePtr, newName, allowOverwrite)) - { - } + { } } var newBranch = this[newName]; @@ -217,11 +304,7 @@ private static bool LooksLikeABranchName(string referenceName) private string DebuggerDisplay { - get - { - return string.Format(CultureInfo.InvariantCulture, - "Count = {0}", this.Count()); - } + get { return string.Format(CultureInfo.InvariantCulture, "Count = {0}", this.Count()); } } } } diff --git a/LibGit2Sharp/BranchCollectionExtensions.cs b/LibGit2Sharp/BranchCollectionExtensions.cs deleted file mode 100644 index 68b15dec4..000000000 --- a/LibGit2Sharp/BranchCollectionExtensions.cs +++ /dev/null @@ -1,104 +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 being worked with. - /// The name of the branch. - /// The target commit. - /// A new . - public static Branch Add(this BranchCollection branches, string name, Commit commit) - { - return branches.Add(name, commit, false); - } - - /// - /// Create a new local branch with the specified name - /// - /// The being worked with. - /// The name of the branch. - /// The target commit. - /// True to allow silent overwriting a potentially existing branch, false otherwise. - /// A new . - public static Branch Add(this BranchCollection branches, string name, Commit commit, bool allowOverwrite) - { - Ensure.ArgumentNotNull(commit, "commit"); - - return branches.Add(name, commit.Sha, allowOverwrite); - } - - /// - /// Deletes the branch with the specified name. - /// - /// The name of the branch to delete. - /// The being worked with. - public static void Remove(this BranchCollection branches, string name) - { - branches.Remove(name, false); - } - - /// - /// Deletes the branch with the specified name. - /// - /// The name of the branch to delete. - /// True if the provided is the name of a remote branch, false otherwise. - /// The being worked with. - public static void Remove(this BranchCollection branches, string name, bool isRemote) - { - Ensure.ArgumentNotNullOrEmptyString(name, "name"); - - string branchName = isRemote ? Reference.RemoteTrackingBranchPrefix + name : name; - - Branch branch = branches[branchName]; - - if (branch == null) - { - return; - } - - branches.Remove(branch); - } - - /// - /// Rename an existing local branch, using the default reflog message - /// - /// The current branch name. - /// The new name the existing branch should bear. - /// The being worked with. - /// A new . - public static Branch Rename(this BranchCollection branches, string currentName, string newName) - { - return branches.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. - /// The being worked with. - /// A new . - public static Branch Rename(this BranchCollection branches, string currentName, string newName, bool allowOverwrite) - { - Ensure.ArgumentNotNullOrEmptyString(currentName, "currentName"); - Ensure.ArgumentNotNullOrEmptyString(newName, "newName"); - - Branch branch = branches[currentName]; - - if (branch == null) - { - throw new LibGit2SharpException("No branch named '{0}' exists in the repository."); - } - - return branches.Rename(branch, newName, allowOverwrite); - } - } -} diff --git a/LibGit2Sharp/BranchUpdater.cs b/LibGit2Sharp/BranchUpdater.cs index d1a5c39c4..b0908f272 100644 --- a/LibGit2Sharp/BranchUpdater.cs +++ b/LibGit2Sharp/BranchUpdater.cs @@ -154,7 +154,7 @@ private void SetUpstreamRemote(string remoteName) if (!remoteName.Equals(".", StringComparison.Ordinal)) { // Verify that remote exists. - repo.Network.Remotes.RemoteForName(remoteName); + using (repo.Network.Remotes.RemoteForName(remoteName)) { } } repo.Config.Set(configKey, remoteName); @@ -183,13 +183,16 @@ private void GetUpstreamInformation(string canonicalName, out string remoteName, { remoteName = Proxy.git_branch_remote_name(repo.Handle, canonicalName, true); - Remote remote = repo.Network.Remotes.RemoteForName(remoteName); - mergeBranchName = remote.FetchSpecTransformToSource(canonicalName); + using (var remote = repo.Network.Remotes.RemoteForName(remoteName)) + { + mergeBranchName = remote.FetchSpecTransformToSource(canonicalName); + } } else { throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, - "'{0}' does not look like a valid canonical branch name.", canonicalName)); + "'{0}' does not look like a valid canonical branch name.", + canonicalName)); } } } diff --git a/LibGit2Sharp/BuiltInFeatures.cs b/LibGit2Sharp/BuiltInFeatures.cs index db6a1a0ed..1cf0d92e9 100644 --- a/LibGit2Sharp/BuiltInFeatures.cs +++ b/LibGit2Sharp/BuiltInFeatures.cs @@ -29,5 +29,11 @@ public enum BuiltInFeatures /// libgit2. /// Ssh = (1 << 2), + + /// + /// Support for sub-second resolution in file modification times + /// is compiled into libgit2. + /// + NSec = (1 << 3), } } diff --git a/LibGit2Sharp/Certificate.cs b/LibGit2Sharp/Certificate.cs new file mode 100644 index 000000000..95472a24c --- /dev/null +++ b/LibGit2Sharp/Certificate.cs @@ -0,0 +1,9 @@ +namespace LibGit2Sharp +{ + /// + /// Top-level certificate type. The usable certificates inherit from this class. + /// + public abstract class Certificate + { + } +} diff --git a/LibGit2Sharp/CertificateSsh.cs b/LibGit2Sharp/CertificateSsh.cs new file mode 100644 index 000000000..d72b69469 --- /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..da45eb43e --- /dev/null +++ b/LibGit2Sharp/CertificateX509.cs @@ -0,0 +1,55 @@ +using System; +using System.Runtime.InteropServices; +using System.Security.Cryptography.X509Certificates; +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + /// + /// Conains a X509 certificate + /// + public class CertificateX509 : Certificate + { + /// + /// For mocking purposes + /// + protected CertificateX509() + { } + + /// + /// The certificate. + /// + public virtual X509Certificate Certificate { get; private set; } + + internal unsafe CertificateX509(git_certificate_x509* cert) + { + int len = checked((int) cert->len.ToUInt32()); + byte[] data = new byte[len]; + Marshal.Copy(new IntPtr(cert->data), data, 0, len); + Certificate = new X509Certificate(data); + } + + internal CertificateX509(X509Certificate cert) + { + Certificate = cert; + } + + internal unsafe IntPtr ToPointers(out IntPtr dataPtr) + { + var certData = Certificate.Export(X509ContentType.Cert); + dataPtr = Marshal.AllocHGlobal(certData.Length); + Marshal.Copy(certData, 0, dataPtr, certData.Length); + var gitCert = new git_certificate_x509() + { + cert_type = GitCertificateType.X509, + data = (byte*) dataPtr.ToPointer(), + len = (UIntPtr)certData.Length, + }; + + var ptr = Marshal.AllocHGlobal(Marshal.SizeOf(gitCert)); + Marshal.StructureToPtr(gitCert, ptr, false); + + return ptr; + } + } +} diff --git a/LibGit2Sharp/ChangeKind.cs b/LibGit2Sharp/ChangeKind.cs index c95095a37..304438be8 100644 --- a/LibGit2Sharp/ChangeKind.cs +++ b/LibGit2Sharp/ChangeKind.cs @@ -55,5 +55,10 @@ public enum ChangeKind /// Entry is unreadable. /// Unreadable = 9, + + /// + /// Entry is currently in conflict. + /// + Conflicted = 10, } } diff --git a/LibGit2Sharp/CheckoutConflictException.cs b/LibGit2Sharp/CheckoutConflictException.cs index 9bd525ca1..f2f5092e9 100644 --- a/LibGit2Sharp/CheckoutConflictException.cs +++ b/LibGit2Sharp/CheckoutConflictException.cs @@ -10,14 +10,13 @@ namespace LibGit2Sharp /// in the working directory. /// [Serializable] - public class CheckoutConflictException : LibGit2SharpException + 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. @@ -25,8 +24,16 @@ public CheckoutConflictException() /// 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. @@ -35,8 +42,7 @@ public CheckoutConflictException(string message) /// The exception that is the cause of the current exception. If the parameter is not a null reference, the current exception is raised in a catch block that handles the inner exception. public CheckoutConflictException(string message, Exception innerException) : base(message, innerException) - { - } + { } /// /// Initializes a new instance of the class with a serialized data. @@ -45,12 +51,18 @@ public CheckoutConflictException(string message, Exception innerException) /// The that contains contextual information about the source or destination. protected CheckoutConflictException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } + { } + + internal CheckoutConflictException(string message, GitErrorCategory category) + : base(message, category) + { } - internal CheckoutConflictException(string message, GitErrorCode code, GitErrorCategory category) - : base(message, code, category) + internal override GitErrorCode ErrorCode { + get + { + return GitErrorCode.Conflict; + } } } } diff --git a/LibGit2Sharp/CheckoutOptions.cs b/LibGit2Sharp/CheckoutOptions.cs index 9e297cd47..010502007 100644 --- a/LibGit2Sharp/CheckoutOptions.cs +++ b/LibGit2Sharp/CheckoutOptions.cs @@ -34,8 +34,9 @@ CheckoutStrategy IConvertableToGitCheckoutOpts.CheckoutStrategy { get { - return CheckoutModifiers.HasFlag(CheckoutModifiers.Force) ? - CheckoutStrategy.GIT_CHECKOUT_FORCE : CheckoutStrategy.GIT_CHECKOUT_SAFE; + return CheckoutModifiers.HasFlag(CheckoutModifiers.Force) + ? CheckoutStrategy.GIT_CHECKOUT_FORCE + : CheckoutStrategy.GIT_CHECKOUT_SAFE; } } diff --git a/LibGit2Sharp/CherryPickOptions.cs b/LibGit2Sharp/CherryPickOptions.cs index 8257fdb50..065e79bbb 100644 --- a/LibGit2Sharp/CherryPickOptions.cs +++ b/LibGit2Sharp/CherryPickOptions.cs @@ -1,7 +1,4 @@ -using LibGit2Sharp.Core; -using LibGit2Sharp.Handlers; - -namespace LibGit2Sharp +namespace LibGit2Sharp { /// /// Options controlling CherryPick behavior. @@ -13,8 +10,7 @@ public sealed class CherryPickOptions : MergeAndCheckoutOptionsBase /// 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 diff --git a/LibGit2Sharp/CloneOptions.cs b/LibGit2Sharp/CloneOptions.cs index 6b264e8a4..f88ff58d7 100644 --- a/LibGit2Sharp/CloneOptions.cs +++ b/LibGit2Sharp/CloneOptions.cs @@ -43,6 +43,11 @@ public CloneOptions() /// public CheckoutProgressHandler OnCheckoutProgress { get; set; } + /// + /// Gets or sets the fetch options. + /// + public FetchOptions FetchOptions { get; set; } + #region IConvertableToGitCheckoutOpts CheckoutCallbacks IConvertableToGitCheckoutOpts.GenerateCallbacks() @@ -54,9 +59,9 @@ CheckoutStrategy IConvertableToGitCheckoutOpts.CheckoutStrategy { get { - return this.Checkout ? - CheckoutStrategy.GIT_CHECKOUT_SAFE : - CheckoutStrategy.GIT_CHECKOUT_NONE; + return this.Checkout + ? CheckoutStrategy.GIT_CHECKOUT_SAFE + : CheckoutStrategy.GIT_CHECKOUT_NONE; } } diff --git a/LibGit2Sharp/Commands/Checkout.cs b/LibGit2Sharp/Commands/Checkout.cs new file mode 100644 index 000000000..bcbd29616 --- /dev/null +++ b/LibGit2Sharp/Commands/Checkout.cs @@ -0,0 +1,157 @@ +using System; +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; + GitObject obj; + + repository.RevParse(committishOrBranchSpec, out reference, out obj); + if (reference != null && reference.IsLocalBranch) + { + Branch 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..d61fca5a5 --- /dev/null +++ b/LibGit2Sharp/Commands/Fetch.cs @@ -0,0 +1,86 @@ +using System.Collections.Generic; +using LibGit2Sharp; +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 = new GitProxyOptions { Version = 1 }; + + 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..bee1bbbda --- /dev/null +++ b/LibGit2Sharp/Commands/Pull.cs @@ -0,0 +1,42 @@ +using System; +using LibGit2Sharp; +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, new string[0], 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..939c427d1 --- /dev/null +++ b/LibGit2Sharp/Commands/Remove.cs @@ -0,0 +1,242 @@ +using System.Linq; +using System.IO; +using System.Collections.Generic; +using LibGit2Sharp; +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..a1febafcb --- /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("sourcePaths"); + } + + foreach (KeyValuePair, Tuple> keyValuePair in batch) + { + string sourcePath = keyValuePair.Key.Item1; + string destPath = keyValuePair.Value.Item1; + + if (Directory.Exists(sourcePath) || Directory.Exists(destPath)) + { + throw new NotImplementedException(); + } + + FileStatus sourceStatus = keyValuePair.Key.Item2; + if (sourceStatus.HasAny(new Enum[] { FileStatus.Nonexistent, FileStatus.DeletedFromIndex, FileStatus.NewInWorkdir, FileStatus.DeletedFromWorkdir })) + { + throw new LibGit2SharpException("Unable to move file '{0}'. Its current status is '{1}'.", + sourcePath, + sourceStatus); + } + + FileStatus desStatus = keyValuePair.Value.Item2; + if (desStatus.HasAny(new Enum[] { FileStatus.Nonexistent, FileStatus.DeletedFromWorkdir })) + { + continue; + } + + throw new LibGit2SharpException("Unable to overwrite file '{0}'. Its current status is '{1}'.", + destPath, + desStatus); + } + + string wd = repository.Info.WorkingDirectory; + var index = repository.Index; + foreach (KeyValuePair, Tuple> keyValuePair in batch) + { + string from = keyValuePair.Key.Item1; + string to = keyValuePair.Value.Item1; + + index.Remove(from); + File.Move(Path.Combine(wd, from), Path.Combine(wd, to)); + index.Add(to); + } + + index.Write(); + } + + private static bool Enumerate(IEnumerator leftEnum, IEnumerator rightEnum) + { + bool isLeftEoF = leftEnum.MoveNext(); + bool isRightEoF = rightEnum.MoveNext(); + + if (isLeftEoF == isRightEoF) + { + return isLeftEoF; + } + + throw new ArgumentException("The collection of paths are of different lengths."); + } + + private static IDictionary, Tuple> PrepareBatch(IRepository repository, IEnumerable leftPaths, IEnumerable rightPaths) + { + IDictionary, Tuple> dic = new Dictionary, Tuple>(); + + IEnumerator leftEnum = leftPaths.GetEnumerator(); + IEnumerator rightEnum = rightPaths.GetEnumerator(); + + while (Enumerate(leftEnum, rightEnum)) + { + Tuple from = BuildFrom(repository, leftEnum.Current); + Tuple to = BuildFrom(repository, rightEnum.Current); + dic.Add(from, to); + } + + return dic; + } + + private static Tuple BuildFrom(IRepository repository, string path) + { + string relativePath = repository.BuildRelativePathFrom(path); + return new Tuple(relativePath, repository.RetrieveStatus(relativePath)); + } + } +} + diff --git a/LibGit2Sharp/Commit.cs b/LibGit2Sharp/Commit.cs index 6ea9d0608..357567d8a 100644 --- a/LibGit2Sharp/Commit.cs +++ b/LibGit2Sharp/Commit.cs @@ -54,7 +54,7 @@ internal Commit(Repository repo, ObjectId id) /// /// Gets the pointed at by the in the . /// - /// The relative path to the from the working directory. + /// Path to the from the tree in this /// null if nothing has been found, the otherwise. public virtual TreeEntry this[string relativePath] { @@ -106,22 +106,100 @@ private IEnumerable RetrieveNotesOfCommit(ObjectId oid) return repo.Notes[oid]; } - private static string RetrieveEncodingOf(GitObjectSafeHandle obj) + private static string RetrieveEncodingOf(ObjectHandle obj) { string encoding = Proxy.git_commit_message_encoding(obj); return encoding ?? "UTF-8"; } + /// + /// Prettify a commit message + /// + /// Remove comment lines and trailing lines + /// + /// + /// The prettified message + /// The message to prettify. + /// Comment character. Lines starting with it will be removed + public static string PrettifyMessage(string message, char commentChar) + { + return Proxy.git_message_prettify(message, commentChar); + } + private string DebuggerDisplay { get { return string.Format(CultureInfo.InvariantCulture, - "{0} {1}", Id.ToString(7), MessageShort); + "{0} {1}", + Id.ToString(7), + MessageShort); } } + /// + /// Extract the signature data from this commit + /// + /// The signature and the signed data + /// The repository in which the object lives + /// The commit to extract the signature from + /// The header field which contains the signature; use null for the default of "gpgsig" + public static SignatureInfo ExtractSignature(Repository repo, ObjectId id, string field) + { + return Proxy.git_commit_extract_signature(repo.Handle, id, field); + } + + /// + /// Extract the signature data from this commit + /// + /// The overload uses the default header field "gpgsig" + /// + /// + /// The signature and the signed data + /// The repository in which the object lives + /// The commit to extract the signature from + public static SignatureInfo ExtractSignature(Repository repo, ObjectId id) + { + return Proxy.git_commit_extract_signature(repo.Handle, id, null); + } + + /// + /// Create a commit in-memory + /// + /// Prettifing the message includes: + /// * Removing empty lines from the beginning and end. + /// * Removing trailing spaces from every line. + /// * Turning multiple consecutive empty lines between paragraphs into just one empty line. + /// * Ensuring the commit message ends with a newline. + /// * Removing every line starting with the . + /// + /// + /// The of who made the change. + /// The of who added the change to the repository. + /// The description of why a change was made to the repository. + /// The of the to be created. + /// The parents of the to be created. + /// True to prettify the message, or false to leave it as is. + /// When non null, lines starting with this character will be stripped if prettifyMessage is true. + /// The contents of the commit object. + public static string CreateBuffer(Signature author, Signature committer, string message, Tree tree, IEnumerable parents, bool prettifyMessage, char? commentChar) + { + Ensure.ArgumentNotNull(message, "message"); + Ensure.ArgumentDoesNotContainZeroByte(message, "message"); + Ensure.ArgumentNotNull(author, "author"); + Ensure.ArgumentNotNull(committer, "committer"); + Ensure.ArgumentNotNull(tree, "tree"); + Ensure.ArgumentNotNull(parents, "parents"); + + if (prettifyMessage) + { + message = Proxy.git_message_prettify(message, commentChar); + } + + return Proxy.git_commit_create_buffer(tree.repo.Handle, author, committer, message, tree, parents.ToArray()); + } + private class ParentsCollection : ICollection { private readonly Lazy> _parents; diff --git a/LibGit2Sharp/CommitFilter.cs b/LibGit2Sharp/CommitFilter.cs index 5021c1260..8997ca772 100644 --- a/LibGit2Sharp/CommitFilter.cs +++ b/LibGit2Sharp/CommitFilter.cs @@ -1,11 +1,12 @@ -using System.Collections; +using System; +using System.Collections; using System.Collections.Generic; using System.Linq; namespace LibGit2Sharp { /// - /// Criterias used to filter out and order the commits of the repository when querying its history. + /// Criteria used to filter out and order the commits of the repository when querying its history. /// public sealed class CommitFilter { @@ -15,12 +16,12 @@ public sealed class CommitFilter public CommitFilter() { SortBy = CommitSortStrategies.Time; - Since = "HEAD"; + IncludeReachableFrom = "HEAD"; FirstParentOnly = false; } /// - /// The ordering stragtegy to use. + /// The ordering strategy to use. /// /// By default, the commits are shown in reverse chronological order. /// @@ -36,11 +37,11 @@ public CommitFilter() /// By default, the will be used as boundary. /// /// - public object Since { get; set; } + public object IncludeReachableFrom { get; set; } internal IList SinceList { - get { return ToList(Since); } + get { return ToList(IncludeReachableFrom); } } /// @@ -51,11 +52,11 @@ internal IList SinceList /// a , an or even a mixed collection of all of the above. /// /// - public object Until { get; set; } + public object ExcludeReachableFrom { get; set; } internal IList UntilList { - get { return ToList(Until); } + get { return ToList(ExcludeReachableFrom); } } /// @@ -73,12 +74,12 @@ private static IList ToList(object obj) } var types = new[] - { - typeof(string), typeof(ObjectId), - typeof(Commit), typeof(TagAnnotation), - typeof(Tag), typeof(Branch), typeof(DetachedHead), - typeof(Reference), typeof(DirectReference), typeof(SymbolicReference) - }; + { + typeof(string), typeof(ObjectId), + typeof(Commit), typeof(TagAnnotation), + typeof(Tag), typeof(Branch), typeof(DetachedHead), + typeof(Reference), typeof(DirectReference), typeof(SymbolicReference) + }; if (types.Contains(obj.GetType())) { diff --git a/LibGit2Sharp/CommitLog.cs b/LibGit2Sharp/CommitLog.cs index a31df9e2b..4a6ab1de3 100644 --- a/LibGit2Sharp/CommitLog.cs +++ b/LibGit2Sharp/CommitLog.cs @@ -22,8 +22,7 @@ public sealed class CommitLog : IQueryableCommitLog /// The repository. internal CommitLog(Repository repo) : this(repo, new CommitFilter()) - { - } + { } /// /// Initializes a new instance of the class. @@ -74,8 +73,8 @@ IEnumerator IEnumerable.GetEnumerator() public ICommitLog QueryBy(CommitFilter filter) { Ensure.ArgumentNotNull(filter, "filter"); - Ensure.ArgumentNotNull(filter.Since, "filter.Since"); - Ensure.ArgumentNotNullOrEmptyString(filter.Since.ToString(), "filter.Since"); + Ensure.ArgumentNotNull(filter.IncludeReachableFrom, "filter.IncludeReachableFrom"); + Ensure.ArgumentNotNullOrEmptyString(filter.IncludeReachableFrom.ToString(), "filter.IncludeReachableFrom"); return new CommitLog(repo, filter); } @@ -98,42 +97,18 @@ public IEnumerable QueryBy(string path) /// 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, FollowFilter filter) + public IEnumerable QueryBy(string path, CommitFilter filter) { Ensure.ArgumentNotNull(path, "path"); Ensure.ArgumentNotNull(filter, "filter"); - return new FileHistory(repo, path, new CommitFilter {SortBy = filter.SortBy}); - } - - /// - /// Find the best possible merge base given two s. - /// - /// The first . - /// The second . - /// The merge base or null if none found. - [Obsolete("This method will be removed in the next release. Please use ObjectDatabase.FindMergeBase() instead.")] - public Commit FindMergeBase(Commit first, Commit second) - { - return repo.ObjectDatabase.FindMergeBase(first, second); - } - - /// - /// 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. - [Obsolete("This method will be removed in the next release. Please use ObjectDatabase.FindMergeBase() instead.")] - public Commit FindMergeBase(IEnumerable commits, MergeBaseFindingStrategy strategy) - { - return repo.ObjectDatabase.FindMergeBase(commits, strategy); + return new FileHistory(repo, path, filter); } private class CommitEnumerator : IEnumerator { private readonly Repository repo; - private readonly RevWalkerSafeHandle handle; + private readonly RevWalkerHandle handle; private ObjectId currentOid; public CommitEnumerator(Repository repo, CommitFilter filter) @@ -192,7 +167,7 @@ private void Dispose(bool disposing) handle.SafeDispose(); } - private delegate void HidePushSignature(RevWalkerSafeHandle handle, ObjectId id); + private delegate void HidePushSignature(RevWalkerHandle handle, ObjectId id); private void InternalHidePush(IList identifier, HidePushSignature hidePush) { diff --git a/LibGit2Sharp/CommitRewriteInfo.cs b/LibGit2Sharp/CommitRewriteInfo.cs index a4387e83d..ca7399578 100644 --- a/LibGit2Sharp/CommitRewriteInfo.cs +++ b/LibGit2Sharp/CommitRewriteInfo.cs @@ -28,18 +28,18 @@ public sealed class CommitRewriteInfo public static CommitRewriteInfo From(Commit commit) { return new CommitRewriteInfo - { - Author = commit.Author, - Committer = commit.Committer, - Message = commit.Message - }; + { + Author = commit.Author, + Committer = commit.Committer, + Message = commit.Message + }; } - /// - /// Build a from the passed in, - /// optionally overriding some of its properties - /// - /// The whose information is to be copied + /// + /// 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.. @@ -85,10 +85,11 @@ public static CommitRewriteInfo From(Commit commit, Signature author, Signature /// 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) + public static CommitRewriteInfo From( + Commit commit, + Signature author, + Signature committer, + string message) { var cri = From(commit); cri.Author = author ?? cri.Author; diff --git a/LibGit2Sharp/CompareOptions.cs b/LibGit2Sharp/CompareOptions.cs index 6e9acb434..fb4234439 100644 --- a/LibGit2Sharp/CompareOptions.cs +++ b/LibGit2Sharp/CompareOptions.cs @@ -14,7 +14,7 @@ public CompareOptions() { ContextLines = 3; InterhunkLines = 0; - Algorithm = DiffAlgorithm.Meyers; + Algorithm = DiffAlgorithm.Myers; } /// @@ -40,15 +40,15 @@ public CompareOptions() public bool IncludeUnmodified { get; set; } /// - /// Use the "patience diff" algorithm. + /// Algorithm to be used when performing a Diff. + /// By default, will be used. /// - [Obsolete("This property will be removed in the next release. Please use Algorithm instead.")] - public bool UsePatienceAlgorithm { get; set; } + public DiffAlgorithm Algorithm { get; set; } /// - /// Algorithm to be used when performing a Diff. - /// By default, will be used. + /// Enable --indent-heuristic Diff option, that attempts to produce more aesthetically pleasing diffs. + /// By default, this option will be false. /// - public DiffAlgorithm Algorithm { get; set; } + public bool IndentHeuristic { get; set; } } } diff --git a/LibGit2Sharp/Configuration.cs b/LibGit2Sharp/Configuration.cs index 47b14fb32..84a8a3e53 100644 --- a/LibGit2Sharp/Configuration.cs +++ b/LibGit2Sharp/Configuration.cs @@ -15,13 +15,13 @@ namespace LibGit2Sharp public class Configuration : IDisposable, IEnumerable> { + private readonly FilePath repoConfigPath; private readonly FilePath globalConfigPath; private readonly FilePath xdgConfigPath; private readonly FilePath systemConfigPath; + private readonly FilePath programDataConfigPath; - private readonly Repository repository; - - private ConfigurationSafeHandle configHandle; + private ConfigurationHandle configHandle; /// /// Needed for mocking purposes. @@ -29,76 +29,178 @@ public class Configuration : IDisposable, protected Configuration() { } - internal Configuration(Repository repository, string globalConfigurationFileLocation, - string xdgConfigurationFileLocation, string systemConfigurationFileLocation) + internal Configuration( + Repository repository, + string repositoryConfigurationFileLocation, + string globalConfigurationFileLocation, + string xdgConfigurationFileLocation, + string systemConfigurationFileLocation) { - this.repository = repository; + if (repositoryConfigurationFileLocation != null) + { + repoConfigPath = NormalizeConfigPath(repositoryConfigurationFileLocation); + } globalConfigPath = globalConfigurationFileLocation ?? Proxy.git_config_find_global(); xdgConfigPath = xdgConfigurationFileLocation ?? Proxy.git_config_find_xdg(); systemConfigPath = systemConfigurationFileLocation ?? Proxy.git_config_find_system(); + programDataConfigPath = Proxy.git_config_find_programdata(); - Init(); + Init(repository); } - private void Init() + private void Init(Repository repository) { configHandle = Proxy.git_config_new(); + RepositoryHandle repoHandle = (repository != null) ? repository.Handle : null; - if (repository != null) + if (repoHandle != null) { //TODO: push back this logic into libgit2. // As stated by @carlosmn "having a helper function to load the defaults and then allowing you // to modify it before giving it to git_repository_open_ext() would be a good addition, I think." // -- Agreed :) string repoConfigLocation = Path.Combine(repository.Info.Path, "config"); - Proxy.git_config_add_file_ondisk(configHandle, repoConfigLocation, ConfigurationLevel.Local); + Proxy.git_config_add_file_ondisk(configHandle, repoConfigLocation, ConfigurationLevel.Local, repoHandle); - Proxy.git_repository_set_config(repository.Handle, configHandle); + Proxy.git_repository_set_config(repoHandle, configHandle); + } + else if (repoConfigPath != null) + { + Proxy.git_config_add_file_ondisk(configHandle, repoConfigPath, ConfigurationLevel.Local, repoHandle); } if (globalConfigPath != null) { - Proxy.git_config_add_file_ondisk(configHandle, globalConfigPath, ConfigurationLevel.Global); + Proxy.git_config_add_file_ondisk(configHandle, globalConfigPath, ConfigurationLevel.Global, repoHandle); } if (xdgConfigPath != null) { - Proxy.git_config_add_file_ondisk(configHandle, xdgConfigPath, ConfigurationLevel.Xdg); + Proxy.git_config_add_file_ondisk(configHandle, xdgConfigPath, ConfigurationLevel.Xdg, repoHandle); } if (systemConfigPath != null) { - Proxy.git_config_add_file_ondisk(configHandle, systemConfigPath, ConfigurationLevel.System); + Proxy.git_config_add_file_ondisk(configHandle, systemConfigPath, ConfigurationLevel.System, repoHandle); + } + + if (programDataConfigPath != null) + { + Proxy.git_config_add_file_ondisk(configHandle, programDataConfigPath, ConfigurationLevel.ProgramData, repoHandle); + } + } + + private FilePath NormalizeConfigPath(FilePath path) + { + if (File.Exists(path.Native)) + { + return path; + } + + if (!Directory.Exists(path.Native)) + { + throw new FileNotFoundException("Cannot find repository configuration file", path.Native); + } + + var configPath = Path.Combine(path.Native, "config"); + + if (File.Exists(configPath)) + { + return configPath; } + + var gitConfigPath = Path.Combine(path.Native, ".git", "config"); + + if (File.Exists(gitConfigPath)) + { + return gitConfigPath; + } + + throw new FileNotFoundException("Cannot find repository configuration file", path.Native); } /// - /// Access configuration values without a repository. Generally you want to access configuration via an instance of instead. + /// 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. - public Configuration(string globalConfigurationFileLocation) - : this(null, globalConfigurationFileLocation, null, null) - { } + /// 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. + /// Access configuration values without a repository. + /// + /// Generally you want to access configuration via an instance of instead. + /// + /// + /// can either contains a path to a file or a directory. In the latter case, + /// this can be the working directory, the .git directory or the directory containing a bare repository. + /// + /// + /// Path to an existing Repository configuration file. + /// Path to a Global configuration file. If null, the default path for a Global configuration file will be probed. + /// An instance of . + public static Configuration BuildFrom( + string repositoryConfigurationFileLocation, + string globalConfigurationFileLocation) + { + return BuildFrom(repositoryConfigurationFileLocation, globalConfigurationFileLocation, null, null); + } + + /// + /// Access configuration values without a repository. + /// + /// Generally you want to access configuration via an instance of instead. + /// + /// + /// can either contains a path to a file or a directory. In the latter case, + /// this can be the working directory, the .git directory or the directory containing a bare repository. + /// /// - /// Path to a Global configuration file. If null, the default path for a global configuration file will be probed. + /// Path to an existing Repository configuration file. + /// Path to a Global configuration file. If null, the default path for a Global configuration file will be probed. /// Path to a XDG configuration file. If null, the default path for a XDG configuration file will be probed. - public Configuration(string globalConfigurationFileLocation, string xdgConfigurationFileLocation) - : this(null, globalConfigurationFileLocation, xdgConfigurationFileLocation, null) - { } + /// An instance of . + public static Configuration BuildFrom( + string repositoryConfigurationFileLocation, + string globalConfigurationFileLocation, + string xdgConfigurationFileLocation) + { + return BuildFrom(repositoryConfigurationFileLocation, globalConfigurationFileLocation, xdgConfigurationFileLocation, null); + } /// - /// Access configuration values without a repository. Generally you want to access configuration via an instance of instead. + /// Access configuration values without a repository. + /// + /// Generally you want to access configuration via an instance of instead. + /// + /// + /// can either contains a path to a file or a directory. In the latter case, + /// this can be the working directory, the .git directory or the directory containing a bare repository. + /// /// - /// Path to a Global configuration file. If null, the default path for a global configuration file will be probed. + /// Path to an existing Repository configuration file. + /// Path to a Global configuration file. If null, the default path for a Global configuration file will be probed. /// Path to a XDG configuration file. If null, the default path for a XDG configuration file will be probed. - /// Path to a System configuration file. If null, the default path for a system configuration file will be probed. - public Configuration(string globalConfigurationFileLocation, string xdgConfigurationFileLocation, string systemConfigurationFileLocation) - : this(null, globalConfigurationFileLocation, xdgConfigurationFileLocation, systemConfigurationFileLocation) + /// Path to a System configuration file. If null, the default path for a System configuration file will be probed. + /// An instance of . + public static Configuration BuildFrom( + string repositoryConfigurationFileLocation, + string globalConfigurationFileLocation, + string xdgConfigurationFileLocation, + string systemConfigurationFileLocation) { + return new Configuration(null, repositoryConfigurationFileLocation, globalConfigurationFileLocation, xdgConfigurationFileLocation, systemConfigurationFileLocation); } /// @@ -106,8 +208,8 @@ public Configuration(string globalConfigurationFileLocation, string xdgConfigura /// public virtual bool HasConfig(ConfigurationLevel level) { - using (ConfigurationSafeHandle snapshot = Snapshot ()) - using (ConfigurationSafeHandle handle = RetrieveConfigurationHandle(level, false, snapshot)) + using (ConfigurationHandle snapshot = Snapshot()) + using (ConfigurationHandle handle = RetrieveConfigurationHandle(level, false, snapshot)) { return handle != null; } @@ -131,9 +233,9 @@ public void Dispose() /// Unset a configuration variable (key and value) in the local configuration. /// /// The key to unset. - public virtual void Unset(string key) + public virtual bool Unset(string key) { - Unset(key, ConfigurationLevel.Local); + return Unset(key, ConfigurationLevel.Local); } /// @@ -141,23 +243,37 @@ public virtual void Unset(string key) /// /// 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) + public virtual bool Unset(string key, ConfigurationLevel level) { Ensure.ArgumentNotNullOrEmptyString(key, "key"); - using (ConfigurationSafeHandle h = RetrieveConfigurationHandle(level, true, configHandle)) + using (ConfigurationHandle h = RetrieveConfigurationHandle(level, true, configHandle)) { - Proxy.git_config_delete(h, key); + return Proxy.git_config_delete(h, key); } } - internal void UnsetMultivar(string key, ConfigurationLevel level) + /// + /// 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 (ConfigurationSafeHandle h = RetrieveConfigurationHandle(level, true, configHandle)) + using (ConfigurationHandle h = RetrieveConfigurationHandle(level, true, configHandle)) { - Proxy.git_config_delete_multivar(h, key); + return Proxy.git_config_delete_multivar(h, key); } } @@ -169,6 +285,64 @@ protected virtual void Dispose(bool disposing) configHandle.SafeDispose(); } + /// + /// Get a configuration value for the given key parts. + /// + /// For example in order to get the value for this in a .git\config file: + /// + /// + /// [core] + /// bare = true + /// + /// + /// You would call: + /// + /// + /// bool isBare = repo.Config.Get<bool>(new []{ "core", "bare" }).Value; + /// + /// + /// + /// The configuration value type + /// The key parts + /// The , or null if not set + public virtual ConfigurationEntry Get(string[] keyParts) + { + Ensure.ArgumentNotNull(keyParts, "keyParts"); + + return Get(string.Join(".", keyParts)); + } + + /// + /// Get a configuration value for the given key parts. + /// + /// For example in order to get the value for this in a .git\config file: + /// + /// + /// [difftool "kdiff3"] + /// path = c:/Program Files/KDiff3/kdiff3.exe + /// + /// + /// You would call: + /// + /// + /// string where = repo.Config.Get<string>("difftool", "kdiff3", "path").Value; + /// + /// + /// + /// The configuration value type + /// The first key part + /// The second key part + /// The third key part + /// The , or null if not set + public virtual ConfigurationEntry Get(string firstKeyPart, string secondKeyPart, string thirdKeyPart) + { + Ensure.ArgumentNotNullOrEmptyString(firstKeyPart, "firstKeyPart"); + Ensure.ArgumentNotNullOrEmptyString(secondKeyPart, "secondKeyPart"); + Ensure.ArgumentNotNullOrEmptyString(thirdKeyPart, "thirdKeyPart"); + + return Get(new[] { firstKeyPart, secondKeyPart, thirdKeyPart }); + } + /// /// Get a configuration value for a key. Keys are in the form 'section.name'. /// @@ -202,7 +376,7 @@ public virtual ConfigurationEntry Get(string key) { Ensure.ArgumentNotNullOrEmptyString(key, "key"); - using (ConfigurationSafeHandle snapshot = Snapshot()) + using (ConfigurationHandle snapshot = Snapshot()) { return Proxy.git_config_get_entry(snapshot, key); } @@ -233,8 +407,8 @@ public virtual ConfigurationEntry Get(string key, ConfigurationLevel level { Ensure.ArgumentNotNullOrEmptyString(key, "key"); - using (ConfigurationSafeHandle snapshot = Snapshot()) - using (ConfigurationSafeHandle handle = RetrieveConfigurationHandle(level, false, snapshot)) + using (ConfigurationHandle snapshot = Snapshot()) + using (ConfigurationHandle handle = RetrieveConfigurationHandle(level, false, snapshot)) { if (handle == null) { @@ -245,6 +419,181 @@ public virtual ConfigurationEntry Get(string key, ConfigurationLevel level } } + /// + /// Get a configuration value for the given key. + /// + /// The configuration value type. + /// The key + /// The configuration value, or the default value for the selected if not found + public virtual T GetValueOrDefault(string key) + { + return ValueOrDefault(Get(key), default(T)); + } + + /// + /// Get a configuration value for the given key, + /// or if the key is not set. + /// + /// The configuration value type. + /// The key + /// The default value if the key is not set. + /// The configuration value, or the default value + public virtual T GetValueOrDefault(string key, T defaultValue) + { + return ValueOrDefault(Get(key), defaultValue); + } + + /// + /// Get a configuration value for the given key + /// + /// The configuration value type. + /// The key. + /// The configuration file into which the key should be searched for. + /// The configuration value, or the default value for if not found + public virtual T GetValueOrDefault(string key, ConfigurationLevel level) + { + return ValueOrDefault(Get(key, level), default(T)); + } + + /// + /// Get a configuration value for the given key, + /// or if the key is not set. + /// + /// The configuration value type. + /// The key. + /// The configuration file into which the key should be searched for. + /// The selector used to generate a default value if the key is not set. + /// The configuration value, or the default value. + public virtual T GetValueOrDefault(string key, ConfigurationLevel level, T defaultValue) + { + return ValueOrDefault(Get(key, level), defaultValue); + } + + /// + /// Get a configuration value for the given key parts + /// + /// The configuration value type. + /// The key parts. + /// The configuration value, or the default value for if not found + public virtual T GetValueOrDefault(string[] keyParts) + { + return ValueOrDefault(Get(keyParts), default(T)); + } + + /// + /// Get a configuration value for the given key parts, + /// or if the key is not set. + /// + /// The configuration value type. + /// The key parts. + /// The default value if the key is not set. + /// The configuration value, or the default value. + public virtual T GetValueOrDefault(string[] keyParts, T defaultValue) + { + return ValueOrDefault(Get(keyParts), defaultValue); + } + + /// + /// Get a configuration value for the given key parts. + /// + /// The configuration value type. + /// The first key part. + /// The second key part. + /// The third key part. + /// The configuration value, or the default value for the selected if not found + public virtual T GetValueOrDefault(string firstKeyPart, string secondKeyPart, string thirdKeyPart) + { + return ValueOrDefault(Get(firstKeyPart, secondKeyPart, thirdKeyPart), default(T)); + } + + /// + /// Get a configuration value for the given key parts, + /// or if the key is not set. + /// + /// The configuration value type. + /// The first key part. + /// The second key part. + /// The third key part. + /// The default value if the key is not set. + /// The configuration value, or the default. + public virtual T GetValueOrDefault(string firstKeyPart, string secondKeyPart, string thirdKeyPart, T defaultValue) + { + return ValueOrDefault(Get(firstKeyPart, secondKeyPart, thirdKeyPart), defaultValue); + } + + /// + /// Get a configuration value for the given key, + /// or a value generated by + /// if the key is not set. + /// + /// The configuration value type. + /// The key + /// The selector used to generate a default value if the key is not set. + /// The configuration value, or a generated default. + public virtual T GetValueOrDefault(string key, Func defaultValueSelector) + { + return ValueOrDefault(Get(key), defaultValueSelector); + } + + /// + /// Get a configuration value for the given key, + /// or a value generated by + /// if the key is not set. + /// + /// The configuration value type. + /// The key. + /// The configuration file into which the key should be searched for. + /// The selector used to generate a default value if the key is not set. + /// The configuration value, or a generated default. + public virtual T GetValueOrDefault(string key, ConfigurationLevel level, Func defaultValueSelector) + { + return ValueOrDefault(Get(key, level), defaultValueSelector); + } + + /// + /// Get a configuration value for the given key parts, + /// or a value generated by + /// if the key is not set. + /// + /// The configuration value type. + /// The key parts. + /// The selector used to generate a default value if the key is not set. + /// The configuration value, or a generated default. + public virtual T GetValueOrDefault(string[] keyParts, Func defaultValueSelector) + { + return ValueOrDefault(Get(keyParts), defaultValueSelector); + } + + /// + /// Get a configuration value for the given key parts, + /// or a value generated by + /// if the key is not set. + /// + /// The configuration value type. + /// The first key part. + /// The second key part. + /// The third key part. + /// The selector used to generate a default value if the key is not set. + /// The configuration value, or a generated default. + public virtual T GetValueOrDefault(string firstKeyPart, string secondKeyPart, string thirdKeyPart, Func defaultValueSelector) + { + return ValueOrDefault(Get(firstKeyPart, secondKeyPart, thirdKeyPart), defaultValueSelector); + } + + private static T ValueOrDefault(ConfigurationEntry value, T defaultValue) + { + return value == null ? defaultValue : value.Value; + } + + private static T ValueOrDefault(ConfigurationEntry value, Func defaultValueSelector) + { + Ensure.ArgumentNotNull(defaultValueSelector, "defaultValueSelector"); + + return value == null + ? defaultValueSelector() + : value.Value; + } + /// /// Set a configuration value for a key in the local configuration. Keys are in the form 'section.name'. /// @@ -288,7 +637,7 @@ public virtual void Set(string key, T value, ConfigurationLevel level) Ensure.ArgumentNotNull(value, "value"); Ensure.ArgumentNotNullOrEmptyString(key, "key"); - using (ConfigurationSafeHandle h = RetrieveConfigurationHandle(level, true, configHandle)) + using (ConfigurationHandle h = RetrieveConfigurationHandle(level, true, configHandle)) { if (!configurationTypedUpdater.ContainsKey(typeof(T))) { @@ -299,6 +648,53 @@ public virtual void Set(string key, T value, ConfigurationLevel level) } } + /// + /// 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 . /// @@ -319,16 +715,16 @@ public virtual IEnumerable> Find(string regexp, Confi { Ensure.ArgumentNotNullOrEmptyString(regexp, "regexp"); - using (ConfigurationSafeHandle snapshot = Snapshot()) - using (ConfigurationSafeHandle h = RetrieveConfigurationHandle(level, true, snapshot)) + using (ConfigurationHandle snapshot = Snapshot()) + using (ConfigurationHandle h = RetrieveConfigurationHandle(level, true, snapshot)) { - return Proxy.git_config_iterator_glob(h, regexp, BuildConfigEntry).ToList(); + return Proxy.git_config_iterator_glob(h, regexp).ToList(); } } - private ConfigurationSafeHandle RetrieveConfigurationHandle(ConfigurationLevel level, bool throwIfStoreHasNotBeenFound, ConfigurationSafeHandle fromHandle) + private ConfigurationHandle RetrieveConfigurationHandle(ConfigurationLevel level, bool throwIfStoreHasNotBeenFound, ConfigurationHandle fromHandle) { - ConfigurationSafeHandle handle = null; + ConfigurationHandle handle = null; if (fromHandle != null) { handle = Proxy.git_config_open_level(fromHandle, level); @@ -336,20 +732,19 @@ private ConfigurationSafeHandle RetrieveConfigurationHandle(ConfigurationLevel l if (handle == null && throwIfStoreHasNotBeenFound) { - throw new LibGit2SharpException( - string.Format(CultureInfo.InvariantCulture, "No {0} configuration file has been found.", - Enum.GetName(typeof(ConfigurationLevel), level))); + throw new LibGit2SharpException("No {0} configuration file has been found.", + Enum.GetName(typeof(ConfigurationLevel), level)); } return handle; } - private static Action GetUpdater(Action setter) + private static Action GetUpdater(Action setter) { return (key, val, handle) => setter(handle, key, (T)val); } - private readonly static IDictionary> configurationTypedUpdater = new Dictionary> + private readonly static IDictionary> configurationTypedUpdater = new Dictionary> { { typeof(int), GetUpdater(Proxy.git_config_set_int32) }, { typeof(long), GetUpdater(Proxy.git_config_set_int64) }, @@ -376,22 +771,17 @@ private IEnumerable> BuildConfigEntries() return Proxy.git_config_foreach(configHandle, BuildConfigEntry); } - private static ConfigurationEntry BuildConfigEntry(IntPtr entryPtr) + internal static unsafe ConfigurationEntry BuildConfigEntry(IntPtr entryPtr) { - var entry = entryPtr.MarshalAs(); - - return new ConfigurationEntry(LaxUtf8Marshaler.FromNative(entry.namePtr), - LaxUtf8Marshaler.FromNative(entry.valuePtr), - (ConfigurationLevel)entry.level); + var entry = (GitConfigEntry*)entryPtr.ToPointer(); + return new ConfigurationEntry(LaxUtf8Marshaler.FromNative(entry->namePtr), + LaxUtf8Marshaler.FromNative(entry->valuePtr), + (ConfigurationLevel)entry->level); } /// - /// Builds a based on current configuration. - /// - /// Name is populated from the user.name setting, and is "unknown" if unspecified. - /// Email is populated from the user.email setting, and is built from - /// and if unspecified. - /// + /// Builds a based on current configuration. If it is not found or + /// some configuration is missing, null is returned. /// /// The same escalation logic than in git.git will be used when looking for the key in the config files: /// - local: the Git file in the current repository @@ -401,50 +791,59 @@ private static ConfigurationEntry BuildConfigEntry(IntPtr entryPtr) /// /// /// The timestamp to use for the . - /// The signature. + /// The signature or null if no user identity can be found in the configuration. public virtual Signature BuildSignature(DateTimeOffset now) { - return BuildSignature(now, false); - } - - internal Signature BuildSignature(DateTimeOffset now, bool shouldThrowIfNotFound) - { - const string userNameKey = "user.name"; - var name = this.GetValueOrDefault(userNameKey); - var normalizedName = NormalizeUserSetting(shouldThrowIfNotFound, userNameKey, name, - () => "unknown"); - - const string userEmailKey = "user.email"; - var email = this.GetValueOrDefault(userEmailKey); - var normalizedEmail = NormalizeUserSetting(shouldThrowIfNotFound, userEmailKey, email, - () => string.Format( - CultureInfo.InvariantCulture, "{0}@{1}", Environment.UserName, Environment.UserDomainName)); + var name = this.GetValueOrDefault("user.name"); + var email = this.GetValueOrDefault("user.email"); - return new Signature(normalizedName, normalizedEmail, now); - } - - private string NormalizeUserSetting(bool shouldThrowIfNotFound, string entryName, string currentValue, Func defaultValue) - { - if (!string.IsNullOrEmpty(currentValue)) + if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(email)) { - return currentValue; + return null; } - string message = string.Format("Configuration value '{0}' is missing or invalid.", entryName); + return new Signature(name, email, now); + } - if (shouldThrowIfNotFound) + internal Signature BuildSignatureOrThrow(DateTimeOffset now) + { + var signature = BuildSignature(now); + if (signature == null) { - throw new LibGit2SharpException(message); + 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"); } - Log.Write(LogLevel.Warning, message); - - return defaultValue(); + return signature; } - private ConfigurationSafeHandle Snapshot() + private ConfigurationHandle Snapshot() { return Proxy.git_config_snapshot(configHandle); } + + /// + /// Perform a series of actions within a transaction. + /// + /// The configuration will be locked during this function and the changes will be committed at the end. These + /// changes will not be visible in the configuration until the end of this method. + /// + /// If the action throws an exception, the changes will be rolled back. + /// + /// The code to run under the transaction + public virtual unsafe void WithinTransaction(Action action) + { + IntPtr txn = IntPtr.Zero; + try + { + txn = Proxy.git_config_lock(configHandle); + action(); + Proxy.git_transaction_commit(txn); + } + finally + { + Proxy.git_transaction_free(txn); + } + } } } diff --git a/LibGit2Sharp/ConfigurationEntry.cs b/LibGit2Sharp/ConfigurationEntry.cs index 7456be3c4..13c153a2a 100644 --- a/LibGit2Sharp/ConfigurationEntry.cs +++ b/LibGit2Sharp/ConfigurationEntry.cs @@ -48,8 +48,7 @@ private string DebuggerDisplay { get { - return string.Format(CultureInfo.InvariantCulture, - "{0} = \"{1}\"", Key, Value); + return string.Format(CultureInfo.InvariantCulture, "{0} = \"{1}\"", Key, Value); } } } diff --git a/LibGit2Sharp/ConfigurationExtensions.cs b/LibGit2Sharp/ConfigurationExtensions.cs deleted file mode 100644 index 1ffd71591..000000000 --- a/LibGit2Sharp/ConfigurationExtensions.cs +++ /dev/null @@ -1,258 +0,0 @@ -using System; -using LibGit2Sharp.Core; - -namespace LibGit2Sharp -{ - /// - /// Provides helper overloads to a . - /// - public static class ConfigurationExtensions - { - /// - /// Get a configuration value for the given key parts. - /// - /// For example in order to get the value for this in a .git\config file: - /// - /// - /// [core] - /// bare = true - /// - /// - /// You would call: - /// - /// - /// bool isBare = repo.Config.Get<bool>(new []{ "core", "bare" }).Value; - /// - /// - /// - /// The configuration value type - /// The configuration being worked with. - /// The key parts - /// The , or null if not set - public static ConfigurationEntry Get(this Configuration config, string[] keyParts) - { - Ensure.ArgumentNotNull(keyParts, "keyParts"); - - return config.Get(string.Join(".", keyParts)); - } - - /// - /// Get a configuration value for the given key parts. - /// - /// For example in order to get the value for this in a .git\config file: - /// - /// - /// [difftool "kdiff3"] - /// path = c:/Program Files/KDiff3/kdiff3.exe - /// - /// - /// You would call: - /// - /// - /// string where = repo.Config.Get<string>("difftool", "kdiff3", "path").Value; - /// - /// - /// - /// The configuration value type - /// The configuration being worked with. - /// The first key part - /// The second key part - /// The third key part - /// The , or null if not set - public static ConfigurationEntry Get(this Configuration config, string firstKeyPart, string secondKeyPart, string thirdKeyPart) - { - Ensure.ArgumentNotNullOrEmptyString(firstKeyPart, "firstKeyPart"); - Ensure.ArgumentNotNullOrEmptyString(secondKeyPart, "secondKeyPart"); - Ensure.ArgumentNotNullOrEmptyString(thirdKeyPart, "thirdKeyPart"); - - return config.Get(new[] { firstKeyPart, secondKeyPart, thirdKeyPart }); - } - - /// - /// Get a configuration value for the given key. - /// - /// The configuration value type. - /// The configuration being worked with. - /// The key - /// The configuration value, or the default value for the selected if not found - public static T GetValueOrDefault(this Configuration config, string key) - { - return ValueOrDefault(config.Get(key), default(T)); - } - - /// - /// Get a configuration value for the given key, - /// or if the key is not set. - /// - /// The configuration value type. - /// The configuration being worked with. - /// The key - /// The default value if the key is not set. - /// The configuration value, or the default value - public static T GetValueOrDefault(this Configuration config, string key, T defaultValue) - { - return ValueOrDefault(config.Get(key), defaultValue); - } - - /// - /// Get a configuration value for the given key - /// - /// The configuration value type. - /// The configuration being worked with. - /// 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 static T GetValueOrDefault(this Configuration config, string key, ConfigurationLevel level) - { - return ValueOrDefault(config.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 configuration being worked with. - /// The key. - /// The configuration file into which the key should be searched for. - /// The selector used to generate a default value if the key is not set. - /// The configuration value, or the default value. - public static T GetValueOrDefault(this Configuration config, string key, ConfigurationLevel level, T defaultValue) - { - return ValueOrDefault(config.Get(key, level), defaultValue); - } - - /// - /// Get a configuration value for the given key parts - /// - /// The configuration value type. - /// The configuration being worked with. - /// The key parts. - /// The configuration value, or the default value for if not found - public static T GetValueOrDefault(this Configuration config, string[] keyParts) - { - return ValueOrDefault(config.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 configuration being worked with. - /// The key parts. - /// The default value if the key is not set. - /// The configuration value, or the default value. - public static T GetValueOrDefault(this Configuration config, string[] keyParts, T defaultValue) - { - return ValueOrDefault(config.Get(keyParts), defaultValue); - } - - /// - /// Get a configuration value for the given key parts. - /// - /// The configuration value type. - /// The configuration being worked with. - /// 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 static T GetValueOrDefault(this Configuration config, string firstKeyPart, string secondKeyPart, string thirdKeyPart) - { - return ValueOrDefault(config.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 configuration being worked with. - /// The first key part. - /// The second key part. - /// The third key part. - /// The default value if the key is not set. - /// The configuration value, or the default. - public static T GetValueOrDefault(this Configuration config, string firstKeyPart, string secondKeyPart, string thirdKeyPart, T defaultValue) - { - return ValueOrDefault(config.Get(firstKeyPart, secondKeyPart, thirdKeyPart), defaultValue); - } - - /// - /// Get a configuration value for the given key, - /// or a value generated by - /// if the key is not set. - /// - /// The configuration value type. - /// The configuration being worked with. - /// The key - /// The selector used to generate a default value if the key is not set. - /// The configuration value, or a generated default. - public static T GetValueOrDefault(this Configuration config, string key, Func defaultValueSelector) - { - return ValueOrDefault(config.Get(key), defaultValueSelector); - } - - /// - /// Get a configuration value for the given key, - /// or a value generated by - /// if the key is not set. - /// - /// The configuration value type. - /// The configuration being worked with. - /// The key. - /// The configuration file into which the key should be searched for. - /// The selector used to generate a default value if the key is not set. - /// The configuration value, or a generated default. - public static T GetValueOrDefault(this Configuration config, string key, ConfigurationLevel level, Func defaultValueSelector) - { - return ValueOrDefault(config.Get(key, level), defaultValueSelector); - } - - /// - /// Get a configuration value for the given key parts, - /// or a value generated by - /// if the key is not set. - /// - /// The configuration value type. - /// The configuration being worked with. - /// The key parts. - /// The selector used to generate a default value if the key is not set. - /// The configuration value, or a generated default. - public static T GetValueOrDefault(this Configuration config, string[] keyParts, Func defaultValueSelector) - { - return ValueOrDefault(config.Get(keyParts), defaultValueSelector); - } - - /// - /// Get a configuration value for the given key parts, - /// or a value generated by - /// if the key is not set. - /// - /// The configuration value type. - /// The configuration being worked with. - /// The first key part. - /// The second key part. - /// The third key part. - /// The selector used to generate a default value if the key is not set. - /// The configuration value, or a generated default. - public static T GetValueOrDefault(this Configuration config, string firstKeyPart, string secondKeyPart, string thirdKeyPart, Func defaultValueSelector) - { - return ValueOrDefault(config.Get(firstKeyPart, secondKeyPart, thirdKeyPart), defaultValueSelector); - } - - private static T ValueOrDefault(ConfigurationEntry value, T defaultValue) - { - return value == null ? defaultValue : value.Value; - } - - private static T ValueOrDefault(ConfigurationEntry value, Func defaultValueSelector) - { - Ensure.ArgumentNotNull(defaultValueSelector, "defaultValueSelector"); - - return value == null - ? defaultValueSelector() - : value.Value; - } - } -} diff --git a/LibGit2Sharp/ConfigurationLevel.cs b/LibGit2Sharp/ConfigurationLevel.cs index b8ab12097..9fd57df28 100644 --- a/LibGit2Sharp/ConfigurationLevel.cs +++ b/LibGit2Sharp/ConfigurationLevel.cs @@ -8,21 +8,26 @@ public enum ConfigurationLevel /// /// The local .git/config of the current repository. /// - Local = 4, + Local = 5, /// /// The global ~/.gitconfig of the current user. /// - Global = 3, + Global = 4, /// /// The global ~/.config/git/config of the current user. /// - Xdg = 2, + Xdg = 3, /// /// The system wide .gitconfig. /// - System = 1, + System = 2, + + /// + /// Another system-wide configuration on Windows. + /// + ProgramData = 1, } } diff --git a/LibGit2Sharp/ConflictCollection.cs b/LibGit2Sharp/ConflictCollection.cs index 8cd72a3b7..90d48fa33 100644 --- a/LibGit2Sharp/ConflictCollection.cs +++ b/LibGit2Sharp/ConflictCollection.cs @@ -102,10 +102,10 @@ private List AllConflicts() theirs = entry; break; default: - throw new InvalidOperationException(string.Format( - CultureInfo.InvariantCulture, - "Entry '{0}' bears an unexpected StageLevel '{1}'", - entry.Path, entry.StageLevel)); + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, + "Entry '{0}' bears an unexpected StageLevel '{1}'", + entry.Path, + entry.StageLevel)); } } diff --git a/LibGit2Sharp/ContentChanges.cs b/LibGit2Sharp/ContentChanges.cs index e5d84503e..221c99efa 100644 --- a/LibGit2Sharp/ContentChanges.cs +++ b/LibGit2Sharp/ContentChanges.cs @@ -20,12 +20,15 @@ public class ContentChanges protected ContentChanges() { } - internal ContentChanges(Repository repo, Blob oldBlob, Blob newBlob, GitDiffOptions options) + internal unsafe ContentChanges(Repository repo, Blob oldBlob, Blob newBlob, GitDiffOptions options) { Proxy.git_diff_blobs(repo.Handle, oldBlob != null ? oldBlob.Id : null, newBlob != null ? newBlob.Id : null, - options, FileCallback, HunkCallback, LineCallback); + options, + FileCallback, + HunkCallback, + LineCallback); } internal ContentChanges(bool isBinaryComparison) @@ -61,9 +64,9 @@ public virtual string Patch /// public virtual bool IsBinaryComparison { get; private set; } - private int FileCallback(GitDiffDelta delta, float progress, IntPtr payload) + private unsafe int FileCallback(git_diff_delta* delta, float progress, IntPtr payload) { - IsBinaryComparison = delta.IsBinary(); + IsBinaryComparison = delta->flags.HasFlag(GitDiffFlags.GIT_DIFF_FLAG_BINARY); if (!IsBinaryComparison) { @@ -75,7 +78,7 @@ private int FileCallback(GitDiffDelta delta, float progress, IntPtr payload) return 0; } - private int HunkCallback(GitDiffDelta delta, GitDiffHunk hunk, IntPtr payload) + private unsafe int HunkCallback(git_diff_delta* delta, GitDiffHunk hunk, IntPtr payload) { string decodedContent = LaxUtf8Marshaler.FromBuffer(hunk.Header, (int)hunk.HeaderLen); @@ -83,7 +86,7 @@ private int HunkCallback(GitDiffDelta delta, GitDiffHunk hunk, IntPtr payload) return 0; } - private int LineCallback(GitDiffDelta delta, GitDiffHunk hunk, GitDiffLine line, IntPtr payload) + private unsafe int LineCallback(git_diff_delta* delta, GitDiffHunk hunk, GitDiffLine line, IntPtr payload) { string decodedContent = LaxUtf8Marshaler.FromNative(line.content, (int)line.contentLen); @@ -120,7 +123,9 @@ private string DebuggerDisplay get { return string.Format(CultureInfo.InvariantCulture, - @"{{+{0}, -{1}}}", LinesAdded, LinesDeleted); + @"{{+{0}, -{1}}}", + LinesAdded, + LinesDeleted); } } } diff --git a/LibGit2Sharp/Core/ArrayMarshaler.cs b/LibGit2Sharp/Core/ArrayMarshaler.cs index b831f6dbc..4a37d241b 100644 --- a/LibGit2Sharp/Core/ArrayMarshaler.cs +++ b/LibGit2Sharp/Core/ArrayMarshaler.cs @@ -13,7 +13,7 @@ public ArrayMarshaler(T[] objs) for (var i = 0; i < objs.Length; i++) { - IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(T))); + IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf()); ptrs[i] = ptr; Marshal.StructureToPtr(objs[i], ptr, false); } diff --git a/LibGit2Sharp/Core/EncodingMarshaler.cs b/LibGit2Sharp/Core/EncodingMarshaler.cs index ad85c5ec9..0cafd9aa1 100644 --- a/LibGit2Sharp/Core/EncodingMarshaler.cs +++ b/LibGit2Sharp/Core/EncodingMarshaler.cs @@ -43,8 +43,9 @@ public virtual IntPtr MarshalManagedToNative(Object managedObj) if (str == null) { - throw new MarshalDirectiveException( - string.Format(CultureInfo.InvariantCulture, "{0} must be used on a string.", GetType().Name)); + throw new MarshalDirectiveException(string.Format(CultureInfo.InvariantCulture, + "{0} must be used on a string.", + GetType().Name)); } return FromManaged(encoding, str); @@ -59,7 +60,7 @@ public virtual Object MarshalNativeToManaged(IntPtr pNativeData) public static unsafe IntPtr FromManaged(Encoding encoding, String value) { - if (value == null) + if (encoding == null || value == null) { return IntPtr.Zero; } @@ -92,7 +93,12 @@ public static void Cleanup(IntPtr pNativeData) public static unsafe string FromNative(Encoding encoding, IntPtr pNativeData) { - if (pNativeData == IntPtr.Zero) + return FromNative(encoding, (byte*)pNativeData); + } + + public static unsafe string FromNative(Encoding encoding, byte* pNativeData) + { + if (pNativeData == null) { return null; } @@ -111,7 +117,7 @@ public static unsafe string FromNative(Encoding encoding, IntPtr pNativeData) return String.Empty; } - return new String((sbyte*)pNativeData.ToPointer(), 0, (int)(walk - start), encoding); + return new String((sbyte*)pNativeData, 0, (int)(walk - start), encoding); } public static unsafe string FromNative(Encoding encoding, IntPtr pNativeData, int length) diff --git a/LibGit2Sharp/Core/Ensure.cs b/LibGit2Sharp/Core/Ensure.cs index bc9e45506..9b00da4eb 100644 --- a/LibGit2Sharp/Core/Ensure.cs +++ b/LibGit2Sharp/Core/Ensure.cs @@ -87,44 +87,71 @@ public static void ArgumentDoesNotContainZeroByte(string argumentValue, string a "Zero bytes ('\\0') are not allowed. A zero byte has been found at position {0}.", zeroPos), argumentName); } - private static readonly Dictionary> + /// + /// Checks an argument to ensure it isn't a IntPtr.Zero (aka null). + /// + /// The argument value to check. + /// The name of the argument. + public static void ArgumentNotZeroIntPtr(IntPtr argumentValue, string argumentName) + { + if (argumentValue == IntPtr.Zero) + { + throw new ArgumentNullException(argumentName); + } + } + + /// + /// Checks a pointer argument to ensure it is the expected pointer value. + /// + /// The argument value to check. + /// The expected value. + /// The name of the argument. + public static void ArgumentIsExpectedIntPtr(IntPtr argumentValue, IntPtr expectedValue, string argumentName) + { + if (argumentValue != expectedValue) + { + throw new ArgumentException("Unexpected IntPtr value", argumentName); + } + } + + private static readonly Dictionary> GitErrorsToLibGit2SharpExceptions = - new Dictionary> + new Dictionary> { - { GitErrorCode.User, (m, r, c) => new UserCancelledException(m, r, c) }, - { GitErrorCode.BareRepo, (m, r, c) => new BareRepositoryException(m, r, c) }, - { GitErrorCode.Exists, (m, r, c) => new NameConflictException(m, r, c) }, - { GitErrorCode.InvalidSpecification, (m, r, c) => new InvalidSpecificationException(m, r, c) }, - { GitErrorCode.UnmergedEntries, (m, r, c) => new UnmergedIndexEntriesException(m, r, c) }, - { GitErrorCode.NonFastForward, (m, r, c) => new NonFastForwardException(m, r, c) }, - { GitErrorCode.Conflict, (m, r, c) => new CheckoutConflictException(m, r, c) }, - { GitErrorCode.LockedFile, (m, r, c) => new LockedFileException(m, r, c) }, - { GitErrorCode.NotFound, (m, r, c) => new NotFoundException(m, r, c) }, - { GitErrorCode.Peel, (m, r, c) => new PeelException(m, r, c) }, + { GitErrorCode.User, (m, c) => new UserCancelledException(m, c) }, + { GitErrorCode.BareRepo, (m, c) => new BareRepositoryException(m, c) }, + { GitErrorCode.Exists, (m, c) => new NameConflictException(m, c) }, + { GitErrorCode.InvalidSpecification, (m, c) => new InvalidSpecificationException(m, c) }, + { GitErrorCode.UnmergedEntries, (m, c) => new UnmergedIndexEntriesException(m, c) }, + { GitErrorCode.NonFastForward, (m, c) => new NonFastForwardException(m, c) }, + { GitErrorCode.Conflict, (m, c) => new CheckoutConflictException(m, c) }, + { GitErrorCode.LockedFile, (m, c) => new LockedFileException(m, c) }, + { GitErrorCode.NotFound, (m, c) => new NotFoundException(m, c) }, + { GitErrorCode.Peel, (m, c) => new PeelException(m, c) }, }; - private static void HandleError(int result) + private static unsafe void HandleError(int result) { string errorMessage; - GitError error = NativeMethods.giterr_last().MarshalAsGitError(); + GitErrorCategory errorCategory = GitErrorCategory.Unknown; + GitError* error = NativeMethods.giterr_last(); if (error == null) { - error = new GitError { Category = GitErrorCategory.Unknown, Message = IntPtr.Zero }; errorMessage = "No error message has been provided by the native library"; } else { - errorMessage = LaxUtf8Marshaler.FromNative(error.Message); + errorMessage = LaxUtf8Marshaler.FromNative(error->Message); } - Func exceptionBuilder; + Func exceptionBuilder; if (!GitErrorsToLibGit2SharpExceptions.TryGetValue((GitErrorCode)result, out exceptionBuilder)) { - exceptionBuilder = (m, r, c) => new LibGit2SharpException(m, r, c); + exceptionBuilder = (m, c) => new LibGit2SharpException(m, c); } - throw exceptionBuilder(errorMessage, (GitErrorCode)result, error.Category); + throw exceptionBuilder(errorMessage, errorCategory); } /// @@ -223,44 +250,19 @@ public static void ArgumentPositiveInt32(long argumentValue, string argumentName /// The identifier to examine. public static void GitObjectIsNotNull(GitObject gitObject, string identifier) { - Func exceptionBuilder; - - if (string.Equals("HEAD", identifier, StringComparison.Ordinal)) - { - exceptionBuilder = m => new UnbornBranchException(m); - } - else + if (gitObject != null) { - exceptionBuilder = m => new NotFoundException(m); + return; } - GitObjectIsNotNull(gitObject, identifier, exceptionBuilder); - } - + var messageFormat = "No valid git object identified by '{0}' exists in the repository."; - /// - /// 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. - /// The builder which constructs an from a message. - public static void GitObjectIsNotNull( - GitObject gitObject, - string identifier, - Func exceptionBuilder) - { - if (gitObject != null) + if (string.Equals("HEAD", identifier, StringComparison.Ordinal)) { - return; + throw new UnbornBranchException(messageFormat, identifier); } - throw exceptionBuilder(string.Format(CultureInfo.InvariantCulture, - "No valid git object identified by '{0}' exists in the repository.", - identifier)); + throw new NotFoundException(messageFormat, identifier); } } } diff --git a/LibGit2Sharp/Core/Epoch.cs b/LibGit2Sharp/Core/Epoch.cs deleted file mode 100644 index 0f2657267..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 . - /// - internal 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/FileHistory.cs b/LibGit2Sharp/Core/FileHistory.cs index 42a1aa2f9..5c10a1a24 100644 --- a/LibGit2Sharp/Core/FileHistory.cs +++ b/LibGit2Sharp/Core/FileHistory.cs @@ -72,9 +72,10 @@ internal FileHistory(Repository repo, string path, CommitFilter 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.", - "queryFilter"); + { + throw new ArgumentException("Unsupported sort strategy. Only 'Topological', 'Time', or 'Topological | Time' are allowed.", + "queryFilter"); + } _repo = repo; _path = path; @@ -162,11 +163,13 @@ private static void DetermineParentPaths(IRepository repo, Commit currentCommit, private static string ParentPath(IRepository repo, Commit currentCommit, string currentPath, Commit parentCommit) { - 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; + 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/FilePathMarshaler.cs b/LibGit2Sharp/Core/FilePathMarshaler.cs index 2732b77d8..209254ac5 100644 --- a/LibGit2Sharp/Core/FilePathMarshaler.cs +++ b/LibGit2Sharp/Core/FilePathMarshaler.cs @@ -18,7 +18,7 @@ internal class LaxFilePathNoCleanupMarshaler : LaxFilePathMarshaler { private static readonly LaxFilePathNoCleanupMarshaler staticInstance = new LaxFilePathNoCleanupMarshaler(); - public new static ICustomMarshaler GetInstance(String cookie) + public new static ICustomMarshaler GetInstance(string cookie) { return staticInstance; } @@ -50,7 +50,7 @@ internal class StrictFilePathMarshaler : StrictUtf8Marshaler { private static readonly StrictFilePathMarshaler staticInstance = new StrictFilePathMarshaler(); - public new static ICustomMarshaler GetInstance(String cookie) + public new static ICustomMarshaler GetInstance(string cookie) { return staticInstance; } @@ -68,8 +68,9 @@ public override IntPtr MarshalManagedToNative(Object managedObj) if (null == filePath) { - throw new MarshalDirectiveException( - string.Format(CultureInfo.InvariantCulture, "{0} must be used on a FilePath.", GetType().Name)); + throw new MarshalDirectiveException(string.Format(CultureInfo.InvariantCulture, + "{0} must be used on a FilePath.", + this.GetType().Name)); } return FromManaged(filePath); @@ -97,7 +98,7 @@ internal class LaxFilePathMarshaler : LaxUtf8Marshaler { private static readonly LaxFilePathMarshaler staticInstance = new LaxFilePathMarshaler(); - public new static ICustomMarshaler GetInstance(String cookie) + public new static ICustomMarshaler GetInstance(string cookie) { return staticInstance; } @@ -116,6 +117,11 @@ public override Object MarshalNativeToManaged(IntPtr pNativeData) return LaxUtf8Marshaler.FromNative(pNativeData); } + public new static unsafe FilePath FromNative(char* buffer) + { + return LaxUtf8Marshaler.FromNative(buffer); + } + public new static FilePath FromBuffer(byte[] buffer) { return LaxUtf8Marshaler.FromBuffer(buffer); diff --git a/LibGit2Sharp/Core/GitBlame.cs b/LibGit2Sharp/Core/GitBlame.cs index fd08b991c..f36294729 100644 --- a/LibGit2Sharp/Core/GitBlame.cs +++ b/LibGit2Sharp/Core/GitBlame.cs @@ -37,36 +37,38 @@ internal enum GitBlameOptionFlags /// Restrict the search of commits to those reachable /// following only the first parents. /// - GIT_BLAME_FIRST_PARENT = (1<<4), + GIT_BLAME_FIRST_PARENT = (1 << 4), } [StructLayout(LayoutKind.Sequential)] - internal class GitBlameOptions + internal class git_blame_options { public uint version = 1; + public GitDiffFindOptions FindOptions; public GitBlameOptionFlags flags; - public UInt16 MinMatchCharacters; - public GitOid NewestCommit; - public GitOid OldestCommit; - public uint MinLine; - public uint MaxLine; + + public UInt16 min_match_characters; + public git_oid newest_commit; + public git_oid oldest_commit; + public UIntPtr min_line; + public UIntPtr max_line; } [StructLayout(LayoutKind.Sequential)] - internal class GitBlameHunk + internal unsafe struct git_blame_hunk { - public ushort LinesInHunk; - - public GitOid FinalCommitId; - public ushort FinalStartLineNumber; - public IntPtr FinalSignature; + public UIntPtr lines_in_hunk; - public GitOid OrigCommitId; - public IntPtr OrigPath; - public ushort OrigStartLineNumber; - public IntPtr OrigSignature; + 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; + public byte boundary; } internal static class BlameStrategyExtensions @@ -79,8 +81,9 @@ public static GitBlameOptionFlags ToGitBlameOptionFlags(this BlameStrategy strat return GitBlameOptionFlags.GIT_BLAME_NORMAL; default: - throw new NotSupportedException( - string.Format(CultureInfo.InvariantCulture, "{0} is not supported at this time", strategy)); + throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture, + "{0} is not supported at this time", + strategy)); } } } diff --git a/LibGit2Sharp/Core/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..a5151123c --- /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 3424094be..053258565 100644 --- a/LibGit2Sharp/Core/GitCheckoutOpts.cs +++ b/LibGit2Sharp/Core/GitCheckoutOpts.cs @@ -118,6 +118,7 @@ internal enum CheckoutStrategy GIT_CHECKOUT_UPDATE_SUBMODULES_IF_CHANGED = (1 << 17), } + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int checkout_notify_cb( CheckoutNotifyFlags why, IntPtr path, @@ -126,12 +127,14 @@ internal delegate int checkout_notify_cb( IntPtr workdir, IntPtr payload); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate void progress_cb( IntPtr strPtr, UIntPtr completed_steps, UIntPtr total_steps, IntPtr payload); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int perfdata_cb( IntPtr perfdata, IntPtr payload); diff --git a/LibGit2Sharp/Core/GitCheckoutOptsWrapper.cs b/LibGit2Sharp/Core/GitCheckoutOptsWrapper.cs index 26614fa94..0fba82754 100644 --- a/LibGit2Sharp/Core/GitCheckoutOptsWrapper.cs +++ b/LibGit2Sharp/Core/GitCheckoutOptsWrapper.cs @@ -66,12 +66,15 @@ internal static CheckoutStrategy CheckoutStrategyFromFileConflictStrategy(Checko case CheckoutFileConflictStrategy.Ours: flags = CheckoutStrategy.GIT_CHECKOUT_USE_OURS; break; + case CheckoutFileConflictStrategy.Theirs: flags = CheckoutStrategy.GIT_CHECKOUT_USE_THEIRS; break; + case CheckoutFileConflictStrategy.Merge: flags = CheckoutStrategy.GIT_CHECKOUT_CONFLICT_STYLE_MERGE; break; + case CheckoutFileConflictStrategy.Diff3: flags = CheckoutStrategy.GIT_CHECKOUT_CONFLICT_STYLE_DIFF3; break; diff --git a/LibGit2Sharp/Core/GitCloneOptions.cs b/LibGit2Sharp/Core/GitCloneOptions.cs index bed647e31..1ad86c5c3 100644 --- a/LibGit2Sharp/Core/GitCloneOptions.cs +++ b/LibGit2Sharp/Core/GitCloneOptions.cs @@ -23,8 +23,6 @@ internal struct GitCloneOptions public GitCloneLocal Local; public IntPtr CheckoutBranch; - public IntPtr signature; // Really a SignatureSafeHandle - public IntPtr RepositoryCb; public IntPtr RepositoryCbPayload; diff --git a/LibGit2Sharp/Core/GitConfigEntry.cs b/LibGit2Sharp/Core/GitConfigEntry.cs index 85d1669a8..9eaa9e468 100644 --- a/LibGit2Sharp/Core/GitConfigEntry.cs +++ b/LibGit2Sharp/Core/GitConfigEntry.cs @@ -4,12 +4,13 @@ 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 uint include_depth; public uint level; - public IntPtr freePtr; - public IntPtr payloadPtr; + public void* freePtr; + public void* payloadPtr; } } diff --git a/LibGit2Sharp/Core/GitCredential.cs b/LibGit2Sharp/Core/GitCredential.cs new file mode 100644 index 000000000..f3c6605fd --- /dev/null +++ b/LibGit2Sharp/Core/GitCredential.cs @@ -0,0 +1,13 @@ +using System; +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core +{ + [StructLayout(LayoutKind.Sequential)] + internal struct GitCredential + { + public GitCredentialType credtype; + public IntPtr free; + } +} + diff --git a/LibGit2Sharp/Core/GitCredentialType.cs b/LibGit2Sharp/Core/GitCredentialType.cs index 00a7460d9..0ab1273e2 100644 --- a/LibGit2Sharp/Core/GitCredentialType.cs +++ b/LibGit2Sharp/Core/GitCredentialType.cs @@ -32,5 +32,20 @@ internal enum GitCredentialType /// TODO /// SshInteractive = (1 << 4), + + /// + /// Username-only information + /// + /// If the SSH transport does not know which username to use, + /// it will ask via this credential type. + /// + Username = (1 << 5), + + /// + /// Credentials read from memory. + /// + /// Only available for libssh2+OpenSSL for now. + /// + SshMemory = (1 << 6), } } diff --git a/LibGit2Sharp/Core/GitCredentialUserpass.cs b/LibGit2Sharp/Core/GitCredentialUserpass.cs new file mode 100644 index 000000000..ab6cf6c7a --- /dev/null +++ b/LibGit2Sharp/Core/GitCredentialUserpass.cs @@ -0,0 +1,14 @@ +using System; +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core +{ + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct GitCredentialUserpass + { + public GitCredential parent; + public char* username; + public char* password; + } +} + diff --git a/LibGit2Sharp/Core/GitDiff.cs b/LibGit2Sharp/Core/GitDiff.cs index 1c71fbb5c..4b4022b37 100644 --- a/LibGit2Sharp/Core/GitDiff.cs +++ b/LibGit2Sharp/Core/GitDiff.cs @@ -133,6 +133,13 @@ internal enum GitDiffOptionFlags * Options controlling how output will be generated */ + /// + /// Use a heuristic that takes indentation and whitespace into account + /// which generally can produce better diffs when dealing with ambiguous + /// diff hunks. + /// + GIT_DIFF_INDENT_HEURISTIC = (1 << 18), + /// /// Treat all files as text, disabling binary attributes and detection /// @@ -191,12 +198,20 @@ internal enum GitDiffOptionFlags GIT_DIFF_SHOW_BINARY = (1 << 30), } + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int diff_notify_cb( IntPtr diff_so_far, IntPtr delta_to_add, IntPtr matched_pathspec, IntPtr payload); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate int diff_progress_cb( + IntPtr diff_so_far, + IntPtr old_path, + IntPtr new_path, + IntPtr payload); + [StructLayout(LayoutKind.Sequential)] internal class GitDiffOptions : IDisposable { @@ -208,7 +223,8 @@ internal class GitDiffOptions : IDisposable public SubmoduleIgnore IgnoreSubmodules; public GitStrArrayManaged PathSpec; public diff_notify_cb NotifyCallback; - public IntPtr NotifyPayload; + public diff_progress_cb ProgressCallback; + public IntPtr Payload; /* options controlling how to diff text is generated */ @@ -231,27 +247,29 @@ internal enum GitDiffFlags GIT_DIFF_FLAG_BINARY = (1 << 0), GIT_DIFF_FLAG_NOT_BINARY = (1 << 1), GIT_DIFF_FLAG_VALID_ID = (1 << 2), + GIT_DIFF_FLAG_EXISTS = (1 << 3), } [StructLayout(LayoutKind.Sequential)] - internal class GitDiffFile + internal unsafe struct git_diff_file { - public GitOid Id; - public IntPtr Path; + public git_oid Id; + public char* Path; public Int64 Size; public GitDiffFlags Flags; public UInt16 Mode; + public UInt16 IdAbbrev; } [StructLayout(LayoutKind.Sequential)] - internal class GitDiffDelta + internal unsafe struct git_diff_delta { - public ChangeKind Status; - public GitDiffFlags Flags; - public UInt16 Similarity; - public UInt16 NumberOfFiles; - public GitDiffFile OldFile; - public GitDiffFile NewFile; + public ChangeKind status; + public GitDiffFlags flags; + public UInt16 similarity; + public UInt16 nfiles; + public git_diff_file old_file; + public git_diff_file new_file; } [StructLayout(LayoutKind.Sequential)] @@ -295,11 +313,11 @@ enum GitDiffLineOrigin : byte enum GitDiffFormat { - GIT_DIFF_FORMAT_PATCH = 1, // < full git diff + GIT_DIFF_FORMAT_PATCH = 1, // < full git diff GIT_DIFF_FORMAT_PATCH_HEADER = 2, // < just the file headers of patch - GIT_DIFF_FORMAT_RAW = 3, // < like git diff --raw - GIT_DIFF_FORMAT_NAME_ONLY = 4, // < like git diff --name-only - GIT_DIFF_FORMAT_NAME_STATUS = 5, // < like git diff --name-status + GIT_DIFF_FORMAT_RAW = 3, // < like git diff --raw + GIT_DIFF_FORMAT_NAME_ONLY = 4, // < like git diff --name-only + GIT_DIFF_FORMAT_NAME_STATUS = 5, // < like git diff --name-status } [Flags] @@ -332,6 +350,9 @@ enum GitDiffFindFlags // turn on all finding features GIT_DIFF_FIND_ALL = (0x0ff), + // does no work trying to find renames + GIT_DIFF_FIND_NO_RENAMES = (1 << 8), + // measure similarity ignoring leading whitespace (default) GIT_DIFF_FIND_IGNORE_LEADING_WHITESPACE = 0, // measure similarity ignoring all whitespace @@ -349,9 +370,9 @@ enum GitDiffFindFlags } [StructLayout(LayoutKind.Sequential)] - internal class GitDiffFindOptions + internal struct GitDiffFindOptions { - public uint Version = 1; + public uint Version; public GitDiffFindFlags Flags; public UInt16 RenameThreshold; public UInt16 RenameFromRewriteThreshold; @@ -362,4 +383,34 @@ internal class GitDiffFindOptions // TODO public IntPtr SimilarityMetric; } + + [Flags] + enum GitDiffBinaryType + { + // There is no binary delta. + GIT_DIFF_BINARY_NONE = 0, + + // The binary data is the literal contents of the file. */ + GIT_DIFF_BINARY_LITERAL, + + // The binary data is the delta from one side to the other. */ + GIT_DIFF_BINARY_DELTA, + } + + [StructLayout(LayoutKind.Sequential)] + internal class GitDiffBinaryFile + { + public GitDiffBinaryType Type; + public IntPtr Data; + public UIntPtr DataLen; + public UIntPtr InflatedLen; + } + + [StructLayout(LayoutKind.Sequential)] + internal class GitDiffBinary + { + public uint ContainsData; + public GitDiffBinaryFile OldFile; + public GitDiffBinaryFile NewFile; + } } diff --git a/LibGit2Sharp/Core/GitDiffExtensions.cs b/LibGit2Sharp/Core/GitDiffExtensions.cs deleted file mode 100644 index a131d2091..000000000 --- a/LibGit2Sharp/Core/GitDiffExtensions.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace LibGit2Sharp.Core -{ - internal static class GitDiffExtensions - { - public static bool IsBinary(this GitDiffDelta delta) - { - return delta.Flags.HasFlag(GitDiffFlags.GIT_DIFF_FLAG_BINARY); - } - } -} diff --git a/LibGit2Sharp/Core/GitError.cs b/LibGit2Sharp/Core/GitError.cs index 0041097da..3d982f466 100644 --- a/LibGit2Sharp/Core/GitError.cs +++ b/LibGit2Sharp/Core/GitError.cs @@ -4,9 +4,9 @@ namespace LibGit2Sharp.Core { [StructLayout(LayoutKind.Sequential)] - internal class GitError + internal unsafe struct GitError { - public IntPtr Message; + public char* Message; public GitErrorCategory Category; } } diff --git a/LibGit2Sharp/Core/GitErrorCategory.cs b/LibGit2Sharp/Core/GitErrorCategory.cs index 66e10cbaf..5fc4c7d57 100644 --- a/LibGit2Sharp/Core/GitErrorCategory.cs +++ b/LibGit2Sharp/Core/GitErrorCategory.cs @@ -30,5 +30,12 @@ internal enum GitErrorCategory Filter, Revert, Callback, + CherryPick, + Describe, + Rebase, + Filesystem, + Patch, + Worktree, + Sha1 } } diff --git a/LibGit2Sharp/Core/GitErrorCode.cs b/LibGit2Sharp/Core/GitErrorCode.cs index a6c44e68b..6180cc4a8 100644 --- a/LibGit2Sharp/Core/GitErrorCode.cs +++ b/LibGit2Sharp/Core/GitErrorCode.cs @@ -91,6 +91,31 @@ internal enum GitErrorCode /// 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. /// @@ -100,5 +125,15 @@ internal enum GitErrorCode /// There are no more entries left to iterate. /// IterOver = -31, + + /// + /// Internal-only. + /// + Retry = -32, + + /// + /// A retrieved object did not match its expected ID. + /// + Mismatch = -33, } } diff --git a/LibGit2Sharp/Core/GitFetchOptions.cs b/LibGit2Sharp/Core/GitFetchOptions.cs index 02f996dff..3f0baa2c2 100644 --- a/LibGit2Sharp/Core/GitFetchOptions.cs +++ b/LibGit2Sharp/Core/GitFetchOptions.cs @@ -10,5 +10,7 @@ internal class GitFetchOptions public FetchPruneStrategy Prune; public bool UpdateFetchHead = true; public TagFetchMode download_tags; + public GitProxyOptions ProxyOptions; + public GitStrArrayManaged CustomHeaders; } } diff --git a/LibGit2Sharp/Core/GitFetchOptionsWrapper.cs b/LibGit2Sharp/Core/GitFetchOptionsWrapper.cs new file mode 100644 index 000000000..351947bbe --- /dev/null +++ b/LibGit2Sharp/Core/GitFetchOptionsWrapper.cs @@ -0,0 +1,36 @@ +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) + { + this.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; + + this.Options.CustomHeaders.Dispose(); + 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 11bee09ea..ac0c141ed 100644 --- a/LibGit2Sharp/Core/GitIndexEntry.cs +++ b/LibGit2Sharp/Core/GitIndexEntry.cs @@ -4,21 +4,28 @@ namespace LibGit2Sharp.Core { [StructLayout(LayoutKind.Sequential)] - internal class GitIndexEntry + internal unsafe struct git_index_mtime + { + public int seconds; + public uint nanoseconds; + } + + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct git_index_entry { internal const ushort GIT_IDXENTRY_VALID = 0x8000; - public GitIndexTime CTime; - public GitIndexTime MTime; - public uint Dev; - public uint Ino; - public uint Mode; - public uint Uid; - public uint Gid; + 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 GitOid Id; - public ushort Flags; - public ushort ExtendedFlags; - public IntPtr Path; + public git_oid id; + public ushort flags; + public ushort extended_flags; + public char* path; } } diff --git a/LibGit2Sharp/Core/GitIndexNameEntry.cs b/LibGit2Sharp/Core/GitIndexNameEntry.cs index 251a00791..47a9b1bcb 100644 --- a/LibGit2Sharp/Core/GitIndexNameEntry.cs +++ b/LibGit2Sharp/Core/GitIndexNameEntry.cs @@ -4,10 +4,10 @@ namespace LibGit2Sharp.Core { [StructLayout(LayoutKind.Sequential)] - internal class GitIndexNameEntry + internal unsafe struct git_index_name_entry { - public IntPtr Ancestor; - public IntPtr Ours; - public IntPtr Theirs; + public char* ancestor; + public char* ours; + public char* theirs; } } diff --git a/LibGit2Sharp/Core/GitIndexReucEntry.cs b/LibGit2Sharp/Core/GitIndexReucEntry.cs index dfd684edb..bc98d50df 100644 --- a/LibGit2Sharp/Core/GitIndexReucEntry.cs +++ b/LibGit2Sharp/Core/GitIndexReucEntry.cs @@ -4,14 +4,14 @@ namespace LibGit2Sharp.Core { [StructLayout(LayoutKind.Sequential)] - internal class GitIndexReucEntry + internal unsafe struct git_index_reuc_entry { public uint AncestorMode; public uint OurMode; public uint TheirMode; - public GitOid AncestorId; - public GitOid OurId; - public GitOid TheirId; - public IntPtr Path; + public git_oid AncestorId; + public git_oid OurId; + public git_oid TheirId; + public char* Path; } } diff --git a/LibGit2Sharp/Core/GitMergeOpts.cs b/LibGit2Sharp/Core/GitMergeOpts.cs index e122cbdc1..48675a2d0 100644 --- a/LibGit2Sharp/Core/GitMergeOpts.cs +++ b/LibGit2Sharp/Core/GitMergeOpts.cs @@ -8,7 +8,7 @@ internal struct GitMergeOpts { public uint Version; - public GitMergeTreeFlags MergeTreeFlags; + public GitMergeFlag MergeTreeFlags; /// /// Similarity to consider a file renamed. @@ -27,6 +27,20 @@ internal struct GitMergeOpts /// public IntPtr SimilarityMetric; + /// + /// Maximum number of times to merge common ancestors to build a + /// virtual merge base when faced with criss-cross merges. When this + /// limit is reached, the next ancestor will simply be used instead of + /// attempting to merge it. The default is unlimited. + /// + public uint RecursionLimit; + + /// + /// Default merge driver to be used when both sides of a merge have + /// changed. The default is the `text` driver. + /// + public string DefaultDriver; + /// /// Flags for automerging content. /// @@ -35,7 +49,7 @@ internal struct GitMergeOpts /// /// File merging flags. /// - public GitMergeFileFlags FileFlags; + public GitMergeFileFlag FileFlags; } /// @@ -98,21 +112,43 @@ internal enum GitMergePreference } [Flags] - internal enum GitMergeTreeFlags + internal enum GitMergeFlag { /// /// No options. /// - GIT_MERGE_TREE_NORMAL = 0, + GIT_MERGE_NORMAL = 0, + + /// + /// Detect renames that occur between the common ancestor and the "ours" + /// side or the common ancestor and the "theirs" side. This will enable + /// the ability to merge between a modified and renamed file. + /// + GIT_MERGE_FIND_RENAMES = (1 << 0), + + /// + /// If a conflict occurs, exit immediately instead of attempting to + /// continue resolving conflicts. The merge operation will fail with + /// GIT_EMERGECONFLICT and no index will be returned. + /// + GIT_MERGE_FAIL_ON_CONFLICT = (1 << 1), + + /// + /// Do not write the REUC extension on the generated index + /// + GIT_MERGE_SKIP_REUC = (1 << 2), /// - /// GIT_MERGE_TREE_FIND_RENAMES in libgit2 + /// 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_TREE_FIND_RENAMES = (1 << 0), + GIT_MERGE_NO_RECURSIVE = (1 << 3), } [Flags] - internal enum GitMergeFileFlags + internal enum GitMergeFileFlag { /// /// Defaults diff --git a/LibGit2Sharp/Core/GitObjectLazyGroup.cs b/LibGit2Sharp/Core/GitObjectLazyGroup.cs index 7d4429166..4e0ba384e 100644 --- a/LibGit2Sharp/Core/GitObjectLazyGroup.cs +++ b/LibGit2Sharp/Core/GitObjectLazyGroup.cs @@ -3,7 +3,7 @@ namespace LibGit2Sharp.Core { - internal class GitObjectLazyGroup : LazyGroup + internal class GitObjectLazyGroup : LazyGroup { private readonly ObjectId id; @@ -13,7 +13,7 @@ public GitObjectLazyGroup(Repository repo, ObjectId id) this.id = id; } - protected override void EvaluateInternal(Action evaluator) + protected override void EvaluateInternal(Action evaluator) { using (var osw = new ObjectSafeWrapper(id, repo.Handle)) { @@ -21,12 +21,14 @@ protected override void EvaluateInternal(Action evaluator) } } - public static ILazy Singleton(Repository repo, ObjectId id, Func resultSelector) + public static ILazy Singleton(Repository repo, ObjectId id, Func resultSelector) { return Singleton(() => { using (var osw = new ObjectSafeWrapper(id, repo.Handle)) + { return resultSelector(osw.ObjectPtr); + } }); } } diff --git a/LibGit2Sharp/Core/GitObjectType.cs b/LibGit2Sharp/Core/GitObjectType.cs index a38523f2c..50d8412c2 100644 --- a/LibGit2Sharp/Core/GitObjectType.cs +++ b/LibGit2Sharp/Core/GitObjectType.cs @@ -75,8 +75,9 @@ public static TreeEntryTargetType ToTreeEntryTargetType(this GitObjectType type) return TreeEntryTargetType.Blob; default: - throw new InvalidOperationException( - string.Format(CultureInfo.InvariantCulture, "Cannot map {0} to a TreeEntryTargetType.", type)); + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, + "Cannot map {0} to a TreeEntryTargetType.", + type)); } } @@ -97,8 +98,9 @@ public static ObjectType ToObjectType(this GitObjectType type) return ObjectType.Tag; default: - throw new InvalidOperationException( - string.Format(CultureInfo.InvariantCulture, "Cannot map {0} to a ObjectType.", type)); + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, + "Cannot map {0} to a ObjectType.", + type)); } } } diff --git a/LibGit2Sharp/Core/GitOdbBackend.cs b/LibGit2Sharp/Core/GitOdbBackend.cs index e36cdc531..0d68a3433 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; @@ -34,6 +34,7 @@ static GitOdbBackend() public IntPtr Refresh; public foreach_callback Foreach; public IntPtr Writepack; + public IntPtr Freshen; public free_callback Free; /* The libgit2 structure definition ends here. Subsequent fields are for libgit2sharp bookkeeping. */ @@ -54,6 +55,7 @@ static GitOdbBackend() /// [in] A pointer to the backend which is being asked to perform the task. /// [in] The OID which the backend is being asked to look up. /// 0 if successful; an error code otherwise. + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int read_callback( out IntPtr buffer_p, out UIntPtr len_p, @@ -76,6 +78,7 @@ public delegate int read_callback( /// [in] The short-form OID which the backend is being asked to look up. /// [in] The length of the short-form OID (short_oid). /// 0 if successful; an error code otherwise. + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int read_prefix_callback( out GitOid out_oid, out IntPtr buffer_p, @@ -94,6 +97,7 @@ public delegate int read_prefix_callback( /// [in] A pointer to the backend which is being asked to perform the task. /// [in] The OID which the backend is being asked to look up. /// 0 if successful; an error code otherwise. + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int read_header_callback( out UIntPtr len_p, out GitObjectType type_p, @@ -110,6 +114,7 @@ public delegate int read_header_callback( /// [in] The length of the buffer pointed to by data. /// [in] The type of the object. /// 0 if successful; an error code otherwise. + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int write_callback( IntPtr backend, ref GitOid oid, @@ -127,6 +132,7 @@ 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, @@ -141,6 +147,7 @@ public delegate int writestream_callback( /// [in] A pointer to the backend which is being asked to perform the task. /// [in] The object ID that the caller is requesting. /// 0 if successful; an error code otherwise. + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int readstream_callback( out IntPtr stream_out, IntPtr backend, @@ -153,6 +160,7 @@ public delegate int readstream_callback( /// [in] A pointer to the backend which is being asked to perform the task. /// [in] The object ID that the caller is requesting. /// True if the object exists; false otherwise + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate bool exists_callback( IntPtr backend, ref GitOid oid); @@ -168,6 +176,7 @@ public delegate bool exists_callback( /// [in] The short-form OID which the backend is being asked to look up. /// [in] The length of the short-form OID (short_oid). /// 1 if the object exists, 0 if the object doesn't; an error code otherwise. + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int exists_prefix_callback( ref GitOid found_oid, IntPtr backend, @@ -181,6 +190,7 @@ public delegate int exists_prefix_callback( /// [in] A pointer to the backend which is being asked to perform the task. /// [in] The callback function to invoke. /// [in] An arbitrary parameter to pass through to the callback + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int foreach_callback( IntPtr backend, foreach_callback_callback cb, @@ -190,6 +200,7 @@ public delegate int foreach_callback( /// The owner of this backend is finished with it. The backend is asked to clean up and shut down. /// /// [in] A pointer to the backend which is being freed. + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void free_callback( IntPtr backend); @@ -199,6 +210,7 @@ public delegate void free_callback( /// The oid of each object in the backing store. /// The arbitrary parameter given to foreach_callback. /// A non-negative result indicates the enumeration should continue. Otherwise, the enumeration should stop. + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int foreach_callback_callback( IntPtr oid, IntPtr data); diff --git a/LibGit2Sharp/Core/GitOdbBackendStream.cs b/LibGit2Sharp/Core/GitOdbBackendStream.cs index f7eec27d5..984274f04 100644 --- a/LibGit2Sharp/Core/GitOdbBackendStream.cs +++ b/LibGit2Sharp/Core/GitOdbBackendStream.cs @@ -15,7 +15,7 @@ internal class GitOdbBackendStream { static GitOdbBackendStream() { - GCHandleOffset = Marshal.OffsetOf(typeof(GitOdbBackendStream), "GCHandle").ToInt32(); + GCHandleOffset = Marshal.OffsetOf(nameof(GCHandle)).ToInt32(); } public IntPtr Backend; @@ -38,21 +38,22 @@ static GitOdbBackendStream() public static int GCHandleOffset; + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int read_callback( IntPtr stream, IntPtr buffer, UIntPtr len); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int write_callback( IntPtr stream, IntPtr buffer, UIntPtr len); - public delegate int finalize_write_callback( - IntPtr stream, - ref GitOid oid); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int finalize_write_callback(IntPtr stream, ref GitOid oid); - public delegate void free_callback( - IntPtr stream); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void free_callback(IntPtr stream); } } diff --git a/LibGit2Sharp/Core/GitOid.cs b/LibGit2Sharp/Core/GitOid.cs index 9c5930908..f466621b1 100644 --- a/LibGit2Sharp/Core/GitOid.cs +++ b/LibGit2Sharp/Core/GitOid.cs @@ -2,6 +2,12 @@ namespace LibGit2Sharp.Core { + internal struct git_oid + { + public const int Size = 20; + public unsafe fixed byte Id[20]; + } + /// /// Represents a unique id in git which is the sha1 hash of this id's content. /// @@ -27,5 +33,13 @@ public static implicit operator ObjectId(GitOid? oid) { return oid == null ? null : new ObjectId(oid.Value); } + + /// + /// Static convenience property to return an id (all zeros). + /// + public static GitOid Empty + { + get { return new GitOid(); } + } } } diff --git a/LibGit2Sharp/Core/GitProxyOptions.cs b/LibGit2Sharp/Core/GitProxyOptions.cs new file mode 100644 index 000000000..b62b8e08f --- /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 IntPtr CredentialsCb; + public IntPtr CertificateCheck; + public IntPtr CbPayload; + } +} diff --git a/LibGit2Sharp/Core/GitPushOptions.cs b/LibGit2Sharp/Core/GitPushOptions.cs index d4bbcdc3f..f733534d2 100644 --- a/LibGit2Sharp/Core/GitPushOptions.cs +++ b/LibGit2Sharp/Core/GitPushOptions.cs @@ -8,5 +8,7 @@ internal class GitPushOptions public int Version = 1; public int PackbuilderDegreeOfParallelism; public GitRemoteCallbacks RemoteCallbacks; + public GitProxyOptions ProxyOptions; + public GitStrArrayManaged CustomHeaders; } } diff --git a/LibGit2Sharp/Core/GitPushUpdate.cs b/LibGit2Sharp/Core/GitPushUpdate.cs index f38697a42..ef0ba827a 100644 --- a/LibGit2Sharp/Core/GitPushUpdate.cs +++ b/LibGit2Sharp/Core/GitPushUpdate.cs @@ -4,11 +4,11 @@ namespace LibGit2Sharp.Core { [StructLayout(LayoutKind.Sequential)] - internal class GitPushUpdate + internal unsafe struct git_push_update { - IntPtr src_refname; - IntPtr dst_refname; - GitOid src; - GitOid dst; + 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..e1416e803 --- /dev/null +++ b/LibGit2Sharp/Core/GitRebaseOptions.cs @@ -0,0 +1,23 @@ +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 }; + + public NativeMethods.commit_signing_callback signing_callback; + } +} diff --git a/LibGit2Sharp/Core/GitRemoteCallbacks.cs b/LibGit2Sharp/Core/GitRemoteCallbacks.cs index 71537f762..54cdb46ed 100644 --- a/LibGit2Sharp/Core/GitRemoteCallbacks.cs +++ b/LibGit2Sharp/Core/GitRemoteCallbacks.cs @@ -17,7 +17,7 @@ internal struct GitRemoteCallbacks internal NativeMethods.git_cred_acquire_cb acquire_credentials; - internal IntPtr certificate_check; + internal NativeMethods.git_transport_certificate_check_cb certificate_check; internal NativeMethods.git_transfer_progress_callback download_progress; @@ -34,5 +34,7 @@ internal struct GitRemoteCallbacks internal IntPtr transport; internal IntPtr payload; + + internal NativeMethods.url_resolve_callback resolve_url; } } diff --git a/LibGit2Sharp/Core/GitRemoteHead.cs b/LibGit2Sharp/Core/GitRemoteHead.cs index 02c2def8b..cbaf3c818 100644 --- a/LibGit2Sharp/Core/GitRemoteHead.cs +++ b/LibGit2Sharp/Core/GitRemoteHead.cs @@ -4,12 +4,12 @@ namespace LibGit2Sharp.Core { [StructLayout(LayoutKind.Sequential)] - internal struct GitRemoteHead + internal unsafe struct git_remote_head { - public bool Local; - public GitOid Oid; - public GitOid Loid; - public IntPtr NamePtr; - public IntPtr SymRefTargetPtr; + public int Local; + public git_oid Oid; + public git_oid Loid; + public char* Name; + public char* SymrefTarget; } } diff --git a/LibGit2Sharp/Core/GitRepositoryInitOptions.cs b/LibGit2Sharp/Core/GitRepositoryInitOptions.cs index c42bc83f4..f639a0d8d 100644 --- a/LibGit2Sharp/Core/GitRepositoryInitOptions.cs +++ b/LibGit2Sharp/Core/GitRepositoryInitOptions.cs @@ -19,10 +19,10 @@ internal class GitRepositoryInitOptions : IDisposable public static GitRepositoryInitOptions BuildFrom(FilePath workdirPath, bool isBare) { var opts = new GitRepositoryInitOptions - { - Flags = GitRepositoryInitFlags.GIT_REPOSITORY_INIT_MKPATH, - Mode = 0 /* GIT_REPOSITORY_INIT_SHARED_UMASK */ - }; + { + Flags = GitRepositoryInitFlags.GIT_REPOSITORY_INIT_MKPATH, + Mode = 0 /* GIT_REPOSITORY_INIT_SHARED_UMASK */ + }; if (workdirPath != null) { diff --git a/LibGit2Sharp/Core/GitRevertOpts.cs b/LibGit2Sharp/Core/GitRevertOpts.cs index 7976243c2..3d6583a81 100644 --- a/LibGit2Sharp/Core/GitRevertOpts.cs +++ b/LibGit2Sharp/Core/GitRevertOpts.cs @@ -12,6 +12,6 @@ internal class GitRevertOpts public GitMergeOpts MergeOpts = new GitMergeOpts { Version = 1 }; - public GitCheckoutOpts CheckoutOpts = new GitCheckoutOpts {version = 1}; + public GitCheckoutOpts CheckoutOpts = new GitCheckoutOpts { version = 1 }; } } diff --git a/LibGit2Sharp/Core/GitSignature.cs b/LibGit2Sharp/Core/GitSignature.cs index 3261d4c57..5641af368 100644 --- a/LibGit2Sharp/Core/GitSignature.cs +++ b/LibGit2Sharp/Core/GitSignature.cs @@ -4,10 +4,10 @@ namespace LibGit2Sharp.Core { [StructLayout(LayoutKind.Sequential)] - internal class GitSignature + internal unsafe struct git_signature { - public IntPtr Name; - public IntPtr Email; - public GitTime When; + public char* name; + public char* email; + public git_time when; } } diff --git a/LibGit2Sharp/Core/GitSmartSubtransport.cs b/LibGit2Sharp/Core/GitSmartSubtransport.cs index d9b3c7545..d4ce6d728 100644 --- a/LibGit2Sharp/Core/GitSmartSubtransport.cs +++ b/LibGit2Sharp/Core/GitSmartSubtransport.cs @@ -8,7 +8,7 @@ internal class GitSmartSubtransport { static GitSmartSubtransport() { - GCHandleOffset = Marshal.OffsetOf(typeof(GitSmartSubtransport), "GCHandle").ToInt32(); + GCHandleOffset = Marshal.OffsetOf(nameof(GCHandle)).ToInt32(); } public action_callback Action; @@ -23,14 +23,17 @@ static GitSmartSubtransport() public static int GCHandleOffset; + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int action_callback( out IntPtr stream, IntPtr subtransport, IntPtr url, GitSmartSubtransportAction action); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int close_callback(IntPtr subtransport); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void free_callback(IntPtr subtransport); } } diff --git a/LibGit2Sharp/Core/GitSmartSubtransportRegistration.cs b/LibGit2Sharp/Core/GitSmartSubtransportRegistration.cs index e721c1e79..c8ae4fde7 100644 --- a/LibGit2Sharp/Core/GitSmartSubtransportRegistration.cs +++ b/LibGit2Sharp/Core/GitSmartSubtransportRegistration.cs @@ -10,6 +10,7 @@ internal class GitSmartSubtransportRegistration public uint Rpc; public uint Param; + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int create_callback( out IntPtr subtransport, IntPtr owner, diff --git a/LibGit2Sharp/Core/GitSmartSubtransportStream.cs b/LibGit2Sharp/Core/GitSmartSubtransportStream.cs index f73218c09..ae371b980 100644 --- a/LibGit2Sharp/Core/GitSmartSubtransportStream.cs +++ b/LibGit2Sharp/Core/GitSmartSubtransportStream.cs @@ -8,7 +8,7 @@ internal class GitSmartSubtransportStream { static GitSmartSubtransportStream() { - GCHandleOffset = Marshal.OffsetOf(typeof(GitSmartSubtransportStream), "GCHandle").ToInt32(); + GCHandleOffset = Marshal.OffsetOf(nameof(GCHandle)).ToInt32(); } public IntPtr SmartTransport; @@ -25,18 +25,20 @@ static GitSmartSubtransportStream() public static int GCHandleOffset; + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int read_callback( IntPtr stream, IntPtr buffer, UIntPtr buf_size, out UIntPtr bytes_read); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int write_callback( IntPtr stream, IntPtr buffer, UIntPtr len); - public delegate void free_callback( - IntPtr stream); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void free_callback(IntPtr stream); } } diff --git a/LibGit2Sharp/Core/GitStashApplyOpts.cs b/LibGit2Sharp/Core/GitStashApplyOpts.cs new file mode 100644 index 000000000..739c40b0c --- /dev/null +++ b/LibGit2Sharp/Core/GitStashApplyOpts.cs @@ -0,0 +1,20 @@ +using System; +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core +{ + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate int stash_apply_progress_cb(StashApplyProgress progress, IntPtr payload); + + [StructLayout(LayoutKind.Sequential)] + internal class GitStashApplyOpts + { + public uint Version = 1; + + public StashApplyModifiers Flags; + public GitCheckoutOpts CheckoutOptions; + + public stash_apply_progress_cb ApplyProgressCallback; + public IntPtr ProgressPayload; + } +} diff --git a/LibGit2Sharp/Core/GitStatusEntry.cs b/LibGit2Sharp/Core/GitStatusEntry.cs index 3e46c7b23..73e6547a8 100644 --- a/LibGit2Sharp/Core/GitStatusEntry.cs +++ b/LibGit2Sharp/Core/GitStatusEntry.cs @@ -7,21 +7,21 @@ namespace LibGit2Sharp.Core /// A status entry from libgit2. /// [StructLayout(LayoutKind.Sequential)] - internal class GitStatusEntry + internal unsafe struct git_status_entry { /// /// Calculated status of a filepath in the working directory considering the current and the . /// - public FileStatus Status; + public FileStatus status; /// /// The difference between the and . /// - public IntPtr HeadToIndexPtr; + public git_diff_delta* head_to_index; /// /// The difference between the and the working directory. /// - public IntPtr IndexToWorkDirPtr; + public git_diff_delta* index_to_workdir; } } diff --git a/LibGit2Sharp/Core/GitStatusOptions.cs b/LibGit2Sharp/Core/GitStatusOptions.cs index 3e9dbd5d9..192a9737d 100644 --- a/LibGit2Sharp/Core/GitStatusOptions.cs +++ b/LibGit2Sharp/Core/GitStatusOptions.cs @@ -13,6 +13,8 @@ internal class GitStatusOptions : IDisposable public GitStrArrayManaged PathSpec; + public unsafe void* Baseline = (void*)0; + public void Dispose() { PathSpec.Dispose(); 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 index 4fb07411a..59c2b3f80 100644 --- a/LibGit2Sharp/Core/GitSubmoduleOptions.cs +++ b/LibGit2Sharp/Core/GitSubmoduleOptions.cs @@ -3,7 +3,7 @@ namespace LibGit2Sharp.Core { [StructLayout(LayoutKind.Sequential)] - internal struct GitSubmoduleOptions + internal struct GitSubmoduleUpdateOptions { public uint Version; @@ -12,5 +12,7 @@ internal struct GitSubmoduleOptions public GitFetchOptions FetchOptions; public CheckoutStrategy CloneCheckoutStrategy; + + public int AllowFetch; } } diff --git a/LibGit2Sharp/Core/GitTime.cs b/LibGit2Sharp/Core/GitTime.cs index 304500d0c..e8051048e 100644 --- a/LibGit2Sharp/Core/GitTime.cs +++ b/LibGit2Sharp/Core/GitTime.cs @@ -3,9 +3,9 @@ namespace LibGit2Sharp.Core { [StructLayout(LayoutKind.Sequential)] - internal class GitTime + internal unsafe struct git_time { - public long Time; - public int Offset; + public long time; + public int offset; } } diff --git a/LibGit2Sharp/Core/GitWorktree.cs b/LibGit2Sharp/Core/GitWorktree.cs new file mode 100644 index 000000000..64f90ebb7 --- /dev/null +++ b/LibGit2Sharp/Core/GitWorktree.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +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; + } + + [StructLayout(LayoutKind.Sequential)] + internal class git_worktree_prune_options + { + public uint version = 1; + + public GitWorktreePruneOptionFlags flags; + } +} diff --git a/LibGit2Sharp/Core/GitWriteStream.cs b/LibGit2Sharp/Core/GitWriteStream.cs new file mode 100644 index 000000000..891a765fb --- /dev/null +++ b/LibGit2Sharp/Core/GitWriteStream.cs @@ -0,0 +1,27 @@ +using System; +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core +{ + [StructLayout(LayoutKind.Sequential)] + internal struct GitWriteStream + { + [MarshalAs(UnmanagedType.FunctionPtr)] + public write_fn write; + + [MarshalAs(UnmanagedType.FunctionPtr)] + public close_fn close; + + [MarshalAs(UnmanagedType.FunctionPtr)] + public free_fn free; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int write_fn(IntPtr stream, IntPtr buffer, UIntPtr len); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int close_fn(IntPtr stream); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void free_fn(IntPtr stream); + } +} diff --git a/LibGit2Sharp/Core/Handles/BlameSafeHandle.cs b/LibGit2Sharp/Core/Handles/BlameSafeHandle.cs deleted file mode 100644 index e8218e3da..000000000 --- a/LibGit2Sharp/Core/Handles/BlameSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class BlameSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandleImpl() - { - Proxy.git_blame_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/BranchIteratorSafeHandle.cs b/LibGit2Sharp/Core/Handles/BranchIteratorSafeHandle.cs deleted file mode 100644 index 6764bee66..000000000 --- a/LibGit2Sharp/Core/Handles/BranchIteratorSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class BranchIteratorSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandleImpl() - { - Proxy.git_branch_iterator_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/ConfigurationIteratorSafeHandle.cs b/LibGit2Sharp/Core/Handles/ConfigurationIteratorSafeHandle.cs deleted file mode 100644 index 0d2cb6ab6..000000000 --- a/LibGit2Sharp/Core/Handles/ConfigurationIteratorSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class ConfigurationIteratorSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandleImpl() - { - Proxy.git_config_iterator_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/ConfigurationSafeHandle.cs b/LibGit2Sharp/Core/Handles/ConfigurationSafeHandle.cs deleted file mode 100644 index 01b2a4c9d..000000000 --- a/LibGit2Sharp/Core/Handles/ConfigurationSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class ConfigurationSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandleImpl() - { - Proxy.git_config_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/ConflictIteratorSafeHandle.cs b/LibGit2Sharp/Core/Handles/ConflictIteratorSafeHandle.cs deleted file mode 100644 index 255563af5..000000000 --- a/LibGit2Sharp/Core/Handles/ConflictIteratorSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class ConflictIteratorSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandleImpl() - { - Proxy.git_index_conflict_iterator_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/DescribeResultSafeHandle.cs b/LibGit2Sharp/Core/Handles/DescribeResultSafeHandle.cs deleted file mode 100644 index cbdb225e8..000000000 --- a/LibGit2Sharp/Core/Handles/DescribeResultSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class DescribeResultSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandleImpl() - { - Proxy.git_describe_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/DiffSafeHandle.cs b/LibGit2Sharp/Core/Handles/DiffSafeHandle.cs deleted file mode 100644 index fe117cba1..000000000 --- a/LibGit2Sharp/Core/Handles/DiffSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class DiffSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandleImpl() - { - Proxy.git_diff_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/GitAnnotatedCommitHandle.cs b/LibGit2Sharp/Core/Handles/GitAnnotatedCommitHandle.cs deleted file mode 100644 index f125772d0..000000000 --- a/LibGit2Sharp/Core/Handles/GitAnnotatedCommitHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class GitAnnotatedCommitHandle : SafeHandleBase - { - protected override bool ReleaseHandleImpl() - { - Proxy.git_annotated_commit_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/GitConfigEntryHandle.cs b/LibGit2Sharp/Core/Handles/GitConfigEntryHandle.cs deleted file mode 100644 index 677c5fbdc..000000000 --- a/LibGit2Sharp/Core/Handles/GitConfigEntryHandle.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class GitConfigEntryHandle : SafeHandleBase - { - public GitConfigEntry MarshalAsGitConfigEntry() - { - return handle.MarshalAs(); - } - - protected override bool ReleaseHandleImpl() - { - Proxy.git_config_entry_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/GitErrorSafeHandle.cs b/LibGit2Sharp/Core/Handles/GitErrorSafeHandle.cs deleted file mode 100644 index d0010a635..000000000 --- a/LibGit2Sharp/Core/Handles/GitErrorSafeHandle.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; - -namespace LibGit2Sharp.Core.Handles -{ - internal class GitErrorSafeHandle : NotOwnedSafeHandleBase - { - public GitError MarshalAsGitError() - { - // Required on Mono < 3.0.8 - // https://bugzilla.xamarin.com/show_bug.cgi?id=11417 - // https://github.com/mono/mono/commit/9cdddca7ec283f3b9181f3f69c1acecc0d9cc289 - if (handle == IntPtr.Zero) - { - return null; - } - - return handle.MarshalAs(); - } - } -} diff --git a/LibGit2Sharp/Core/Handles/GitObjectSafeHandle.cs b/LibGit2Sharp/Core/Handles/GitObjectSafeHandle.cs deleted file mode 100644 index 46de2dfe7..000000000 --- a/LibGit2Sharp/Core/Handles/GitObjectSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class GitObjectSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandleImpl() - { - Proxy.git_object_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/GitRefSpecHandle.cs b/LibGit2Sharp/Core/Handles/GitRefSpecHandle.cs deleted file mode 100644 index a795472f6..000000000 --- a/LibGit2Sharp/Core/Handles/GitRefSpecHandle.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class GitRefSpecHandle : NotOwnedSafeHandleBase - { - } -} diff --git a/LibGit2Sharp/Core/Handles/IndexEntrySafeHandle.cs b/LibGit2Sharp/Core/Handles/IndexEntrySafeHandle.cs deleted file mode 100644 index f09e01c00..000000000 --- a/LibGit2Sharp/Core/Handles/IndexEntrySafeHandle.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class IndexEntrySafeHandle : NotOwnedSafeHandleBase - { - public GitIndexEntry MarshalAsGitIndexEntry() - { - return handle.MarshalAs(); - } - } -} diff --git a/LibGit2Sharp/Core/Handles/IndexNameEntrySafeHandle.cs b/LibGit2Sharp/Core/Handles/IndexNameEntrySafeHandle.cs deleted file mode 100644 index 9c5421a58..000000000 --- a/LibGit2Sharp/Core/Handles/IndexNameEntrySafeHandle.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class IndexNameEntrySafeHandle : NotOwnedSafeHandleBase - { - public GitIndexNameEntry MarshalAsGitIndexNameEntry() - { - return handle.MarshalAs(); - } - } -} diff --git a/LibGit2Sharp/Core/Handles/IndexReucEntrySafeHandle.cs b/LibGit2Sharp/Core/Handles/IndexReucEntrySafeHandle.cs deleted file mode 100644 index 5150081d1..000000000 --- a/LibGit2Sharp/Core/Handles/IndexReucEntrySafeHandle.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class IndexReucEntrySafeHandle : NotOwnedSafeHandleBase - { - public GitIndexReucEntry MarshalAsGitIndexReucEntry() - { - return handle.MarshalAs(); - } - } -} diff --git a/LibGit2Sharp/Core/Handles/IndexSafeHandle.cs b/LibGit2Sharp/Core/Handles/IndexSafeHandle.cs deleted file mode 100644 index d757efb68..000000000 --- a/LibGit2Sharp/Core/Handles/IndexSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class IndexSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandleImpl() - { - Proxy.git_index_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/Libgit2Object.cs b/LibGit2Sharp/Core/Handles/Libgit2Object.cs new file mode 100644 index 000000000..892ebde90 --- /dev/null +++ b/LibGit2Sharp/Core/Handles/Libgit2Object.cs @@ -0,0 +1,182 @@ +// This activates a lightweight mode which will help put under the light +// incorrectly released handles by outputing 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 throrough 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 System.Linq; +using System.Diagnostics; +using System.Globalization; +using System.Collections.Generic; + +#if LEAKS_IDENTIFYING +namespace LibGit2Sharp.Core +{ + /// + /// 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 +{ + internal unsafe abstract class Libgit2Object : IDisposable + { +#if LEAKS_TRACKING + private readonly string trace; + private readonly Guid id; +#endif + + protected void* ptr; + + internal void* Handle + { + get + { + return ptr; + } + } + + bool owned; + bool disposed; + + internal unsafe Libgit2Object(void* handle, bool owned) + { + this.ptr = handle; + this.owned = owned; + +#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 unsafe Libgit2Object(IntPtr ptr, bool owned) + : this(ptr.ToPointer(), owned) + { + } + + ~Libgit2Object() + { + Dispose(false); + } + + internal bool IsNull + { + get + { + return ptr == null; + } + } + + internal IntPtr AsIntPtr() + { + return new IntPtr(ptr); + } + + public abstract void Free(); + + void Dispose(bool disposing) + { +#if LEAKS_IDENTIFYING + bool leaked = !disposing && ptr != null; + + if (leaked) + { + LeaksContainer.Add(GetType().Name); + } +#endif + + if (!disposed) + { + if (owned) + { + Free(); + } + + ptr = null; + } + + disposed = true; + +#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 + } + + public void Dispose() + { + Dispose(true); + } + } +} + diff --git a/LibGit2Sharp/Core/Handles/NotOwnedSafeHandleBase.cs b/LibGit2Sharp/Core/Handles/NotOwnedSafeHandleBase.cs deleted file mode 100644 index d9c05abf4..000000000 --- a/LibGit2Sharp/Core/Handles/NotOwnedSafeHandleBase.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace LibGit2Sharp.Core.Handles -{ - internal abstract class NotOwnedSafeHandleBase : SafeHandle - { - protected NotOwnedSafeHandleBase() - : base(IntPtr.Zero, false) - { - } - - public override bool IsInvalid - { - get { return IsZero; } - } - - public bool IsZero - { - get { return (handle == IntPtr.Zero); } - } - - protected override bool ReleaseHandle() - { - // Does nothing as the pointer is owned by libgit2 - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/NoteSafeHandle.cs b/LibGit2Sharp/Core/Handles/NoteSafeHandle.cs deleted file mode 100644 index a62c5105a..000000000 --- a/LibGit2Sharp/Core/Handles/NoteSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class NoteSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandleImpl() - { - Proxy.git_note_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/NullGitObjectSafeHandle.cs b/LibGit2Sharp/Core/Handles/NullGitObjectSafeHandle.cs deleted file mode 100644 index 5c4521193..000000000 --- a/LibGit2Sharp/Core/Handles/NullGitObjectSafeHandle.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; - -namespace LibGit2Sharp.Core.Handles -{ - internal class NullGitObjectSafeHandle : GitObjectSafeHandle - { - public NullGitObjectSafeHandle() - { - handle = IntPtr.Zero; - } - - protected override bool ReleaseHandleImpl() - { - // Nothing to release - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/NullIndexSafeHandle.cs b/LibGit2Sharp/Core/Handles/NullIndexSafeHandle.cs deleted file mode 100644 index dcd4988fb..000000000 --- a/LibGit2Sharp/Core/Handles/NullIndexSafeHandle.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; - -namespace LibGit2Sharp.Core.Handles -{ - internal class NullIndexSafeHandle : IndexSafeHandle - { - public NullIndexSafeHandle() - { - handle = IntPtr.Zero; - } - - protected override bool ReleaseHandleImpl() - { - // Nothing to release - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/NullRepositorySafeHandle.cs b/LibGit2Sharp/Core/Handles/NullRepositorySafeHandle.cs deleted file mode 100644 index cf57b96fc..000000000 --- a/LibGit2Sharp/Core/Handles/NullRepositorySafeHandle.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; - -namespace LibGit2Sharp.Core.Handles -{ - internal class NullRepositorySafeHandle : SafeHandleBase - { - public NullRepositorySafeHandle() - { - handle = IntPtr.Zero; - } - - protected override bool ReleaseHandleImpl() - { - // Nothing to release - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/ObjectDatabaseSafeHandle.cs b/LibGit2Sharp/Core/Handles/ObjectDatabaseSafeHandle.cs deleted file mode 100644 index e8cfb2b0a..000000000 --- a/LibGit2Sharp/Core/Handles/ObjectDatabaseSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class ObjectDatabaseSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandleImpl() - { - Proxy.git_odb_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/Objects.cs b/LibGit2Sharp/Core/Handles/Objects.cs new file mode 100644 index 000000000..5f8db722e --- /dev/null +++ b/LibGit2Sharp/Core/Handles/Objects.cs @@ -0,0 +1,582 @@ + +using System; + +namespace LibGit2Sharp.Core.Handles +{ + + internal unsafe class TreeEntryHandle : Libgit2Object + { + internal TreeEntryHandle(git_tree_entry *ptr, bool owned) + : base((void *) ptr, owned) + { + } + + internal TreeEntryHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + public override void Free() + { + NativeMethods.git_tree_entry_free((git_tree_entry*) ptr); + } + + public static implicit operator git_tree_entry*(TreeEntryHandle handle) + { + return (git_tree_entry*) handle.Handle; + } + } + + internal unsafe class ReferenceHandle : Libgit2Object + { + internal ReferenceHandle(git_reference *ptr, bool owned) + : base((void *) ptr, owned) + { + } + + internal ReferenceHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + public override void Free() + { + NativeMethods.git_reference_free((git_reference*) ptr); + } + + public static implicit operator git_reference*(ReferenceHandle handle) + { + return (git_reference*) handle.Handle; + } + } + + internal unsafe class RepositoryHandle : Libgit2Object + { + internal RepositoryHandle(git_repository *ptr, bool owned) + : base((void *) ptr, owned) + { + } + + internal RepositoryHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + public override void Free() + { + NativeMethods.git_repository_free((git_repository*) ptr); + } + + public static implicit operator git_repository*(RepositoryHandle handle) + { + return (git_repository*) handle.Handle; + } + } + + internal unsafe class SignatureHandle : Libgit2Object + { + internal SignatureHandle(git_signature *ptr, bool owned) + : base((void *) ptr, owned) + { + } + + internal SignatureHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + public override void Free() + { + NativeMethods.git_signature_free((git_signature*) ptr); + } + + public static implicit operator git_signature*(SignatureHandle handle) + { + return (git_signature*) handle.Handle; + } + } + + internal unsafe class StatusListHandle : Libgit2Object + { + internal StatusListHandle(git_status_list *ptr, bool owned) + : base((void *) ptr, owned) + { + } + + internal StatusListHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + public override void Free() + { + NativeMethods.git_status_list_free((git_status_list*) ptr); + } + + public static implicit operator git_status_list*(StatusListHandle handle) + { + return (git_status_list*) handle.Handle; + } + } + + internal unsafe class BlameHandle : Libgit2Object + { + internal BlameHandle(git_blame *ptr, bool owned) + : base((void *) ptr, owned) + { + } + + internal BlameHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + public override void Free() + { + NativeMethods.git_blame_free((git_blame*) ptr); + } + + public static implicit operator git_blame*(BlameHandle handle) + { + return (git_blame*) handle.Handle; + } + } + + internal unsafe class DiffHandle : Libgit2Object + { + internal DiffHandle(git_diff *ptr, bool owned) + : base((void *) ptr, owned) + { + } + + internal DiffHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + public override void Free() + { + NativeMethods.git_diff_free((git_diff*) ptr); + } + + public static implicit operator git_diff*(DiffHandle handle) + { + return (git_diff*) handle.Handle; + } + } + + internal unsafe class PatchHandle : Libgit2Object + { + internal PatchHandle(git_patch *ptr, bool owned) + : base((void *) ptr, owned) + { + } + + internal PatchHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + public override void Free() + { + NativeMethods.git_patch_free((git_patch*) ptr); + } + + public static implicit operator git_patch*(PatchHandle handle) + { + return (git_patch*) handle.Handle; + } + } + + internal unsafe class ConfigurationHandle : Libgit2Object + { + internal ConfigurationHandle(git_config *ptr, bool owned) + : base((void *) ptr, owned) + { + } + + internal ConfigurationHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + public override void Free() + { + NativeMethods.git_config_free((git_config*) ptr); + } + + public static implicit operator git_config*(ConfigurationHandle handle) + { + return (git_config*) handle.Handle; + } + } + + internal unsafe class ConflictIteratorHandle : Libgit2Object + { + internal ConflictIteratorHandle(git_index_conflict_iterator *ptr, bool owned) + : base((void *) ptr, owned) + { + } + + internal ConflictIteratorHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + public override void Free() + { + NativeMethods.git_index_conflict_iterator_free((git_index_conflict_iterator*) ptr); + } + + public static implicit operator git_index_conflict_iterator*(ConflictIteratorHandle handle) + { + return (git_index_conflict_iterator*) handle.Handle; + } + } + + internal unsafe class IndexHandle : Libgit2Object + { + internal IndexHandle(git_index *ptr, bool owned) + : base((void *) ptr, owned) + { + } + + internal IndexHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + public override void Free() + { + NativeMethods.git_index_free((git_index*) ptr); + } + + public static implicit operator git_index*(IndexHandle handle) + { + return (git_index*) handle.Handle; + } + } + + internal unsafe class ReflogHandle : Libgit2Object + { + internal ReflogHandle(git_reflog *ptr, bool owned) + : base((void *) ptr, owned) + { + } + + internal ReflogHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + public override void Free() + { + NativeMethods.git_reflog_free((git_reflog*) ptr); + } + + public static implicit operator git_reflog*(ReflogHandle handle) + { + return (git_reflog*) handle.Handle; + } + } + + internal unsafe class TreeBuilderHandle : Libgit2Object + { + internal TreeBuilderHandle(git_treebuilder *ptr, bool owned) + : base((void *) ptr, owned) + { + } + + internal TreeBuilderHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + public override void Free() + { + NativeMethods.git_treebuilder_free((git_treebuilder*) ptr); + } + + public static implicit operator git_treebuilder*(TreeBuilderHandle handle) + { + return (git_treebuilder*) handle.Handle; + } + } + + internal unsafe class PackBuilderHandle : Libgit2Object + { + internal PackBuilderHandle(git_packbuilder *ptr, bool owned) + : base((void *) ptr, owned) + { + } + + internal PackBuilderHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + public override void Free() + { + NativeMethods.git_packbuilder_free((git_packbuilder*) ptr); + } + + public static implicit operator git_packbuilder*(PackBuilderHandle handle) + { + return (git_packbuilder*) handle.Handle; + } + } + + internal unsafe class NoteHandle : Libgit2Object + { + internal NoteHandle(git_note *ptr, bool owned) + : base((void *) ptr, owned) + { + } + + internal NoteHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + public override void Free() + { + NativeMethods.git_note_free((git_note*) ptr); + } + + public static implicit operator git_note*(NoteHandle handle) + { + return (git_note*) handle.Handle; + } + } + + internal unsafe class DescribeResultHandle : Libgit2Object + { + internal DescribeResultHandle(git_describe_result *ptr, bool owned) + : base((void *) ptr, owned) + { + } + + internal DescribeResultHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + public override void Free() + { + NativeMethods.git_describe_result_free((git_describe_result*) ptr); + } + + public static implicit operator git_describe_result*(DescribeResultHandle handle) + { + return (git_describe_result*) handle.Handle; + } + } + + internal unsafe class SubmoduleHandle : Libgit2Object + { + internal SubmoduleHandle(git_submodule *ptr, bool owned) + : base((void *) ptr, owned) + { + } + + internal SubmoduleHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + public override void Free() + { + NativeMethods.git_submodule_free((git_submodule*) ptr); + } + + public static implicit operator git_submodule*(SubmoduleHandle handle) + { + return (git_submodule*) handle.Handle; + } + } + + internal unsafe class AnnotatedCommitHandle : Libgit2Object + { + internal AnnotatedCommitHandle(git_annotated_commit *ptr, bool owned) + : base((void *) ptr, owned) + { + } + + internal AnnotatedCommitHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + public override void Free() + { + NativeMethods.git_annotated_commit_free((git_annotated_commit*) ptr); + } + + public static implicit operator git_annotated_commit*(AnnotatedCommitHandle handle) + { + return (git_annotated_commit*) handle.Handle; + } + } + + internal unsafe class ObjectDatabaseHandle : Libgit2Object + { + internal ObjectDatabaseHandle(git_odb *ptr, bool owned) + : base((void *) ptr, owned) + { + } + + internal ObjectDatabaseHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + public override void Free() + { + NativeMethods.git_odb_free((git_odb*) ptr); + } + + public static implicit operator git_odb*(ObjectDatabaseHandle handle) + { + return (git_odb*) handle.Handle; + } + } + + internal unsafe class RevWalkerHandle : Libgit2Object + { + internal RevWalkerHandle(git_revwalk *ptr, bool owned) + : base((void *) ptr, owned) + { + } + + internal RevWalkerHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + public override void Free() + { + NativeMethods.git_revwalk_free((git_revwalk*) ptr); + } + + public static implicit operator git_revwalk*(RevWalkerHandle handle) + { + return (git_revwalk*) handle.Handle; + } + } + + internal unsafe class RemoteHandle : Libgit2Object + { + internal RemoteHandle(git_remote *ptr, bool owned) + : base((void *) ptr, owned) + { + } + + internal RemoteHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + public override void Free() + { + NativeMethods.git_remote_free((git_remote*) ptr); + } + + public static implicit operator git_remote*(RemoteHandle handle) + { + return (git_remote*) handle.Handle; + } + } + + internal unsafe class ObjectHandle : Libgit2Object + { + internal ObjectHandle(git_object *ptr, bool owned) + : base((void *) ptr, owned) + { + } + + internal ObjectHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + public override void Free() + { + NativeMethods.git_object_free((git_object*) ptr); + } + + public static implicit operator git_object*(ObjectHandle handle) + { + return (git_object*) handle.Handle; + } + } + + internal unsafe class RebaseHandle : Libgit2Object + { + internal RebaseHandle(git_rebase *ptr, bool owned) + : base((void *) ptr, owned) + { + } + + internal RebaseHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + public override void Free() + { + NativeMethods.git_rebase_free((git_rebase*) ptr); + } + + public static implicit operator git_rebase*(RebaseHandle handle) + { + return (git_rebase*) handle.Handle; + } + } + + internal unsafe class OdbStreamHandle : Libgit2Object + { + internal OdbStreamHandle(git_odb_stream *ptr, bool owned) + : base((void *) ptr, owned) + { + } + + internal OdbStreamHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + public override void Free() + { + NativeMethods.git_odb_stream_free((git_odb_stream*) ptr); + } + + public static implicit operator git_odb_stream*(OdbStreamHandle handle) + { + return (git_odb_stream*) handle.Handle; + } + } + + internal unsafe class WorktreeHandle : Libgit2Object + { + internal WorktreeHandle(git_worktree *ptr, bool owned) + : base((void *) ptr, owned) + { + } + + internal WorktreeHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + public override void Free() + { + NativeMethods.git_worktree_free((git_worktree*) ptr); + } + + public static implicit operator git_worktree*(WorktreeHandle handle) + { + return (git_worktree*) handle.Handle; + } + } + +} diff --git a/LibGit2Sharp/Core/Handles/Objects.tt b/LibGit2Sharp/Core/Handles/Objects.tt new file mode 100644 index 000000000..a6d1fa251 --- /dev/null +++ b/LibGit2Sharp/Core/Handles/Objects.tt @@ -0,0 +1,99 @@ +<#@ 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((void *) ptr, owned) + { + } + + internal <#= csNames[i] #>(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + public override void Free() + { + NativeMethods.<#= cNames[i] #>_free((<#= cNames[i] #>*) ptr); + } + + public static implicit operator <#= cNames[i] #>*(<#= csNames[i] #> handle) + { + return (<#= cNames[i] #>*) handle.Handle; + } + } + +<# +} +#> +} diff --git a/LibGit2Sharp/Core/Handles/OdbStreamSafeHandle.cs b/LibGit2Sharp/Core/Handles/OdbStreamSafeHandle.cs deleted file mode 100644 index 10dc69db8..000000000 --- a/LibGit2Sharp/Core/Handles/OdbStreamSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class OdbStreamSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandleImpl() - { - Proxy.git_odb_stream_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/OidSafeHandle.cs b/LibGit2Sharp/Core/Handles/OidSafeHandle.cs deleted file mode 100644 index a30959506..000000000 --- a/LibGit2Sharp/Core/Handles/OidSafeHandle.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace LibGit2Sharp.Core.Handles -{ - internal class OidSafeHandle : NotOwnedSafeHandleBase - { - private GitOid? MarshalAsGitOid() - { - return IsInvalid ? null : (GitOid?)MarshalAsGitOid(handle); - } - - private static GitOid MarshalAsGitOid(IntPtr data) - { - var gitOid = new GitOid { Id = new byte[GitOid.Size] }; - Marshal.Copy(data, gitOid.Id, 0, GitOid.Size); - return gitOid; - } - - public ObjectId MarshalAsObjectId() - { - return MarshalAsGitOid(); - } - } -} diff --git a/LibGit2Sharp/Core/Handles/PatchSafeHandle.cs b/LibGit2Sharp/Core/Handles/PatchSafeHandle.cs deleted file mode 100644 index 97c0dc9bb..000000000 --- a/LibGit2Sharp/Core/Handles/PatchSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class PatchSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandleImpl() - { - Proxy.git_patch_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/ReferenceSafeHandle.cs b/LibGit2Sharp/Core/Handles/ReferenceSafeHandle.cs deleted file mode 100644 index 9ac7d1f7e..000000000 --- a/LibGit2Sharp/Core/Handles/ReferenceSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class ReferenceSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandleImpl() - { - Proxy.git_reference_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/ReflogEntrySafeHandle.cs b/LibGit2Sharp/Core/Handles/ReflogEntrySafeHandle.cs deleted file mode 100644 index 1739ccac3..000000000 --- a/LibGit2Sharp/Core/Handles/ReflogEntrySafeHandle.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class ReflogEntrySafeHandle : NotOwnedSafeHandleBase - { - } -} diff --git a/LibGit2Sharp/Core/Handles/ReflogSafeHandle.cs b/LibGit2Sharp/Core/Handles/ReflogSafeHandle.cs deleted file mode 100644 index a75deabea..000000000 --- a/LibGit2Sharp/Core/Handles/ReflogSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class ReflogSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandleImpl() - { - Proxy.git_reflog_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/RemoteSafeHandle.cs b/LibGit2Sharp/Core/Handles/RemoteSafeHandle.cs deleted file mode 100644 index d032e34f5..000000000 --- a/LibGit2Sharp/Core/Handles/RemoteSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class RemoteSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandleImpl() - { - Proxy.git_remote_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/RepositorySafeHandle.cs b/LibGit2Sharp/Core/Handles/RepositorySafeHandle.cs deleted file mode 100644 index 889a3022c..000000000 --- a/LibGit2Sharp/Core/Handles/RepositorySafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class RepositorySafeHandle : SafeHandleBase - { - protected override bool ReleaseHandleImpl() - { - Proxy.git_repository_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/RevWalkerSafeHandle.cs b/LibGit2Sharp/Core/Handles/RevWalkerSafeHandle.cs deleted file mode 100644 index 457bd027c..000000000 --- a/LibGit2Sharp/Core/Handles/RevWalkerSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class RevWalkerSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandleImpl() - { - Proxy.git_revwalk_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/SafeHandleBase.cs b/LibGit2Sharp/Core/Handles/SafeHandleBase.cs deleted file mode 100644 index b4ad98047..000000000 --- a/LibGit2Sharp/Core/Handles/SafeHandleBase.cs +++ /dev/null @@ -1,199 +0,0 @@ - -// This activates a lightweight mode which will help put under the light -// incorrectly released handles by outputing a warning message in the console. -// -// This should be activated when tests are being run of the CI server. -// -// Uncomment the line below or add a conditional symbol to activate this mode - -//#define LEAKS_IDENTIFYING - -// This activates a more throrough 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 System.Collections.Generic; -using System.Diagnostics; -using System.Globalization; -using System.Linq; -using System.Runtime.ConstrainedExecution; -using System.Runtime.InteropServices; -using System.Threading; - -#if LEAKS_IDENTIFYING -namespace LibGit2Sharp.Core -{ - /// - /// 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 { return _typeNames.ToArray(); } - } - } -} -#endif - -namespace LibGit2Sharp.Core.Handles -{ - internal abstract class SafeHandleBase : SafeHandle - { - -#if LEAKS_TRACKING - private readonly string trace; - private readonly Guid id; -#endif - - /// - /// This is set to non-zero when has - /// been called for this handle, but - /// has not yet been called. - /// - private int registered; - - protected SafeHandleBase() - : base(IntPtr.Zero, true) - { - NativeMethods.AddHandle(); - registered = 1; - -#if LEAKS_TRACKING - id = Guid.NewGuid(); - Trace.WriteLine(string.Format(CultureInfo.InvariantCulture, "Allocating {0} handle ({1})", GetType().Name, id)); - trace = new StackTrace(2, true).ToString(); -#endif - } - - protected override void Dispose(bool disposing) - { - bool leaked = !disposing && !IsInvalid; - -#if LEAKS_IDENTIFYING - 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 - } - - // Prevent the debugger from evaluating this property because it has side effects - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - public override sealed bool IsInvalid - { - get - { - bool invalid = IsInvalidImpl(); - if (invalid && Interlocked.CompareExchange(ref registered, 0, 1) == 1) - { - /* Unregister the handle because we know ReleaseHandle won't be called - * to do it for us. - */ - NativeMethods.RemoveHandle(); - } - - return invalid; - } - } - - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] - protected virtual bool IsInvalidImpl() - { - return handle == IntPtr.Zero; - } - - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] - protected abstract bool ReleaseHandleImpl(); - - protected override sealed bool ReleaseHandle() - { - bool result; - - try - { - result = ReleaseHandleImpl(); - } - finally - { - if (Interlocked.CompareExchange(ref registered, 0, 1) == 1) - { - // if the handle is still registered at this point, we definitely - // want to unregister it - NativeMethods.RemoveHandle(); - } - else - { - /* For this to be called, the following sequence of events must occur: - * - * 1. The handle is created - * 2. The IsInvalid property is evaluated, and the result is false - * 3. The IsInvalid property is evaluated by the runtime to determine if - * finalization is necessary, and the result is now true - * - * This can only happen if the value of `handle` is manipulated in an unexpected - * way (through the Reflection API or by a specially-crafted derived type that - * does not currently exist). The only safe course of action at this point in - * the shutdown process is returning false, which will trigger the - * releaseHandleFailed MDA but have no other impact on the CLR state. - * http://msdn.microsoft.com/en-us/library/85eak4a0.aspx - */ - result = false; - } - } - - return result; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/SignatureSafeHandle.cs b/LibGit2Sharp/Core/Handles/SignatureSafeHandle.cs deleted file mode 100644 index 51f745ae9..000000000 --- a/LibGit2Sharp/Core/Handles/SignatureSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class SignatureSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandleImpl() - { - Proxy.git_signature_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/StatusEntrySafeHandle.cs b/LibGit2Sharp/Core/Handles/StatusEntrySafeHandle.cs deleted file mode 100644 index 45b6eacd4..000000000 --- a/LibGit2Sharp/Core/Handles/StatusEntrySafeHandle.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; - -namespace LibGit2Sharp.Core.Handles -{ - internal class StatusEntrySafeHandle : NotOwnedSafeHandleBase - { - public StatusEntrySafeHandle() - : base() - { - } - - public StatusEntrySafeHandle(IntPtr handle) - : base() - { - this.SetHandle(handle); - } - - public GitStatusEntry MarshalAsGitStatusEntry() - { - return handle.MarshalAs(); - } - } -} diff --git a/LibGit2Sharp/Core/Handles/StatusListSafeHandle.cs b/LibGit2Sharp/Core/Handles/StatusListSafeHandle.cs deleted file mode 100644 index 3a51cd7b5..000000000 --- a/LibGit2Sharp/Core/Handles/StatusListSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class StatusListSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandleImpl() - { - Proxy.git_status_list_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/SubmoduleSafeHandle.cs b/LibGit2Sharp/Core/Handles/SubmoduleSafeHandle.cs deleted file mode 100644 index 3e84b29b1..000000000 --- a/LibGit2Sharp/Core/Handles/SubmoduleSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class SubmoduleSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandleImpl() - { - Proxy.git_submodule_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/TreeBuilderSafeHandle.cs b/LibGit2Sharp/Core/Handles/TreeBuilderSafeHandle.cs deleted file mode 100644 index 1fd17a292..000000000 --- a/LibGit2Sharp/Core/Handles/TreeBuilderSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class TreeBuilderSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandleImpl() - { - Proxy.git_treebuilder_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/TreeEntrySafeHandle.cs b/LibGit2Sharp/Core/Handles/TreeEntrySafeHandle.cs deleted file mode 100644 index f621cb2ff..000000000 --- a/LibGit2Sharp/Core/Handles/TreeEntrySafeHandle.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class TreeEntrySafeHandle : NotOwnedSafeHandleBase - { - } -} diff --git a/LibGit2Sharp/Core/Handles/TreeEntrySafeHandle_Owned.cs b/LibGit2Sharp/Core/Handles/TreeEntrySafeHandle_Owned.cs deleted file mode 100644 index f2d63e13f..000000000 --- a/LibGit2Sharp/Core/Handles/TreeEntrySafeHandle_Owned.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class TreeEntrySafeHandle_Owned : SafeHandleBase - { - protected override bool ReleaseHandleImpl() - { - Proxy.git_tree_entry_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/HistoryRewriter.cs b/LibGit2Sharp/Core/HistoryRewriter.cs index d520b3c23..c4cc2be8b 100644 --- a/LibGit2Sharp/Core/HistoryRewriter.cs +++ b/LibGit2Sharp/Core/HistoryRewriter.cs @@ -45,14 +45,14 @@ public void Execute() var filter = new CommitFilter { - Since = refsToRewrite, + IncludeReachableFrom = refsToRewrite, SortBy = CommitSortStrategies.Reverse | CommitSortStrategies.Topological }; var commits = repo.Commits.QueryBy(filter); foreach (var commit in commits) { - RewriteCommit(commit); + RewriteCommit(commit, options); } // Ordering matters. In the case of `A -> B -> commit`, we need to make sure B is rewritten @@ -110,24 +110,33 @@ private Reference RewriteReference(Reference reference) var sref = reference as SymbolicReference; if (sref != null) { - return RewriteReference( - sref, old => old.Target, RewriteReference, - (refs, old, target, logMessage) => refs.UpdateTarget(old, target, logMessage)); + return RewriteReference(sref, + old => old.Target, + RewriteReference, + (refs, old, target, logMessage) => refs.UpdateTarget(old, + target, + logMessage)); } var dref = reference as DirectReference; if (dref != null) { - return RewriteReference( - dref, old => old.Target, RewriteTarget, - (refs, old, target, logMessage) => refs.UpdateTarget(old, target.Id, logMessage)); + return RewriteReference(dref, + old => old.Target, + RewriteTarget, + (refs, old, target, logMessage) => refs.UpdateTarget(old, + target.Id, + logMessage)); } return reference; } private delegate Reference ReferenceUpdater( - ReferenceCollection refs, TRef origRef, TTarget origTarget, string logMessage) + ReferenceCollection refs, + TRef origRef, + TTarget origTarget, + string logMessage) where TRef : Reference where TTarget : class; @@ -141,11 +150,12 @@ private Reference RewriteReference( var oldRefTarget = selectTarget(oldRef); string newRefName = oldRef.CanonicalName; - if (oldRef.IsTag() && options.TagNameRewriter != null) + if (oldRef.IsTag && options.TagNameRewriter != null) { newRefName = Reference.TagPrefix + options.TagNameRewriter(oldRef.CanonicalName.Substring(Reference.TagPrefix.Length), - false, oldRef.TargetIdentifier); + false, + oldRef.TargetIdentifier); } var newTarget = rewriteTarget(oldRefTarget); @@ -160,10 +170,10 @@ private Reference RewriteReference( if (repo.Refs.Resolve(backupName) != null) { - throw new InvalidOperationException( - String.Format( - CultureInfo.InvariantCulture, "Can't back up reference '{0}' - '{1}' already exists", - oldRef.CanonicalName, backupName)); + throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, + "Can't back up reference '{0}' - '{1}' already exists", + oldRef.CanonicalName, + backupName)); } repo.Refs.Add(backupName, oldRef.TargetIdentifier, "filter-branch: backup"); @@ -189,7 +199,7 @@ private Reference RewriteReference( return refMap[oldRef] = movedRef; } - private void RewriteCommit(Commit commit) + private void RewriteCommit(Commit commit, RewriteHistoryOptions options) { var newHeader = CommitRewriteInfo.From(commit); var newTree = commit.Tree; @@ -221,8 +231,7 @@ private void RewriteCommit(Commit commit) // Create the new commit var mappedNewParents = newParents - .Select(oldParent => - objectMap.ContainsKey(oldParent) + .Select(oldParent => objectMap.ContainsKey(oldParent) ? objectMap[oldParent] as Commit : oldParent) .Where(newParent => newParent != null) @@ -239,7 +248,7 @@ private void RewriteCommit(Commit commit) newHeader.Message, newTree, mappedNewParents, - true); + options.PrettifyMessages); // Record the rewrite objectMap[commit] = newCommit; @@ -293,8 +302,10 @@ private GitObject RewriteTarget(GitObject oldTarget) newName = options.TagNameRewriter(annotation.Name, true, annotation.Target.Sha); } - var newAnnotation = repo.ObjectDatabase.CreateTagAnnotation(newName, newTarget, annotation.Tagger, - annotation.Message); + var newAnnotation = repo.ObjectDatabase.CreateTagAnnotation(newName, + newTarget, + annotation.Tagger, + annotation.Message); objectMap[annotation] = newAnnotation; return newAnnotation; } @@ -303,8 +314,8 @@ private int ReferenceDepth(Reference reference) { var dref = reference as DirectReference; return dref == null - ? 1 + ReferenceDepth(((SymbolicReference)reference).Target) - : 1; + ? 1 + ReferenceDepth(((SymbolicReference)reference).Target) + : 1; } } } diff --git a/LibGit2Sharp/Core/IntPtrExtensions.cs b/LibGit2Sharp/Core/IntPtrExtensions.cs deleted file mode 100644 index 314a16834..000000000 --- a/LibGit2Sharp/Core/IntPtrExtensions.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace LibGit2Sharp.Core -{ - internal static class IntPtrExtensions - { - public static T MarshalAs(this IntPtr ptr, bool throwWhenNull = true) - { - if (!throwWhenNull && ptr == IntPtr.Zero) - { - return default(T); - } - return (T)Marshal.PtrToStructure(ptr, typeof(T)); - } - } -} diff --git a/LibGit2Sharp/Core/LambdaEqualityHelper.cs b/LibGit2Sharp/Core/LambdaEqualityHelper.cs index 120e705de..80e826c02 100644 --- a/LibGit2Sharp/Core/LambdaEqualityHelper.cs +++ b/LibGit2Sharp/Core/LambdaEqualityHelper.cs @@ -48,7 +48,7 @@ public int GetHashCode(T instance) foreach (Func accessor in equalityContributorAccessors) { object item = accessor(instance); - hashCode = (hashCode*397) ^ (item != null ? item.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (item != null ? item.GetHashCode() : 0); } } diff --git a/LibGit2Sharp/Core/LazyGroup.cs b/LibGit2Sharp/Core/LazyGroup.cs index 3c82fa3ad..d8b13fa42 100644 --- a/LibGit2Sharp/Core/LazyGroup.cs +++ b/LibGit2Sharp/Core/LazyGroup.cs @@ -24,18 +24,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; } @@ -93,8 +94,7 @@ protected class LazyWrapper : Lazy, ILazy { public LazyWrapper(Func evaluator) : base(evaluator) - { - } + { } } } } diff --git a/LibGit2Sharp/Core/ManagedHttpSmartSubtransport.cs b/LibGit2Sharp/Core/ManagedHttpSmartSubtransport.cs new file mode 100644 index 000000000..88eced880 --- /dev/null +++ b/LibGit2Sharp/Core/ManagedHttpSmartSubtransport.cs @@ -0,0 +1,270 @@ +using System; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Security.Authentication; + +namespace LibGit2Sharp.Core +{ + internal class ManagedHttpSmartSubtransport : 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 ManagedHttpSmartSubtransportStream(this, endpointUrl, isPost, contentType); + } + + private class ManagedHttpSmartSubtransportStream : SmartSubtransportStream + { + private static int MAX_REDIRECTS = 7; + +#if NETCOREAPP + private static readonly SocketsHttpHandler httpHandler; +#else + private static readonly HttpClientHandler httpHandler; +#endif + + private static readonly CredentialCache credentialCache; + + private MemoryStream postBuffer = new MemoryStream(); + private HttpResponseMessage response; + private Stream responseStream; + + static ManagedHttpSmartSubtransportStream() + { +#if NETCOREAPP + httpHandler = new SocketsHttpHandler(); + httpHandler.PooledConnectionLifetime = TimeSpan.FromMinutes(5); +#else + httpHandler = new HttpClientHandler(); + httpHandler.SslProtocols |= SslProtocols.Tls12; +#endif + + httpHandler.AllowAutoRedirect = false; + + credentialCache = new CredentialCache(); + httpHandler.Credentials = credentialCache; + } + + public ManagedHttpSmartSubtransportStream(ManagedHttpSmartSubtransport parent, string endpointUrl, bool isPost, string contentType) + : base(parent) + { + EndpointUrl = new Uri(endpointUrl); + IsPost = isPost; + ContentType = contentType; + } + + private HttpClient CreateHttpClient(HttpMessageHandler handler) + { + return new HttpClient(handler, false) + { + DefaultRequestHeaders = + { + // This worked fine when it was on, but git.exe doesn't specify this header, so we don't either. + ExpectContinue = false, + }, + }; + } + + private Uri 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 string GetUserAgent() + { + string userAgent = GlobalSettings.GetUserAgent(); + + if (string.IsNullOrEmpty(userAgent)) + { + userAgent = "LibGit2Sharp " + GlobalSettings.Version.InformationalVersion; + } + + return userAgent; + } + + private HttpRequestMessage CreateRequest(Uri endpointUrl, bool isPost) + { + var verb = isPost ? new HttpMethod("POST") : new HttpMethod("GET"); + var request = new HttpRequestMessage(verb, endpointUrl); + request.Headers.Add("User-Agent", $"git/2.0 ({GetUserAgent()})"); + request.Headers.Remove("Expect"); + + return request; + } + + private HttpResponseMessage GetResponseWithRedirects() + { + var url = EndpointUrl; + int retries; + + for (retries = 0; ; retries++) + { + using (var httpClient = CreateHttpClient(httpHandler)) + { + var request = CreateRequest(url, IsPost); + + if (retries > MAX_REDIRECTS) + { + throw new Exception("too many redirects or authentication replays"); + } + + if (IsPost && postBuffer.Length > 0) + { + var bufferDup = new MemoryStream(postBuffer.GetBuffer(), 0, (int)postBuffer.Length); + + request.Content = new StreamContent(bufferDup); + request.Content.Headers.Add("Content-Type", ContentType); + } + + var response = httpClient.SendAsync(request, HttpCompletionOption.ResponseContentRead).GetAwaiter().GetResult(); + + if (response.StatusCode == HttpStatusCode.OK) + { + return response; + } + else if (response.StatusCode == HttpStatusCode.Unauthorized) + { + int ret = SmartTransport.AcquireCredentials(out Credentials cred, null, typeof(UsernamePasswordCredentials)); + + if (ret != 0) + { + throw new InvalidOperationException("authentication cancelled"); + } + + var scheme = response.Headers.WwwAuthenticate.First().Scheme; + + if (cred is DefaultCredentials) + { + lock (credentialCache) + { + credentialCache.Add(url, scheme, CredentialCache.DefaultNetworkCredentials); + } + } + else if (cred is UsernamePasswordCredentials userpass) + { + lock (credentialCache) + { + credentialCache.Add(url, scheme, new NetworkCredential(userpass.Username, userpass.Password)); + } + } + + continue; + } + else if (response.StatusCode == HttpStatusCode.Moved || response.StatusCode == HttpStatusCode.Redirect) + { + url = new Uri(response.Headers.GetValues("Location").First()); + continue; + } + + throw new Exception(string.Format("unexpected HTTP response: {0}", response.StatusCode)); + } + } + + throw new Exception("too many redirects or authentication replays"); + } + + public override int Read(Stream dataStream, long length, out long readTotal) + { + byte[] buffer = new byte[4096]; + readTotal = 0; + + if (responseStream == null) + { + response = GetResponseWithRedirects(); + responseStream = response.Content.ReadAsStreamAsync().GetAwaiter().GetResult(); + } + + 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; + } + + if (response != null) + { + response.Dispose(); + response = null; + } + + base.Free(); + } + } + } +} diff --git a/LibGit2Sharp/Core/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs index 9b4e818f3..7be9a1734 100644 --- a/LibGit2Sharp/Core/NativeMethods.cs +++ b/LibGit2Sharp/Core/NativeMethods.cs @@ -1,13 +1,16 @@ using System; -using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.ConstrainedExecution; using System.Runtime.InteropServices; -using System.Threading; using LibGit2Sharp.Core.Handles; +// Restrict the set of directories where the native library is loaded from to safe directories. +[assembly: DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory | DllImportSearchPath.ApplicationDirectory | DllImportSearchPath.SafeDirectories)] + +#pragma warning disable IDE1006 // Naming Styles + // ReSharper disable InconsistentNaming namespace LibGit2Sharp.Core { @@ -15,381 +18,626 @@ internal static class NativeMethods { public const uint GIT_PATH_MAX = 4096; private const string libgit2 = NativeDllName.Name; - private static readonly LibraryLifetimeObject lifetimeObject; - private static int handlesCount; - - /// - /// Internal hack to ensure that the call to git_threads_shutdown is called after all handle finalizers - /// have run to completion ensuring that no dangling git-related finalizer runs after git_threads_shutdown. - /// There should never be more than one instance of this object per AppDomain. - /// - private sealed class LibraryLifetimeObject : CriticalFinalizerObject + + // An object tied to the lifecycle of the NativeMethods static class. + // This will handle initialization and shutdown of the underlying + // native library. +#pragma warning disable 0414 + private static NativeShutdownObject shutdownObject; +#pragma warning restore 0414 + + private static SmartSubtransportRegistration httpSubtransportRegistration; + private static SmartSubtransportRegistration httpsSubtransportRegistration; + + static NativeMethods() { - // Ensure mono can JIT the .cctor and adjust the PATH before trying to load the native library. - // See https://github.com/libgit2/libgit2sharp/pull/190 - [MethodImpl(MethodImplOptions.NoInlining)] - public LibraryLifetimeObject() + if (Platform.IsRunningOnNetFramework() || Platform.IsRunningOnNetCore()) { - int res = git_libgit2_init(); - Ensure.Int32Result(res); - if (res == 1) + // Use .NET Core 3.0+ NativeLibrary when available. + if (!TryUseNativeLibrary()) { - // Ignore the error that this propagates. Call it in case openssl is being used. - git_openssl_set_locking(); + // 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); + } + } } - AddHandle(); } - ~LibraryLifetimeObject() + InitializeNativeLibrary(); + } + + private static string GetGlobalSettingsNativeLibraryPath() + { + string nativeLibraryDir = GlobalSettings.GetAndLockNativeLibraryPath(); + if (nativeLibraryDir == null) { - RemoveHandle(); + return null; } + return Path.Combine(nativeLibraryDir, libgit2 + Platform.GetNativeLibraryExtension()); } - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] - internal static void AddHandle() + private delegate bool TryLoadLibraryByNameDelegate(string libraryName, Assembly assembly, DllImportSearchPath? searchPath, out IntPtr handle); + private delegate bool TryLoadLibraryByPathDelegate(string libraryPath, out IntPtr handle); + + static TryLoadLibraryByNameDelegate _tryLoadLibraryByName; + static TryLoadLibraryByPathDelegate _tryLoadLibraryByPath; + + static bool TryLoadLibrary(string libraryName, Assembly assembly, DllImportSearchPath? searchPath, out IntPtr handle) { - Interlocked.Increment(ref handlesCount); + if (_tryLoadLibraryByName == null) + { + throw new NotSupportedException(); + } + return _tryLoadLibraryByName(libraryName, assembly, searchPath, out handle); } - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] - internal static void RemoveHandle() + static bool TryLoadLibrary(string libraryPath, out IntPtr handle) { - int count = Interlocked.Decrement(ref handlesCount); - if (count == 0) + if (_tryLoadLibraryByPath == null) { - git_libgit2_shutdown(); + throw new NotSupportedException(); } + return _tryLoadLibraryByPath(libraryPath, out handle); } - static NativeMethods() + private static bool TryUseNativeLibrary() { - if (Platform.OperatingSystem == OperatingSystemType.Windows) + // NativeLibrary is available in .NET Core 3.0+. + // We use reflection to use NativeLibrary so this library can target 'netstandard2.0'. + + Type dllImportResolverType = Type.GetType("System.Runtime.InteropServices.DllImportResolver, System.Runtime.InteropServices", throwOnError: false); + Type nativeLibraryType = Type.GetType("System.Runtime.InteropServices.NativeLibrary, System.Runtime.InteropServices", throwOnError: false); + var tryLoadLibraryByName = (TryLoadLibraryByNameDelegate)nativeLibraryType?.GetMethod("TryLoad", + new Type[] { typeof(string), typeof(Assembly), typeof(DllImportSearchPath?), typeof(IntPtr).MakeByRefType() })?.CreateDelegate(typeof(TryLoadLibraryByNameDelegate)); + var tryLoadLibraryByPath = (TryLoadLibraryByPathDelegate)nativeLibraryType?.GetMethod("TryLoad", + new Type[] { typeof(string), typeof(IntPtr).MakeByRefType() })?.CreateDelegate(typeof(TryLoadLibraryByPathDelegate)); + MethodInfo setDllImportResolver = nativeLibraryType?.GetMethod("SetDllImportResolver", new Type[] { typeof(Assembly), dllImportResolverType}); + + if (dllImportResolverType == null || + nativeLibraryType == null || + tryLoadLibraryByName == null || + tryLoadLibraryByPath == null || + setDllImportResolver == null) { - string nativeLibraryPath = GlobalSettings.GetAndLockNativeLibraryPath(); + return false; + } + + _tryLoadLibraryByPath = tryLoadLibraryByPath; + _tryLoadLibraryByName = tryLoadLibraryByName; + + // NativeMethods.SetDllImportResolver(typeof(NativeMethods).Assembly, ResolveDll); + object resolveDelegate = typeof(NativeMethods).GetMethod(nameof(ResolveDll), BindingFlags.NonPublic | BindingFlags.Static).CreateDelegate(dllImportResolverType); + setDllImportResolver.Invoke(null, new object[] { typeof(NativeMethods).Assembly, resolveDelegate }); + + return true; + } + + private static IntPtr ResolveDll(string libraryName, Assembly assembly, DllImportSearchPath? searchPath) + { + IntPtr handle = IntPtr.Zero; + if (libraryName == libgit2) + { + // Use GlobalSettings.NativeLibraryPath when set. + string nativeLibraryPath = GetGlobalSettingsNativeLibraryPath(); + if (nativeLibraryPath != null && + TryLoadLibrary(nativeLibraryPath, out handle)) + { + return handle; + } + + // Use Default DllImport resolution. + if (TryLoadLibrary(libraryName, assembly, searchPath, out handle)) + { + return handle; + } + + // We cary 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(typeof(NativeMethods).Assembly.Location); + 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 (TryLoadLibrary(libPath, out handle)) + { + return handle; + } + } + } + } + } + return handle; + } - string path = Path.Combine(nativeLibraryPath, Platform.ProcessorArchitecture); + public const int RTLD_NOW = 0x002; - const string pathEnvVariable = "PATH"; - Environment.SetEnvironmentVariable(pathEnvVariable, - String.Format(CultureInfo.InvariantCulture, "{0}{1}{2}", path, Path.PathSeparator, Environment.GetEnvironmentVariable(pathEnvVariable))); + [DllImport("libdl", EntryPoint = "dlopen")] + private static extern IntPtr LoadUnixLibrary(string path, int flags); + + [DllImport("kernel32", EntryPoint = "LoadLibrary")] + private static extern IntPtr LoadWindowsLibrary(string path); + + // Avoid inlining this method because otherwise mono's JITter may try + // to load the library _before_ we've configured the path. + [MethodImpl(MethodImplOptions.NoInlining)] + private static void InitializeNativeLibrary() + { + 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(); } - // See LibraryLifetimeObject description. - lifetimeObject = new LibraryLifetimeObject(); + // Configure the .NET HTTP(S) mechanism on the first initialization of the library in the current process. + if (initCounter == 1) + { + httpSubtransportRegistration = GlobalSettings.RegisterDefaultSmartSubtransport("http"); + httpsSubtransportRegistration = GlobalSettings.RegisterDefaultSmartSubtransport("https"); + } } - [DllImport(libgit2)] - internal static extern GitErrorSafeHandle giterr_last(); + // Shutdown the native library in a finalizer. + private sealed class NativeShutdownObject : CriticalFinalizerObject + { + ~NativeShutdownObject() + { + if (httpSubtransportRegistration != null) + { + GlobalSettings.UnregisterDefaultSmartSubtransport(httpSubtransportRegistration); + } - [DllImport(libgit2)] + if (httpsSubtransportRegistration != null) + { + GlobalSettings.UnregisterDefaultSmartSubtransport(httpsSubtransportRegistration); + } + + git_libgit2_shutdown(); + } + } + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe GitError* giterr_last(); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern void giterr_set_str( GitErrorCategory error_class, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string errorString); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern void giterr_set_oom(); - [DllImport(libgit2)] - internal static extern UInt32 git_blame_get_hunk_count(BlameSafeHandle blame); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe UInt32 git_blame_get_hunk_count(git_blame* blame); - [DllImport(libgit2)] - internal static extern IntPtr git_blame_get_hunk_byindex( - BlameSafeHandle blame, UInt32 index); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_blame_hunk* git_blame_get_hunk_byindex( + git_blame* blame, UInt32 index); - [DllImport(libgit2)] - internal static extern int git_blame_file( - out BlameSafeHandle blame, - RepositorySafeHandle repo, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath path, - GitBlameOptions options); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_blame_file( + out git_blame* blame, + git_repository* repo, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string path, + git_blame_options options); - [DllImport(libgit2)] - internal static extern void git_blame_free(IntPtr blame); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_blame_free(git_blame* blame); - [DllImport(libgit2)] - internal static extern int git_blob_create_fromdisk( + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_blob_create_fromdisk( ref GitOid id, - RepositorySafeHandle repo, + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath path); - [DllImport(libgit2)] - internal static extern int git_blob_create_fromworkdir( + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_blob_create_fromworkdir( ref GitOid id, - RepositorySafeHandle repo, + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath relative_path); - internal delegate int source_callback( - IntPtr content, - int max_length, - IntPtr data); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_blob_create_fromstream( + out IntPtr stream, + git_repository* repositoryPtr, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string hintpath); - [DllImport(libgit2)] - internal static extern int git_blob_create_fromchunks( + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_blob_create_fromstream_commit( ref GitOid oid, - RepositorySafeHandle repositoryPtr, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath hintpath, - source_callback fileCallback, - IntPtr data); + IntPtr stream); - [DllImport(libgit2)] - internal static extern int git_blob_filtered_content( + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_blob_filtered_content( GitBuf buf, - GitObjectSafeHandle blob, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath as_path, + git_object* blob, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string as_path, [MarshalAs(UnmanagedType.Bool)] bool check_for_binary_data); - [DllImport(libgit2)] - internal static extern IntPtr git_blob_rawcontent(GitObjectSafeHandle blob); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe IntPtr git_blob_rawcontent(git_object* blob); - [DllImport(libgit2)] - internal static extern Int64 git_blob_rawsize(GitObjectSafeHandle blob); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe Int64 git_blob_rawsize(git_object* blob); - [DllImport(libgit2)] - internal static extern int git_branch_create_from_annotated( - out ReferenceSafeHandle ref_out, - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_branch_create_from_annotated( + out git_reference* ref_out, + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string branch_name, - GitAnnotatedCommitHandle target, + git_annotated_commit* target, [MarshalAs(UnmanagedType.Bool)] bool force); - [DllImport(libgit2)] - internal static extern int git_branch_delete( - ReferenceSafeHandle reference); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_branch_delete( + git_reference* reference); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int branch_foreach_callback( IntPtr branch_name, GitBranchType branch_type, IntPtr payload); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern void git_branch_iterator_free( IntPtr iterator); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern int git_branch_iterator_new( - out BranchIteratorSafeHandle iter_out, - RepositorySafeHandle repo, + out IntPtr iter_out, + IntPtr repo, GitBranchType branch_type); - [DllImport(libgit2)] - internal static extern int git_branch_move( - out ReferenceSafeHandle ref_out, - ReferenceSafeHandle reference, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_branch_move( + out git_reference* ref_out, + git_reference* reference, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string new_branch_name, [MarshalAs(UnmanagedType.Bool)] bool force); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern int git_branch_next( - out ReferenceSafeHandle ref_out, + out IntPtr ref_out, out GitBranchType type_out, - BranchIteratorSafeHandle iter); + IntPtr iter); - [DllImport(libgit2)] - internal static extern int git_branch_remote_name( + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_branch_remote_name( GitBuf buf, - RepositorySafeHandle repo, + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string canonical_branch_name); - [DllImport(libgit2)] - internal static extern int git_remote_rename( + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate int commit_signing_callback( + IntPtr signature, + IntPtr signature_field, + IntPtr commit_content, + IntPtr payload); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_rebase_init( + out git_rebase* rebase, + git_repository* repo, + git_annotated_commit* branch, + git_annotated_commit* upstream, + git_annotated_commit* onto, + GitRebaseOptions options); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_rebase_open( + out git_rebase* rebase, + git_repository* repo, + GitRebaseOptions options); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe UIntPtr git_rebase_operation_entrycount( + git_rebase* rebase); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe UIntPtr git_rebase_operation_current( + git_rebase* rebase); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_rebase_operation* git_rebase_operation_byindex( + git_rebase* rebase, + UIntPtr index); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_rebase_next( + out git_rebase_operation* operation, + git_rebase* rebase); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_rebase_commit( + ref GitOid id, + git_rebase* rebase, + git_signature* author, + git_signature* committer, + IntPtr message_encoding, + IntPtr message); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_rebase_abort( + git_rebase* rebase); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_rebase_finish( + git_rebase* repo, + git_signature* signature); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_rebase_free(git_rebase* rebase); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_remote_rename( ref GitStrArray problems, - RepositorySafeHandle repo, + 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_upstream_name( + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_branch_upstream_name( GitBuf buf, - RepositorySafeHandle repo, + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string referenceName); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern void git_buf_free(GitBuf buf); - [DllImport(libgit2)] - internal static extern int git_checkout_tree( - RepositorySafeHandle repo, - GitObjectSafeHandle treeish, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_checkout_tree( + git_repository* repo, + git_object* treeish, ref GitCheckoutOpts opts); - [DllImport(libgit2)] - internal static extern int git_checkout_index( - RepositorySafeHandle repo, - GitObjectSafeHandle treeish, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_checkout_index( + git_repository* repo, + git_object* treeish, ref GitCheckoutOpts opts); - [DllImport(libgit2)] - internal static extern int git_clone( - out RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_clone( + out git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string origin_url, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath workdir_path, ref GitCloneOptions opts); - [DllImport(libgit2)] - internal static extern IntPtr git_commit_author(GitObjectSafeHandle commit); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_signature* git_commit_author(git_object* commit); - [DllImport(libgit2)] - internal static extern IntPtr git_commit_committer(GitObjectSafeHandle commit); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_signature* git_commit_committer(git_object* commit); - [DllImport(libgit2)] - internal static extern int git_commit_create_from_ids( + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_commit_create_from_ids( out GitOid id, - RepositorySafeHandle repo, + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string updateRef, - SignatureSafeHandle author, - SignatureSafeHandle committer, + git_signature* author, + git_signature* committer, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string encoding, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string message, ref GitOid tree, UIntPtr parentCount, [MarshalAs(UnmanagedType.LPArray)] [In] IntPtr[] parents); - [DllImport(libgit2)] - [return : MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] - internal static extern string git_commit_message(GitObjectSafeHandle commit); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_commit_create_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)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_commit_create_with_signature( + out GitOid id, + git_repository* repo, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string commit_content, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string signature, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string signature_field); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] - internal static extern string git_commit_summary(GitObjectSafeHandle commit); + internal static extern unsafe string git_commit_message(git_object* commit); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] - internal static extern string git_commit_message_encoding(GitObjectSafeHandle commit); + internal static extern unsafe string git_commit_summary(git_object* commit); - [DllImport(libgit2)] - internal static extern OidSafeHandle git_commit_parent_id(GitObjectSafeHandle commit, uint n); + [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)] - internal static extern uint git_commit_parentcount(GitObjectSafeHandle commit); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_oid* git_commit_parent_id(git_object* commit, uint n); - [DllImport(libgit2)] - internal static extern OidSafeHandle git_commit_tree_id(GitObjectSafeHandle commit); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe uint git_commit_parentcount(git_object* commit); - [DllImport(libgit2)] - internal static extern int git_config_delete_entry( - ConfigurationSafeHandle cfg, + [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)] - internal static extern int git_config_delete_multivar( - ConfigurationSafeHandle cfg, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof (StrictUtf8Marshaler))] string name, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_config_lock(out IntPtr txn, git_config* config); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_config_delete_multivar( + git_config* cfg, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string regexp); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_config_set_multivar( + git_config* cfg, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string regexp, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string value); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern int git_config_find_global(GitBuf global_config_path); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern int git_config_find_system(GitBuf system_config_path); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern int git_config_find_xdg(GitBuf xdg_config_path); - [DllImport(libgit2)] - internal static extern void git_config_free(IntPtr cfg); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_config_find_programdata(GitBuf programdata_config_path); - [DllImport(libgit2)] - internal static extern void git_config_entry_free(IntPtr entry); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_config_free(git_config* cfg); - [DllImport(libgit2)] - internal static extern int git_config_get_entry( - out GitConfigEntryHandle entry, - ConfigurationSafeHandle cfg, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_config_entry_free(GitConfigEntry* entry); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_config_get_entry( + out GitConfigEntry* entry, + git_config* cfg, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name); - [DllImport(libgit2)] - internal static extern int git_config_add_file_ondisk( - ConfigurationSafeHandle cfg, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_config_add_file_ondisk( + git_config* cfg, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath path, uint level, + git_repository* repo, [MarshalAs(UnmanagedType.Bool)] bool force); - [DllImport(libgit2)] - internal static extern int git_config_new(out ConfigurationSafeHandle cfg); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_config_new(out git_config* cfg); - [DllImport(libgit2)] - internal static extern int git_config_open_level( - out ConfigurationSafeHandle cfg, - ConfigurationSafeHandle parent, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_config_open_level( + out git_config* cfg, + git_config* parent, uint level); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern int git_config_parse_bool( [MarshalAs(UnmanagedType.Bool)] out bool value, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string valueToParse); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern int git_config_parse_int32( [MarshalAs(UnmanagedType.I4)] out int value, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string valueToParse); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern int git_config_parse_int64( [MarshalAs(UnmanagedType.I8)] out long value, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string valueToParse); - [DllImport(libgit2)] - internal static extern int git_config_set_bool( - ConfigurationSafeHandle cfg, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_config_set_bool( + git_config* cfg, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, [MarshalAs(UnmanagedType.Bool)] bool value); - [DllImport(libgit2)] - internal static extern int git_config_set_int32( - ConfigurationSafeHandle cfg, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_config_set_int32( + git_config* cfg, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, int value); - [DllImport(libgit2)] - internal static extern int git_config_set_int64( - ConfigurationSafeHandle cfg, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_config_set_int64( + git_config* cfg, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, long value); - [DllImport(libgit2)] - internal static extern int git_config_set_string( - ConfigurationSafeHandle cfg, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_config_set_string( + git_config* cfg, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string value); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int config_foreach_callback( IntPtr entry, IntPtr payload); - [DllImport(libgit2)] - internal static extern int git_config_foreach( - ConfigurationSafeHandle cfg, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_config_foreach( + git_config* cfg, config_foreach_callback callback, IntPtr payload); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern int git_config_iterator_glob_new( - out ConfigurationIteratorSafeHandle iter, - ConfigurationSafeHandle cfg, + out IntPtr iter, + IntPtr cfg, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string regexp); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern int git_config_next( out IntPtr entry, - ConfigurationIteratorSafeHandle iter); + IntPtr iter); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern void git_config_iterator_free(IntPtr iter); - [DllImport(libgit2)] - internal static extern int git_config_snapshot(out ConfigurationSafeHandle @out, ConfigurationSafeHandle config); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_config_snapshot(out git_config* @out, git_config* config); // Ordinarily we would decorate the `url` parameter with the StrictUtf8Marshaler like we do everywhere // else, but apparently doing a native->managed callback with the 64-bit version of CLR 2.0 can // sometimes vomit when using a custom IMarshaler. So yeah, don't do that. If you need the url, // call StrictUtf8Marshaler.FromNative manually. See the discussion here: // http://social.msdn.microsoft.com/Forums/en-US/netfx64bit/thread/1eb746c6-d695-4632-8a9e-16c4fa98d481 + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int git_cred_acquire_cb( out IntPtr cred, IntPtr url, @@ -397,768 +645,914 @@ internal delegate int git_cred_acquire_cb( GitCredentialType allowed_types, IntPtr payload); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern int git_cred_default_new(out IntPtr cred); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern int git_cred_userpass_plaintext_new( out IntPtr cred, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof (StrictUtf8Marshaler))] string username, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof (StrictUtf8Marshaler))] string password); + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string username, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string password); - [DllImport(libgit2)] - internal static extern int git_describe_commit( - out DescribeResultSafeHandle describe, - GitObjectSafeHandle committish, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_cred_ssh_key_new( + out IntPtr cred, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string username, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string publickey, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string privatekey, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string passphrase); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_cred_ssh_key_from_agent( + out IntPtr cred, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string username); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_cred_username_new( + out IntPtr cred, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string username); + + [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)] - internal static extern int git_describe_format( + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_describe_format( GitBuf buf, - DescribeResultSafeHandle describe, + git_describe_result* describe, ref GitDescribeFormatOptions options); - [DllImport(libgit2)] - internal static extern void git_describe_result_free(IntPtr describe); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_describe_result_free(git_describe_result* describe); - [DllImport(libgit2)] - internal static extern void git_diff_free(IntPtr diff); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_diff_free(git_diff* diff); - [DllImport(libgit2)] - internal static extern int git_diff_tree_to_tree( - out DiffSafeHandle diff, - RepositorySafeHandle repo, - GitObjectSafeHandle oldTree, - GitObjectSafeHandle newTree, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_diff_tree_to_tree( + out git_diff* diff, + git_repository* repo, + git_object* oldTree, + git_object* newTree, GitDiffOptions options); - [DllImport(libgit2)] - internal static extern int git_diff_tree_to_index( - out DiffSafeHandle diff, - RepositorySafeHandle repo, - GitObjectSafeHandle oldTree, - IndexSafeHandle index, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_diff_tree_to_index( + out git_diff* diff, + git_repository* repo, + git_object* oldTree, + git_index* index, GitDiffOptions options); - [DllImport(libgit2)] - internal static extern int git_diff_merge( - DiffSafeHandle onto, - DiffSafeHandle from); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_diff_merge( + git_diff* onto, + git_diff* from); - [DllImport(libgit2)] - internal static extern int git_diff_index_to_workdir( - out DiffSafeHandle diff, - RepositorySafeHandle repo, - IndexSafeHandle index, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_diff_index_to_workdir( + out git_diff* diff, + git_repository* repo, + git_index* index, GitDiffOptions options); - [DllImport(libgit2)] - internal static extern int git_diff_tree_to_workdir( - out DiffSafeHandle diff, - RepositorySafeHandle repo, - GitObjectSafeHandle oldTree, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_diff_tree_to_workdir( + out git_diff* diff, + git_repository* repo, + git_object* oldTree, GitDiffOptions options); - internal delegate int git_diff_file_cb( - [In] GitDiffDelta delta, + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal unsafe delegate int git_diff_file_cb( + [In] git_diff_delta* delta, float progress, IntPtr payload); - internal delegate int git_diff_hunk_cb( - [In] GitDiffDelta delta, + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal unsafe delegate int git_diff_hunk_cb( + [In] git_diff_delta* delta, [In] GitDiffHunk hunk, IntPtr payload); - internal delegate int git_diff_line_cb( - [In] GitDiffDelta delta, + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal unsafe delegate int git_diff_line_cb( + [In] git_diff_delta* delta, [In] GitDiffHunk hunk, [In] GitDiffLine line, IntPtr payload); - [DllImport(libgit2)] - internal static extern int git_diff_blobs( - GitObjectSafeHandle oldBlob, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath old_as_path, - GitObjectSafeHandle newBlob, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath new_as_path, + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal unsafe delegate int git_diff_binary_cb( + [In] git_diff_delta* delta, + [In] GitDiffBinary binary, + IntPtr payload); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_diff_blobs( + git_object* oldBlob, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string old_as_path, + git_object* newBlob, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string new_as_path, GitDiffOptions options, git_diff_file_cb fileCallback, + git_diff_binary_cb binaryCallback, git_diff_hunk_cb hunkCallback, git_diff_line_cb lineCallback, IntPtr payload); - [DllImport(libgit2)] - internal static extern int git_diff_foreach( - DiffSafeHandle diff, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_diff_foreach( + git_diff* diff, git_diff_file_cb fileCallback, + git_diff_binary_cb binaryCallback, git_diff_hunk_cb hunkCallback, git_diff_line_cb lineCallback, IntPtr payload); - [DllImport(libgit2)] - internal static extern int git_diff_find_similar( - DiffSafeHandle diff, - GitDiffFindOptions options); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_diff_find_similar( + git_diff* diff, + IntPtr options); [DllImport(libgit2)] - internal static extern UIntPtr git_diff_num_deltas(DiffSafeHandle diff); + internal static extern unsafe int git_diff_find_similar( + git_diff* diff, + ref GitDiffFindOptions options); - [DllImport(libgit2)] - internal static extern IntPtr git_diff_get_delta(DiffSafeHandle diff, UIntPtr idx); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe UIntPtr git_diff_num_deltas(git_diff* diff); - [DllImport(libgit2)] - internal static extern int git_libgit2_features(); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_diff_delta* git_diff_get_delta(git_diff* diff, UIntPtr idx); - [DllImport(libgit2)] - internal static extern int git_graph_ahead_behind(out UIntPtr ahead, out UIntPtr behind, RepositorySafeHandle repo, ref GitOid one, ref GitOid two); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_filter_register( + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, + IntPtr gitFilter, int priority); - [DllImport(libgit2)] - internal static extern int git_graph_descendant_of( - RepositorySafeHandle repo, - ref GitOid commit, - ref GitOid ancestor); + [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)] - internal static extern int git_ignore_add_rule( - RepositorySafeHandle repo, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof (StrictUtf8Marshaler))] string rules); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_filter_source_mode(git_filter_source* source); - [DllImport(libgit2)] - internal static extern int git_ignore_clear_internal_rules(RepositorySafeHandle repo); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_libgit2_features(); - [DllImport(libgit2)] - internal static extern int git_ignore_path_is_ignored( - out int ignored, - RepositorySafeHandle repo, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath path); + #region git_libgit2_opts - [DllImport(libgit2)] - internal static extern int git_index_add_bypath( - IndexSafeHandle index, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath path); + // 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_index_add( - IndexSafeHandle index, - GitIndexEntry entry); + // 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_index_conflict_get( - out IndexEntrySafeHandle ancestor, - out IndexEntrySafeHandle ours, - out IndexEntrySafeHandle theirs, - IndexSafeHandle index, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath path); + // 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_index_conflict_iterator_new( - out ConflictIteratorSafeHandle iterator, - IndexSafeHandle index); + // git_libgit2_opts(GIT_OPT_ENABLE_*, int enabled) + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_libgit2_opts(int option, int enabled); - [DllImport(libgit2)] - internal static extern int git_index_conflict_next( - out IndexEntrySafeHandle ancestor, - out IndexEntrySafeHandle ours, - out IndexEntrySafeHandle theirs, - ConflictIteratorSafeHandle iterator); + // 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); - [DllImport(libgit2)] - internal static extern void git_index_conflict_iterator_free( - IntPtr iterator); + // 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); + #endregion - [DllImport(libgit2)] - internal static extern UIntPtr git_index_entrycount(IndexSafeHandle index); + [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)] - internal static extern int git_index_entry_stage(IndexEntrySafeHandle indexentry); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_graph_descendant_of( + git_repository* repo, + ref GitOid commit, + ref GitOid ancestor); - [DllImport(libgit2)] - internal static extern void git_index_free(IntPtr index); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_ignore_add_rule( + git_repository* repo, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string rules); - [DllImport(libgit2)] - internal static extern IndexEntrySafeHandle git_index_get_byindex(IndexSafeHandle index, UIntPtr n); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_ignore_clear_internal_rules(git_repository* repo); - [DllImport(libgit2)] - internal static extern IndexEntrySafeHandle git_index_get_bypath( - IndexSafeHandle index, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath path, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_ignore_path_is_ignored( + out int ignored, + 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 UIntPtr git_index_name_entrycount(IndexSafeHandle handle); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe UIntPtr git_index_name_entrycount(git_index* handle); - [DllImport(libgit2)] - internal static extern IndexNameEntrySafeHandle git_index_name_get_byindex(IndexSafeHandle handle, UIntPtr n); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_index_name_entry* git_index_name_get_byindex(git_index* handle, UIntPtr n); - [DllImport(libgit2)] - internal static extern int git_index_open( - out IndexSafeHandle index, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_index_open( + out git_index* index, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath indexpath); - [DllImport(libgit2)] - internal static extern int git_index_read( - IndexSafeHandle index, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_index_read( + git_index* index, [MarshalAs(UnmanagedType.Bool)] bool force); - [DllImport(libgit2)] - internal static extern int git_index_remove_bypath( - IndexSafeHandle index, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath path); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_index_remove_bypath( + git_index* index, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string path); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe UIntPtr git_index_reuc_entrycount(git_index* handle); - [DllImport(libgit2)] - internal static extern UIntPtr git_index_reuc_entrycount(IndexSafeHandle handle); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_index_reuc_entry* git_index_reuc_get_byindex(git_index* handle, UIntPtr n); - [DllImport(libgit2)] - internal static extern IndexReucEntrySafeHandle git_index_reuc_get_byindex(IndexSafeHandle handle, UIntPtr n); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_index_reuc_entry* git_index_reuc_get_bypath( + git_index* handle, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string path); - [DllImport(libgit2)] - internal static extern IndexReucEntrySafeHandle git_index_reuc_get_bypath( - IndexSafeHandle handle, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath path); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_index_write(git_index* index); - [DllImport(libgit2)] - internal static extern int git_index_write(IndexSafeHandle index); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_index_write_tree(out GitOid treeOid, git_index* index); - [DllImport(libgit2)] - internal static extern int git_index_write_tree(out GitOid treeOid, IndexSafeHandle index); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_index_write_tree_to(out GitOid treeOid, git_index* index, git_repository* repo); - [DllImport(libgit2)] - internal static extern int git_index_write_tree_to(out GitOid treeOid, IndexSafeHandle index, RepositorySafeHandle repo); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_index_read_tree(git_index* index, git_object* tree); - [DllImport(libgit2)] - internal static extern int git_index_read_tree(IndexSafeHandle index, GitObjectSafeHandle tree); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_index_clear(git_index* index); - [DllImport(libgit2)] - internal static extern int git_index_clear(IndexSafeHandle index); - - [DllImport(libgit2)] - internal static extern int git_merge_base_many( + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_merge_base_many( out GitOid mergeBase, - RepositorySafeHandle repo, + git_repository* repo, int length, [In] GitOid[] input_array); - [DllImport(libgit2)] - internal static extern int git_merge_base_octopus( + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_merge_base_octopus( out GitOid mergeBase, - RepositorySafeHandle repo, + git_repository* repo, int length, [In] GitOid[] input_array); - [DllImport(libgit2)] - internal static extern int git_annotated_commit_from_ref( - out GitAnnotatedCommitHandle annotatedCommit, - RepositorySafeHandle repo, - ReferenceSafeHandle reference); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_annotated_commit_from_ref( + out git_annotated_commit* annotatedCommit, + git_repository* repo, + git_reference* reference); - [DllImport(libgit2)] - internal static extern int git_annotated_commit_from_fetchhead( - out GitAnnotatedCommitHandle annotatedCommit, - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_annotated_commit_from_fetchhead( + out git_annotated_commit* annotatedCommit, + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string branch_name, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string remote_url, ref GitOid oid); - [DllImport(libgit2)] - internal static extern int git_annotated_commit_from_revspec( - out GitAnnotatedCommitHandle annotatedCommit, - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_annotated_commit_from_revspec( + out git_annotated_commit* annotatedCommit, + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string revspec); - [DllImport(libgit2)] - internal static extern int git_annotated_commit_lookup( - out GitAnnotatedCommitHandle annotatedCommit, - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_annotated_commit_lookup( + out git_annotated_commit* annotatedCommit, + git_repository* repo, ref GitOid id); - [DllImport(libgit2)] - internal static extern OidSafeHandle git_annotated_commit_id( - GitAnnotatedCommitHandle annotatedCommit); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_oid* git_annotated_commit_id( + git_annotated_commit* annotatedCommit); - [DllImport(libgit2)] - internal static extern int git_merge( - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_merge( + git_repository* repo, [In] IntPtr[] their_heads, UIntPtr their_heads_len, ref GitMergeOpts merge_opts, ref GitCheckoutOpts checkout_opts); - [DllImport(libgit2)] - internal static extern int git_merge_commits( - out IndexSafeHandle index, - RepositorySafeHandle repo, - GitObjectSafeHandle our_commit, - GitObjectSafeHandle their_commit, + [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)] - internal static extern int git_merge_analysis( + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_merge_analysis( out GitMergeAnalysis status_out, out GitMergePreference preference_out, - RepositorySafeHandle repo, + git_repository* repo, [In] IntPtr[] their_heads, int their_heads_len); - [DllImport(libgit2)] - internal static extern void git_annotated_commit_free( - IntPtr merge_head); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_annotated_commit_free(git_annotated_commit* commit); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern int git_message_prettify( GitBuf buf, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string message, [MarshalAs(UnmanagedType.Bool)] bool strip_comments, sbyte comment_char); - [DllImport(libgit2)] - internal static extern int git_note_create( + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_note_create( out GitOid noteOid, - RepositorySafeHandle repo, + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string notes_ref, - SignatureSafeHandle author, - SignatureSafeHandle committer, + git_signature* author, + git_signature* committer, ref GitOid oid, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string note, int force); - [DllImport(libgit2)] - internal static extern void git_note_free(IntPtr note); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_note_free(git_note* note); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] - internal static extern string git_note_message(NoteSafeHandle note); + internal static extern unsafe string git_note_message(git_note* note); - [DllImport(libgit2)] - internal static extern OidSafeHandle git_note_id(NoteSafeHandle note); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_oid* git_note_id(git_note* note); - [DllImport(libgit2)] - internal static extern int git_note_read( - out NoteSafeHandle note, - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_note_read( + out git_note* note, + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string notes_ref, ref GitOid oid); - [DllImport(libgit2)] - internal static extern int git_note_remove( - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_note_remove( + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string notes_ref, - SignatureSafeHandle author, - SignatureSafeHandle committer, + git_signature* author, + git_signature* committer, ref GitOid oid); - [DllImport(libgit2)] - internal static extern int git_note_default_ref( + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_note_default_ref( GitBuf notes_ref, - RepositorySafeHandle repo); + git_repository* repo); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int git_note_foreach_cb( ref GitOid blob_id, ref GitOid annotated_object_id, IntPtr payload); - [DllImport(libgit2)] - internal static extern int git_note_foreach( - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_note_foreach( + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string notes_ref, git_note_foreach_cb cb, IntPtr payload); - [DllImport(libgit2)] - internal static extern int git_odb_add_backend(ObjectDatabaseSafeHandle odb, IntPtr backend, int priority); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_odb_add_backend(git_odb* odb, IntPtr backend, int priority); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr git_odb_backend_malloc(IntPtr backend, UIntPtr len); - [DllImport(libgit2)] - internal static extern int git_odb_exists(ObjectDatabaseSafeHandle odb, ref GitOid id); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_odb_exists(git_odb* odb, ref GitOid id); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int git_odb_foreach_cb( IntPtr id, IntPtr payload); - [DllImport(libgit2)] - internal static extern int git_odb_foreach( - ObjectDatabaseSafeHandle odb, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_odb_foreach( + git_odb* odb, git_odb_foreach_cb cb, IntPtr payload); - [DllImport(libgit2)] - internal static extern int git_odb_open_wstream(out OdbStreamSafeHandle stream, ObjectDatabaseSafeHandle odb, Int64 size, GitObjectType type); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_odb_open_wstream(out git_odb_stream* stream, git_odb* odb, Int64 size, GitObjectType type); - [DllImport(libgit2)] - internal static extern void git_odb_free(IntPtr odb); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_odb_free(git_odb* odb); - [DllImport(libgit2)] - internal static extern int git_odb_read_header(out UIntPtr len_out, out GitObjectType type, ObjectDatabaseSafeHandle odb, ref GitOid id); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_odb_read_header(out UIntPtr len_out, out GitObjectType type, git_odb* odb, ref GitOid id); - [DllImport(libgit2)] - internal static extern void git_object_free(IntPtr obj); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_object_free(git_object* obj); - [DllImport(libgit2)] - internal static extern int git_odb_stream_write(OdbStreamSafeHandle Stream, IntPtr Buffer, UIntPtr len); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_odb_stream_write(git_odb_stream* Stream, IntPtr Buffer, UIntPtr len); - [DllImport(libgit2)] - internal static extern int git_odb_stream_finalize_write(out GitOid id, OdbStreamSafeHandle stream); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_odb_stream_finalize_write(out GitOid id, git_odb_stream* stream); - [DllImport(libgit2)] - internal static extern void git_odb_stream_free(IntPtr stream); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe 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 int git_object_short_id( + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_object_short_id( GitBuf buf, - GitObjectSafeHandle obj); + git_object* obj); - [DllImport(libgit2)] - internal static extern GitObjectType git_object_type(GitObjectSafeHandle obj); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe GitObjectType git_object_type(git_object* obj); - [DllImport(libgit2)] - internal static extern int git_patch_from_diff(out PatchSafeHandle patch, DiffSafeHandle diff, UIntPtr idx); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_patch_from_diff(out git_patch* patch, git_diff* diff, UIntPtr idx); - [DllImport(libgit2)] - internal static extern int git_patch_print(PatchSafeHandle patch, git_diff_line_cb print_cb, IntPtr payload); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_patch_print(git_patch* patch, git_diff_line_cb print_cb, IntPtr payload); - [DllImport(libgit2)] - internal static extern int git_patch_line_stats( + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_patch_line_stats( out UIntPtr total_context, out UIntPtr total_additions, out UIntPtr total_deletions, - PatchSafeHandle patch); + git_patch* patch); - [DllImport(libgit2)] - internal static extern void git_patch_free(IntPtr patch); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_patch_free(git_patch* patch); /* Push network progress notification function */ + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int git_push_transfer_progress(uint current, uint total, UIntPtr bytes, IntPtr payload); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int git_packbuilder_progress(int stage, uint current, uint total, IntPtr payload); - [DllImport(libgit2)] - internal static extern int git_reference_create( - out ReferenceSafeHandle reference, - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_packbuilder_free(git_packbuilder* packbuilder); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_packbuilder_insert( + git_packbuilder* packbuilder, + ref GitOid id, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_packbuilder_insert_commit( + git_packbuilder* packbuilder, + ref GitOid id); + + [DllImport(libgit2, 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, 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 UInt32 git_packbuilder_set_threads(git_packbuilder* packbuilder, UInt32 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)] - internal static extern int git_reference_symbolic_create( - out ReferenceSafeHandle reference, - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_reference_symbolic_create( + out git_reference* reference, + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string target, [MarshalAs(UnmanagedType.Bool)] bool force, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string log_message); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int ref_glob_callback( IntPtr reference_name, IntPtr payload); - [DllImport(libgit2)] - internal static extern int git_reference_foreach_glob( - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_reference_foreach_glob( + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string glob, ref_glob_callback callback, IntPtr payload); - [DllImport(libgit2)] - internal static extern void git_reference_free(IntPtr reference); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_reference_free(git_reference* reference); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern int git_reference_is_valid_name( [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string refname); - [DllImport(libgit2)] - internal static extern int git_reference_list(out GitStrArray array, RepositorySafeHandle repo); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_reference_list(out GitStrArray array, git_repository* repo); - [DllImport(libgit2)] - internal static extern int git_reference_lookup( - out ReferenceSafeHandle reference, - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_reference_lookup( + out git_reference* reference, + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] - internal static extern string git_reference_name(ReferenceSafeHandle reference); + internal static extern unsafe string git_reference_name(git_reference* reference); - [DllImport(libgit2)] - internal static extern int git_reference_remove( - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_reference_remove( + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name); - [DllImport(libgit2)] - internal static extern OidSafeHandle git_reference_target(ReferenceSafeHandle reference); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_oid* git_reference_target(git_reference* reference); - [DllImport(libgit2)] - internal static extern int git_reference_rename( - out ReferenceSafeHandle ref_out, - ReferenceSafeHandle reference, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_reference_rename( + out git_reference* ref_out, + git_reference* reference, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string newName, [MarshalAs(UnmanagedType.Bool)] bool force, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string log_message); - [DllImport(libgit2)] - internal static extern int git_reference_set_target( - out ReferenceSafeHandle ref_out, - ReferenceSafeHandle reference, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_reference_set_target( + out git_reference* ref_out, + git_reference* reference, ref GitOid id, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string log_message); - [DllImport(libgit2)] - internal static extern int git_reference_symbolic_set_target( - out ReferenceSafeHandle ref_out, - ReferenceSafeHandle reference, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_reference_symbolic_set_target( + out git_reference* ref_out, + git_reference* reference, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string target, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string log_message); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] - internal static extern string git_reference_symbolic_target(ReferenceSafeHandle reference); + internal static extern unsafe string git_reference_symbolic_target(git_reference* reference); - [DllImport(libgit2)] - internal static extern GitReferenceType git_reference_type(ReferenceSafeHandle reference); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe GitReferenceType git_reference_type(git_reference* reference); - [DllImport(libgit2)] - internal static extern int git_reference_ensure_log( - RepositorySafeHandle repo, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof (StrictUtf8Marshaler))] string refname); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_reference_ensure_log( + git_repository* repo, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string refname); - [DllImport(libgit2)] - internal static extern void git_reflog_free( - IntPtr reflog); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_reflog_free(git_reflog* reflog); - [DllImport(libgit2)] - internal static extern int git_reflog_read( - out ReflogSafeHandle ref_out, - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_reflog_read( + out git_reflog* ref_out, + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name); - [DllImport(libgit2)] - internal static extern UIntPtr git_reflog_entrycount - (ReflogSafeHandle reflog); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe UIntPtr git_reflog_entrycount + (git_reflog* reflog); - [DllImport(libgit2)] - internal static extern ReflogEntrySafeHandle git_reflog_entry_byindex( - ReflogSafeHandle reflog, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_reflog_entry* git_reflog_entry_byindex( + git_reflog* reflog, UIntPtr idx); - [DllImport(libgit2)] - internal static extern OidSafeHandle git_reflog_entry_id_old( - SafeHandle entry); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_oid* git_reflog_entry_id_old( + git_reflog_entry* entry); - [DllImport(libgit2)] - internal static extern OidSafeHandle git_reflog_entry_id_new( - SafeHandle entry); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_oid* git_reflog_entry_id_new( + git_reflog_entry* entry); - [DllImport(libgit2)] - internal static extern IntPtr git_reflog_entry_committer( - SafeHandle entry); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_signature* git_reflog_entry_committer( + git_reflog_entry* entry); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] - internal static extern string git_reflog_entry_message(SafeHandle entry); + internal static extern unsafe string git_reflog_entry_message(git_reflog_entry* entry); - [DllImport(libgit2)] + [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, CallingConvention = CallingConvention.Cdecl)] internal static extern int git_refspec_rtransform( GitBuf buf, - GitRefSpecHandle refSpec, + IntPtr refspec, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] internal static extern string git_refspec_string( - GitRefSpecHandle refSpec); + IntPtr refSpec); - [DllImport(libgit2)] - internal static extern RefSpecDirection git_refspec_direction(GitRefSpecHandle refSpec); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern RefSpecDirection git_refspec_direction(IntPtr refSpec); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] internal static extern string git_refspec_dst( - GitRefSpecHandle refSpec); + IntPtr refSpec); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] internal static extern string git_refspec_src( - GitRefSpecHandle refSpec); + IntPtr refspec); - [DllImport(libgit2)] - internal static extern bool git_refspec_force(GitRefSpecHandle refSpec); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern bool git_refspec_force(IntPtr refSpec); - [DllImport(libgit2)] - internal static extern int git_remote_autotag(RemoteSafeHandle remote); + [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)] - internal static extern int git_remote_connect( - RemoteSafeHandle remote, - GitDirection direction, - ref GitRemoteCallbacks callbacks); + [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)] - internal static extern int git_remote_create( - out RemoteSafeHandle remote, - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_remote_autotag(git_remote* remote); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_remote_connect( + git_remote* remote, + GitDirection direction, + ref GitRemoteCallbacks callbacks, + ref GitProxyOptions proxy_options, + ref GitStrArray custom_headers); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_remote_create( + out git_remote* remote, + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string url); - [DllImport(libgit2)] - internal static extern int git_remote_create_anonymous( - out RemoteSafeHandle remote, - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_remote_create_anonymous( + out git_remote* remote, + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string url); - [DllImport(libgit2)] - internal static extern int git_remote_create_with_fetchspec( - out RemoteSafeHandle remote, - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_remote_create_with_fetchspec( + out git_remote* remote, + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string url, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string refspec); - [DllImport(libgit2)] - internal static extern int git_remote_delete( - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_remote_delete( + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name); - [DllImport(libgit2)] - internal static extern int git_remote_fetch( - RemoteSafeHandle remote, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_remote_fetch( + git_remote* remote, ref GitStrArray refspecs, GitFetchOptions fetch_opts, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string log_message); - [DllImport(libgit2)] - internal static extern void git_remote_free(IntPtr remote); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_remote_free(git_remote* remote); - [DllImport(libgit2)] - internal static extern int git_remote_get_fetch_refspecs(out GitStrArray array, RemoteSafeHandle remote); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_remote_get_fetch_refspecs(out GitStrArray array, git_remote* remote); - [DllImport(libgit2)] - internal static extern GitRefSpecHandle git_remote_get_refspec(RemoteSafeHandle remote, UIntPtr n); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_refspec* git_remote_get_refspec(git_remote* remote, UIntPtr n); - [DllImport(libgit2)] - internal static extern int git_remote_get_push_refspecs(out GitStrArray array, RemoteSafeHandle remote); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_remote_get_push_refspecs(out GitStrArray array, git_remote* remote); - [DllImport(libgit2)] - internal static extern int git_remote_push( - RemoteSafeHandle remote, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_remote_push( + git_remote* remote, ref GitStrArray refSpecs, GitPushOptions opts); - [DllImport(libgit2)] - internal static extern UIntPtr git_remote_refspec_count(RemoteSafeHandle remote); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe UIntPtr git_remote_refspec_count(git_remote* remote); - [DllImport(libgit2)] - internal static extern int git_remote_set_fetch_refspecs(RemoteSafeHandle remote, ref GitStrArray array); - - [DllImport(libgit2)] - internal static extern int git_remote_set_push_refspecs(RemoteSafeHandle remote, ref GitStrArray array); - - [DllImport(libgit2)] - internal static extern int git_remote_set_url( - RepositorySafeHandle repo, + [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)] - internal static extern int git_remote_add_fetch( - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_remote_add_fetch( + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string remote, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string url); - [DllImport(libgit2)] - internal static extern int git_remote_set_pushurl( - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_remote_set_pushurl( + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string remote, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string url); - [DllImport(libgit2)] - internal static extern int git_remote_add_push( - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_remote_add_push( + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string remote, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string url); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern int git_remote_is_valid_name( [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string remote_name); - [DllImport(libgit2)] - internal static extern int git_remote_list(out GitStrArray array, RepositorySafeHandle repo); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_remote_list(out GitStrArray array, git_repository* repo); - [DllImport(libgit2)] - internal static extern int git_remote_lookup( - out RemoteSafeHandle remote, - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_remote_lookup( + out git_remote* remote, + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name); - internal delegate int git_headlist_cb(ref GitRemoteHead remoteHeadPtr, IntPtr payload); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_remote_ls(out git_remote_head** heads, out UIntPtr size, git_remote* remote); - [DllImport(libgit2)] - internal static extern int git_remote_ls(out IntPtr heads, out UIntPtr size, RemoteSafeHandle remote); - - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] - internal static extern string git_remote_name(RemoteSafeHandle remote); + internal static extern unsafe string git_remote_name(git_remote* remote); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] - internal static extern string git_remote_url(RemoteSafeHandle remote); + internal static extern unsafe string git_remote_url(git_remote* remote); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] - internal static extern string git_remote_pushurl(RemoteSafeHandle remote); + internal static extern unsafe string git_remote_pushurl(git_remote* remote); - [DllImport(libgit2)] - internal static extern void git_remote_set_autotag( - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_remote_set_autotag( + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, TagFetchMode option); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int remote_progress_callback(IntPtr str, int len, IntPtr data); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int remote_completion_callback(RemoteCompletionType type, IntPtr data); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int remote_update_tips_callback( IntPtr refName, ref GitOid oldId, ref GitOid newId, IntPtr data); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int push_negotiation_callback( - IntPtr updates, // GitPushUpdate? + IntPtr updates, UIntPtr len, - IntPtr payload - ); + 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( GitBuf buf, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath start_path, [MarshalAs(UnmanagedType.Bool)] bool across_fs, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath ceiling_dirs); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int git_repository_fetchhead_foreach_cb( IntPtr remote_name, IntPtr remote_url, @@ -1166,465 +1560,591 @@ 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_unborn(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_ident( + [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, - RepositorySafeHandle repo); + git_repository* repo); - [DllImport(libgit2)] - internal static extern int git_repository_index(out IndexSafeHandle index, RepositorySafeHandle repo); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_repository_index(out git_index* index, git_repository* repo); - [DllImport(libgit2)] - internal static extern int git_repository_init_ext( - out RepositorySafeHandle repository, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_repository_init_ext( + out git_repository* repository, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath path, GitRepositoryInitOptions options); - [DllImport(libgit2)] - internal static extern int git_repository_is_bare(RepositorySafeHandle handle); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_repository_is_bare(IntPtr handle); - [DllImport(libgit2)] - internal static extern int git_repository_is_shallow(RepositorySafeHandle repo); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_repository_is_shallow(IntPtr repo); - [DllImport(libgit2)] - internal static extern int git_repository_state_cleanup(RepositorySafeHandle repo); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_repository_state_cleanup(git_repository* repo); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int git_repository_mergehead_foreach_cb( ref GitOid oid, IntPtr payload); - [DllImport(libgit2)] - internal static extern int git_repository_mergehead_foreach( - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_repository_mergehead_foreach( + git_repository* repo, git_repository_mergehead_foreach_cb cb, IntPtr payload); - [DllImport(libgit2)] - internal static extern int git_repository_message( + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_repository_message( GitBuf buf, - RepositorySafeHandle repository); + git_repository* repository); - [DllImport(libgit2)] - internal static extern int git_repository_odb(out ObjectDatabaseSafeHandle odb, RepositorySafeHandle repo); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_repository_new( + out git_repository* repo); - [DllImport(libgit2)] - internal static extern int git_repository_open( - out RepositorySafeHandle repository, + [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)] - internal static extern int git_repository_open_ext( - NullRepositorySafeHandle repository, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_repository_open_ext( + out git_repository* repository, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath path, RepositoryOpenFlags flags, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath ceilingDirs); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxFilePathNoCleanupMarshaler))] - internal static extern FilePath git_repository_path(RepositorySafeHandle repository); + internal static extern unsafe FilePath git_repository_path(git_repository* repository); - [DllImport(libgit2)] - internal static extern void git_repository_set_config( - RepositorySafeHandle repository, - ConfigurationSafeHandle config); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_repository_set_config( + git_repository* repository, + git_config* config); - [DllImport(libgit2)] - internal static extern int git_repository_set_ident( - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_repository_set_ident( + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string email); - [DllImport(libgit2)] - internal static extern void git_repository_set_index( - RepositorySafeHandle repository, - IndexSafeHandle index); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_repository_set_index( + git_repository* repository, + git_index* index); - [DllImport(libgit2)] - internal static extern int git_repository_set_workdir( - RepositorySafeHandle repository, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_repository_set_workdir( + git_repository* repository, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath workdir, [MarshalAs(UnmanagedType.Bool)] bool update_gitlink); - [DllImport(libgit2)] - internal static extern int git_repository_set_head_detached( - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_repository_set_head_detached( + git_repository* repo, ref GitOid commitish); - [DllImport(libgit2)] - internal static extern int git_repository_set_head_detached_from_annotated( - RepositorySafeHandle repo, - GitAnnotatedCommitHandle commit); + [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)] - internal static extern int git_repository_set_head( - RepositorySafeHandle repo, + [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)] - internal static extern int git_repository_state( - RepositorySafeHandle repository); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_repository_state( + git_repository* repository); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxFilePathNoCleanupMarshaler))] - internal static extern FilePath git_repository_workdir(RepositorySafeHandle repository); + internal static extern unsafe FilePath git_repository_workdir(git_repository* repository); - [DllImport(libgit2)] - internal static extern int git_reset( - RepositorySafeHandle repo, - GitObjectSafeHandle target, + [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)] - internal static extern int git_revert( - RepositorySafeHandle repo, - GitObjectSafeHandle commit, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_revert( + git_repository* repo, + git_object* commit, GitRevertOpts opts); - [DllImport(libgit2)] - internal static extern int git_revparse_ext( - out GitObjectSafeHandle obj, - out ReferenceSafeHandle reference, - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_revert_commit( + out git_index* index, + git_repository* repo, + git_object* revert_commit, + git_object* our_commit, + uint mainline, + ref GitMergeOpts opts); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_revparse_ext( + out git_object* obj, + out git_reference* reference, + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string spec); - [DllImport(libgit2)] - internal static extern void git_revwalk_free(IntPtr walker); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_revwalk_free(git_revwalk* walker); - [DllImport(libgit2)] - internal static extern int git_revwalk_hide(RevWalkerSafeHandle walker, ref GitOid commit_id); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_revwalk_hide(git_revwalk* walker, ref GitOid commit_id); - [DllImport(libgit2)] - internal static extern int git_revwalk_new(out RevWalkerSafeHandle walker, RepositorySafeHandle repo); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_revwalk_new(out git_revwalk* walker, git_repository* repo); - [DllImport(libgit2)] - internal static extern int git_revwalk_next(out GitOid id, RevWalkerSafeHandle walker); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_revwalk_next(out GitOid id, git_revwalk* walker); - [DllImport(libgit2)] - internal static extern int git_revwalk_push(RevWalkerSafeHandle walker, ref GitOid id); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_revwalk_push(git_revwalk* walker, ref GitOid id); - [DllImport(libgit2)] - internal static extern void git_revwalk_reset(RevWalkerSafeHandle walker); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_revwalk_reset(git_revwalk* walker); - [DllImport(libgit2)] - internal static extern void git_revwalk_sorting(RevWalkerSafeHandle walk, CommitSortStrategies sort); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_revwalk_sorting(git_revwalk* walk, CommitSortStrategies sort); - [DllImport(libgit2)] - internal static extern void git_revwalk_simplify_first_parent(RevWalkerSafeHandle walk); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_revwalk_simplify_first_parent(git_revwalk* walk); - [DllImport(libgit2)] - internal static extern void git_signature_free(IntPtr signature); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_signature_free(git_signature* signature); - [DllImport(libgit2)] - internal static extern int git_signature_new( - out SignatureSafeHandle signature, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_signature_new( + out git_signature* signature, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string email, long time, int offset); - [DllImport(libgit2)] - internal static extern int git_signature_dup(out IntPtr dest, IntPtr sig); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_signature_now( + out git_signature* signature, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string email); - [DllImport(libgit2)] - internal static extern int git_stash_save( + [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, + git_repository* repo, + git_signature* stasher, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string message, StashModifiers flags); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int git_stash_cb( UIntPtr index, IntPtr message, ref GitOid stash_id, IntPtr payload); - [DllImport(libgit2)] - internal static extern int git_stash_foreach( - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_stash_foreach( + git_repository* repo, git_stash_cb callback, IntPtr payload); - [DllImport(libgit2)] - internal static extern int git_stash_drop(RepositorySafeHandle repo, UIntPtr index); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_stash_drop(git_repository* repo, UIntPtr index); - [DllImport(libgit2)] - internal static extern int git_status_file( + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_stash_apply( + git_repository* repo, + UIntPtr index, + GitStashApplyOpts opts); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_stash_pop( + git_repository* repo, + UIntPtr index, + GitStashApplyOpts opts); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_status_file( out FileStatus statusflags, - RepositorySafeHandle repo, + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath filepath); - [DllImport(libgit2)] - internal static extern int git_status_list_new( - out StatusListSafeHandle git_status_list, - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_status_list_new( + out git_status_list* git_status_list, + git_repository* repo, GitStatusOptions options); - [DllImport(libgit2)] - internal static extern int git_status_list_entrycount( - StatusListSafeHandle statusList); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_status_list_entrycount( + git_status_list* statusList); - [DllImport(libgit2)] - internal static extern StatusEntrySafeHandle git_status_byindex( - StatusListSafeHandle list, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_status_entry* git_status_byindex( + git_status_list* list, UIntPtr idx); - [DllImport(libgit2)] - internal static extern void git_status_list_free( - IntPtr statusList); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_status_list_free( + git_status_list* statusList); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern void git_strarray_free( ref GitStrArray array); - [DllImport(libgit2)] - internal static extern int git_submodule_lookup( - out SubmoduleSafeHandle reference, - RepositorySafeHandle repo, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath name); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_submodule_lookup( + out git_submodule* reference, + git_repository* repo, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name); - [DllImport(libgit2)] - internal static extern int git_submodule_resolve_url( + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_submodule_resolve_url( GitBuf buf, - RepositorySafeHandle repo, + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string url); - [DllImport(libgit2)] - internal static extern int git_submodule_update( - SubmoduleSafeHandle sm, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_submodule_update( + git_submodule* sm, [MarshalAs(UnmanagedType.Bool)] bool init, - ref GitSubmoduleOptions submoduleUpdateOptions); + ref GitSubmoduleUpdateOptions submoduleUpdateOptions); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int submodule_callback( IntPtr sm, IntPtr name, IntPtr payload); - [DllImport(libgit2)] - internal static extern int git_submodule_foreach( - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_submodule_foreach( + git_repository* repo, submodule_callback callback, IntPtr payload); - [DllImport(libgit2)] - internal static extern int git_submodule_add_to_index( - SubmoduleSafeHandle submodule, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_submodule_add_to_index( + git_submodule* submodule, [MarshalAs(UnmanagedType.Bool)] bool write_index); - [DllImport(libgit2)] - internal static extern int git_submodule_save( - SubmoduleSafeHandle submodule); - - [DllImport(libgit2)] - internal static extern void git_submodule_free( - IntPtr submodule); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_submodule_free(git_submodule* submodule); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] - internal static extern string git_submodule_path( - SubmoduleSafeHandle submodule); + internal static extern unsafe string git_submodule_path( + git_submodule* submodule); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] - internal static extern string git_submodule_url( - SubmoduleSafeHandle submodule); + internal static extern unsafe string git_submodule_url( + git_submodule* submodule); - [DllImport(libgit2)] - internal static extern OidSafeHandle git_submodule_index_id( - SubmoduleSafeHandle submodule); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_oid* git_submodule_index_id( + git_submodule* submodule); - [DllImport(libgit2)] - internal static extern OidSafeHandle git_submodule_head_id( - SubmoduleSafeHandle submodule); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_oid* git_submodule_head_id( + git_submodule* submodule); - [DllImport(libgit2)] - internal static extern OidSafeHandle git_submodule_wd_id( - SubmoduleSafeHandle submodule); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_oid* git_submodule_wd_id( + git_submodule* submodule); - [DllImport(libgit2)] - internal static extern SubmoduleIgnore git_submodule_ignore( - SubmoduleSafeHandle submodule); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe SubmoduleIgnore git_submodule_ignore( + git_submodule* submodule); - [DllImport(libgit2)] - internal static extern SubmoduleUpdate git_submodule_update_strategy( - SubmoduleSafeHandle submodule); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe SubmoduleUpdate git_submodule_update_strategy( + git_submodule* submodule); - [DllImport(libgit2)] - internal static extern SubmoduleRecurse git_submodule_fetch_recurse_submodules( - SubmoduleSafeHandle submodule); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe SubmoduleRecurse git_submodule_fetch_recurse_submodules( + git_submodule* submodule); - [DllImport(libgit2)] - internal static extern int git_submodule_reload( - SubmoduleSafeHandle submodule, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_submodule_reload( + git_submodule* submodule, [MarshalAs(UnmanagedType.Bool)] bool force); - [DllImport(libgit2)] - internal static extern int git_submodule_status( + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_submodule_status( out SubmoduleStatus status, - SubmoduleSafeHandle submodule); + git_repository* repo, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath name, + GitSubmoduleIgnore ignore); - [DllImport(libgit2)] - internal static extern int git_submodule_init( - SubmoduleSafeHandle submodule, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_submodule_init( + git_submodule* submodule, [MarshalAs(UnmanagedType.Bool)] bool overwrite); - [DllImport(libgit2)] - internal static extern int git_tag_annotation_create( + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_tag_annotation_create( out GitOid oid, - RepositorySafeHandle repo, + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, - GitObjectSafeHandle target, - SignatureSafeHandle signature, + git_object* target, + git_signature* signature, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string message); - [DllImport(libgit2)] - internal static extern int git_tag_create( + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_tag_create( out GitOid oid, - RepositorySafeHandle repo, + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, - GitObjectSafeHandle target, - SignatureSafeHandle signature, + git_object* target, + git_signature* signature, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string message, [MarshalAs(UnmanagedType.Bool)] bool force); - [DllImport(libgit2)] - internal static extern int git_tag_create_lightweight( + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_tag_create_lightweight( out GitOid oid, - RepositorySafeHandle repo, + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, - GitObjectSafeHandle target, + git_object* target, [MarshalAs(UnmanagedType.Bool)] bool force); - [DllImport(libgit2)] - internal static extern int git_tag_delete( - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_tag_delete( + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string tagName); - [DllImport(libgit2)] - internal static extern int git_tag_list(out GitStrArray array, RepositorySafeHandle repo); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_tag_list(out GitStrArray array, git_repository* repo); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] - internal static extern string git_tag_message(GitObjectSafeHandle tag); + internal static extern unsafe string git_tag_message(git_object* tag); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] - internal static extern string git_tag_name(GitObjectSafeHandle tag); + internal static extern unsafe string git_tag_name(git_object* tag); - [DllImport(libgit2)] - internal static extern IntPtr git_tag_tagger(GitObjectSafeHandle tag); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_signature* git_tag_tagger(git_object* tag); - [DllImport(libgit2)] - internal static extern OidSafeHandle git_tag_target_id(GitObjectSafeHandle tag); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_oid* git_tag_target_id(git_object* tag); - [DllImport(libgit2)] - internal static extern GitObjectType git_tag_target_type(GitObjectSafeHandle tag); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe GitObjectType git_tag_target_type(git_object* tag); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern int git_libgit2_init(); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern int git_libgit2_shutdown(); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern int git_openssl_set_locking(); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate void git_trace_cb(LogLevel level, IntPtr message); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern int git_trace_set(LogLevel level, git_trace_cb trace_cb); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int git_transfer_progress_callback(ref GitTransferProgress stats, IntPtr payload); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int git_transport_cb(out IntPtr transport, IntPtr remote, IntPtr payload); - [DllImport(libgit2)] + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal unsafe delegate int git_transport_certificate_check_cb(git_certificate* cert, int valid, IntPtr hostname, IntPtr payload); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern int git_transport_register( [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string prefix, IntPtr transport_cb, IntPtr payload); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern int git_transport_smart( out IntPtr transport, IntPtr remote, IntPtr definition); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_transport_smart_certificate_check( + IntPtr transport, + IntPtr cert, + int valid, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string hostname); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_transport_smart_credentials( + out IntPtr cred_out, + IntPtr transport, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string user, + int methods); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern int git_transport_unregister( [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string prefix); - [DllImport(libgit2)] - internal static extern uint git_tree_entry_filemode(SafeHandle entry); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe uint git_tree_entry_filemode(git_tree_entry* entry); - [DllImport(libgit2)] - internal static extern TreeEntrySafeHandle git_tree_entry_byindex(GitObjectSafeHandle tree, UIntPtr idx); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_tree_entry* git_tree_entry_byindex(git_object* tree, UIntPtr idx); - [DllImport(libgit2)] - internal static extern int git_tree_entry_bypath( - out TreeEntrySafeHandle_Owned tree, - GitObjectSafeHandle root, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath treeentry_path); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_tree_entry_bypath( + out git_tree_entry* tree, + git_object* root, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string treeentry_path); - [DllImport(libgit2)] - internal static extern void git_tree_entry_free(IntPtr treeEntry); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_tree_entry_free(git_tree_entry* treeEntry); - [DllImport(libgit2)] - internal static extern OidSafeHandle git_tree_entry_id(SafeHandle entry); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_oid* git_tree_entry_id(git_tree_entry* entry); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] - internal static extern string git_tree_entry_name(SafeHandle entry); + internal static extern unsafe string git_tree_entry_name(git_tree_entry* entry); - [DllImport(libgit2)] - internal static extern GitObjectType git_tree_entry_type(SafeHandle entry); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe GitObjectType git_tree_entry_type(git_tree_entry* entry); - [DllImport(libgit2)] - internal static extern UIntPtr git_tree_entrycount(GitObjectSafeHandle tree); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe UIntPtr git_tree_entrycount(git_object* tree); - [DllImport(libgit2)] - internal static extern int git_treebuilder_new(out TreeBuilderSafeHandle builder, RepositorySafeHandle repo, IntPtr src); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_treebuilder_new(out git_treebuilder* builder, git_repository* repo, IntPtr src); - [DllImport(libgit2)] - internal static extern int git_treebuilder_insert( + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_treebuilder_insert( IntPtr entry_out, - TreeBuilderSafeHandle builder, + git_treebuilder* builder, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string treeentry_name, ref GitOid id, uint attributes); - [DllImport(libgit2)] - internal static extern int git_treebuilder_write(out GitOid id, TreeBuilderSafeHandle bld); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_treebuilder_write(out GitOid id, git_treebuilder* bld); - [DllImport(libgit2)] - internal static extern void git_treebuilder_free(IntPtr bld); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_treebuilder_free(git_treebuilder* bld); - [DllImport(libgit2)] - internal static extern int git_blob_is_binary(GitObjectSafeHandle blob); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_blob_is_binary(git_object* blob); - [DllImport(libgit2)] - internal static extern int git_cherrypick(RepositorySafeHandle repo, GitObjectSafeHandle commit, GitCherryPickOptions options); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_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, 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..8bb7e9633 100644 --- a/LibGit2Sharp/Core/ObjectSafeWrapper.cs +++ b/LibGit2Sharp/Core/ObjectSafeWrapper.cs @@ -5,15 +5,15 @@ 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) { Ensure.ArgumentNotNull(handle, "handle"); if (allowNullObjectId && id == null) { - objectPtr = new NullGitObjectSafeHandle(); + objectPtr = new ObjectHandle(null, false); } else { @@ -22,7 +22,7 @@ public ObjectSafeWrapper(ObjectId id, RepositorySafeHandle handle, bool allowNul } } - public GitObjectSafeHandle ObjectPtr + public ObjectHandle ObjectPtr { get { return objectPtr; } } diff --git a/LibGit2Sharp/Core/Opaques.cs b/LibGit2Sharp/Core/Opaques.cs new file mode 100644 index 000000000..f5613a276 --- /dev/null +++ b/LibGit2Sharp/Core/Opaques.cs @@ -0,0 +1,32 @@ +using System; + +namespace LibGit2Sharp.Core +{ + internal struct git_tree_entry {} + internal struct git_reference { } + internal struct git_refspec {} + internal struct git_repository {} + internal struct git_status_list {} + internal struct git_blame {} + internal struct git_diff {} + internal struct git_patch {} + internal struct git_config {} + internal struct git_index_conflict_iterator {} + internal struct git_index {} + internal struct git_reflog {} + internal struct git_reflog_entry {} + internal struct git_treebuilder {} + internal struct git_packbuilder {} + internal struct git_note {} + internal struct git_describe_result {} + internal struct git_submodule {} + internal struct git_annotated_commit {} + internal struct git_odb {} + internal struct git_revwalk {} + internal struct git_remote {} + internal struct git_object {} + internal struct git_rebase {} + internal struct git_odb_stream {} + internal struct git_worktree { } +} + diff --git a/LibGit2Sharp/Core/PathCase.cs b/LibGit2Sharp/Core/PathCase.cs index a5fadbb48..600f693de 100644 --- a/LibGit2Sharp/Core/PathCase.cs +++ b/LibGit2Sharp/Core/PathCase.cs @@ -16,6 +16,7 @@ public PathCase(IRepository repo) comparer = StringComparer.OrdinalIgnoreCase; comparison = StringComparison.OrdinalIgnoreCase; break; + default: comparer = StringComparer.Ordinal; comparison = StringComparison.Ordinal; diff --git a/LibGit2Sharp/Core/Platform.cs b/LibGit2Sharp/Core/Platform.cs index debdc73ae..e8d536475 100644 --- a/LibGit2Sharp/Core/Platform.cs +++ b/LibGit2Sharp/Core/Platform.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.InteropServices; namespace LibGit2Sharp.Core { @@ -11,30 +12,64 @@ internal enum OperatingSystemType internal static class Platform { - public static string ProcessorArchitecture + public static string ProcessorArchitecture => IntPtr.Size == 8 ? "x64" : "x86"; + + public static OperatingSystemType OperatingSystem { get { - return Environment.Is64BitProcess ? "amd64" : "x86"; + 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 OperatingSystemType OperatingSystem + public static string GetNativeLibraryExtension() { - get + switch (OperatingSystem) { - // See http://www.mono-project.com/docs/faq/technical/#how-to-detect-the-execution-platform - switch ((int)Environment.OSVersion.Platform) - { - case 4: - case 128: - return OperatingSystemType.Unix; - case 6: - return OperatingSystemType.MacOSX; - default: - return OperatingSystemType.Windows; - } + 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() + => Type.GetType("Mono.Runtime") != null; + + /// + /// 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 65a389a08..7857224b4 100644 --- a/LibGit2Sharp/Core/Proxy.cs +++ b/LibGit2Sharp/Core/Proxy.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; @@ -94,47 +95,42 @@ private static void BuildErrorMessageFromException(StringBuilder sb, int level, #region git_blame_ - public static BlameSafeHandle git_blame_file( - RepositorySafeHandle repo, - FilePath path, - GitBlameOptions options) + public static unsafe BlameHandle git_blame_file( + RepositoryHandle repo, + string path, + git_blame_options options) { - BlameSafeHandle handle; - int res = NativeMethods.git_blame_file(out handle, repo, path, options); + git_blame* ptr; + int res = NativeMethods.git_blame_file(out ptr, repo, path, options); Ensure.ZeroResult(res); - return handle; - } - - public static GitBlameHunk git_blame_get_hunk_byindex(BlameSafeHandle blame, uint idx) - { - return NativeMethods.git_blame_get_hunk_byindex(blame, idx).MarshalAs(false); + return new BlameHandle(ptr, true); } - public static void git_blame_free(IntPtr blame) + public static unsafe git_blame_hunk* git_blame_get_hunk_byindex(BlameHandle blame, uint idx) { - NativeMethods.git_blame_free(blame); + return NativeMethods.git_blame_get_hunk_byindex(blame, idx); } #endregion #region git_blob_ - public static ObjectId git_blob_create_fromchunks(RepositorySafeHandle repo, FilePath hintpath, NativeMethods.source_callback fileCallback) + public static unsafe IntPtr git_blob_create_fromstream(RepositoryHandle repo, string hintpath) { - var oid = new GitOid(); - int res = NativeMethods.git_blob_create_fromchunks(ref oid, repo, hintpath, fileCallback, IntPtr.Zero); - - if (res == (int)GitErrorCode.User) - { - throw new EndOfStreamException("The stream ended unexpectedly"); - } + IntPtr writestream_ptr; - Ensure.ZeroResult(res); + Ensure.ZeroResult(NativeMethods.git_blob_create_fromstream(out writestream_ptr, repo, hintpath)); + return writestream_ptr; + } + public static unsafe ObjectId git_blob_create_fromstream_commit(IntPtr writestream_ptr) + { + var oid = new GitOid(); + Ensure.ZeroResult(NativeMethods.git_blob_create_fromstream_commit(ref oid, writestream_ptr)); return oid; } - public static ObjectId git_blob_create_fromdisk(RepositorySafeHandle repo, FilePath path) + public static unsafe ObjectId git_blob_create_fromdisk(RepositoryHandle repo, FilePath path) { var oid = new GitOid(); int res = NativeMethods.git_blob_create_fromdisk(ref oid, repo, path); @@ -143,7 +139,7 @@ public static ObjectId git_blob_create_fromdisk(RepositorySafeHandle repo, FileP return oid; } - public static ObjectId git_blob_create_fromfile(RepositorySafeHandle repo, FilePath path) + public static unsafe ObjectId git_blob_create_fromfile(RepositoryHandle repo, FilePath path) { var oid = new GitOid(); int res = NativeMethods.git_blob_create_fromworkdir(ref oid, repo, path); @@ -152,7 +148,7 @@ public static ObjectId git_blob_create_fromfile(RepositorySafeHandle repo, FileP return oid; } - public static UnmanagedMemoryStream git_blob_filtered_content_stream(RepositorySafeHandle repo, ObjectId id, FilePath path, bool check_for_binary_data) + public static unsafe UnmanagedMemoryStream git_blob_filtered_content_stream(RepositoryHandle repo, ObjectId id, string path, bool check_for_binary_data) { var buf = new GitBuf(); var handle = new ObjectSafeWrapper(id, repo).ObjectPtr; @@ -166,18 +162,18 @@ public static UnmanagedMemoryStream git_blob_filtered_content_stream(RepositoryS new[] { buf }); } - public static UnmanagedMemoryStream git_blob_rawcontent_stream(RepositorySafeHandle repo, ObjectId id, Int64 size) + public static unsafe UnmanagedMemoryStream git_blob_rawcontent_stream(RepositoryHandle repo, ObjectId id, Int64 size) { var handle = new ObjectSafeWrapper(id, repo).ObjectPtr; - return new RawContentStream(handle, NativeMethods.git_blob_rawcontent, h => size); + 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); @@ -189,9 +185,9 @@ public static bool git_blob_is_binary(GitObjectSafeHandle obj) #region git_branch_ - public static ReferenceSafeHandle git_branch_create_from_annotated(RepositorySafeHandle repo, string branch_name, string targetIdentifier, bool force) + public static unsafe ReferenceHandle git_branch_create_from_annotated(RepositoryHandle repo, string branch_name, string targetIdentifier, bool force) { - ReferenceSafeHandle reference; + git_reference* reference; using (var annotatedCommit = git_annotated_commit_from_revspec(repo, targetIdentifier)) { @@ -199,10 +195,10 @@ public static ReferenceSafeHandle git_branch_create_from_annotated(RepositorySaf Ensure.ZeroResult(res); } - return reference; + return new ReferenceHandle(reference, true); } - public static void git_branch_delete(ReferenceSafeHandle reference) + public static unsafe void git_branch_delete(ReferenceHandle reference) { int res = NativeMethods.git_branch_delete(reference); Ensure.ZeroResult(res); @@ -210,21 +206,35 @@ public static void git_branch_delete(ReferenceSafeHandle reference) public static IEnumerable git_branch_iterator(Repository repo, GitBranchType branchType) { - return git_iterator( - (out BranchIteratorSafeHandle iter_out) => - NativeMethods.git_branch_iterator_new(out iter_out, repo.Handle, branchType), - (BranchIteratorSafeHandle iter, out ReferenceSafeHandle ref_out, out int res) => + IntPtr iter; + var res = NativeMethods.git_branch_iterator_new(out iter, repo.Handle.AsIntPtr(), branchType); + Ensure.ZeroResult(res); + + try + { + while (true) + { + IntPtr refPtr = IntPtr.Zero; + GitBranchType _branchType; + res = NativeMethods.git_branch_next(out refPtr, out _branchType, iter); + if (res == (int)GitErrorCode.IterOver) { - GitBranchType type_out; - res = NativeMethods.git_branch_next(out ref_out, out type_out, iter); - return new { BranchType = type_out }; - }, - (handle, payload) => + yield break; + } + Ensure.ZeroResult(res); + + Reference reference; + using (var refHandle = new ReferenceHandle(refPtr, true)) { - var reference = Reference.BuildFromPtr(handle, repo); - return new Branch(repo, reference, reference.CanonicalName); + reference = Reference.BuildFromPtr(refHandle, repo); } - ); + yield return new Branch(repo, reference, reference.CanonicalName); + } + } + finally + { + NativeMethods.git_branch_iterator_free(iter); + } } public static void git_branch_iterator_free(IntPtr iter) @@ -232,22 +242,22 @@ public static void git_branch_iterator_free(IntPtr iter) NativeMethods.git_branch_iterator_free(iter); } - public static ReferenceSafeHandle git_branch_move(ReferenceSafeHandle reference, string new_branch_name, bool force) + public static unsafe ReferenceHandle git_branch_move(ReferenceHandle reference, string new_branch_name, bool force) { - ReferenceSafeHandle ref_out; + git_reference* ref_out; int res = NativeMethods.git_branch_move(out ref_out, reference, new_branch_name, force); Ensure.ZeroResult(res); - return ref_out; + return new ReferenceHandle(ref_out, true); } - public static string git_branch_remote_name(RepositorySafeHandle repo, string canonical_branch_name, bool shouldThrowIfNotFound) + public static unsafe string git_branch_remote_name(RepositoryHandle repo, string canonical_branch_name, bool shouldThrowIfNotFound) { using (var buf = new GitBuf()) { int res = NativeMethods.git_branch_remote_name(buf, repo, canonical_branch_name); if (!shouldThrowIfNotFound && - (res == (int) GitErrorCode.NotFound || res == (int) GitErrorCode.Ambiguous)) + (res == (int)GitErrorCode.NotFound || res == (int)GitErrorCode.Ambiguous)) { return null; } @@ -257,12 +267,12 @@ public static string git_branch_remote_name(RepositorySafeHandle repo, string ca } } - public static string git_branch_upstream_name(RepositorySafeHandle handle, string canonicalReferenceName) + public static unsafe string git_branch_upstream_name(RepositoryHandle handle, string canonicalReferenceName) { using (var buf = new GitBuf()) { int res = NativeMethods.git_branch_upstream_name(buf, handle, canonicalReferenceName); - if (res == (int) GitErrorCode.NotFound) + if (res == (int)GitErrorCode.NotFound) { return null; } @@ -285,8 +295,8 @@ public static void git_buf_free(GitBuf buf) #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) { @@ -297,7 +307,7 @@ 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); @@ -307,7 +317,7 @@ public static void git_checkout_index(RepositorySafeHandle repo, GitObjectSafeHa #region git_cherry_pick_ - internal static void git_cherrypick(RepositorySafeHandle repo, ObjectId commit, GitCherryPickOptions options) + internal static unsafe void git_cherrypick(RepositoryHandle repo, ObjectId commit, GitCherryPickOptions options) { using (var nativeCommit = git_object_lookup(repo, commit, GitObjectType.Commit)) { @@ -315,37 +325,53 @@ internal static void git_cherrypick(RepositorySafeHandle repo, ObjectId commit, Ensure.ZeroResult(res); } } + + internal static unsafe IndexHandle git_cherrypick_commit(RepositoryHandle repo, ObjectHandle cherrypickCommit, ObjectHandle ourCommit, uint mainline, GitMergeOpts opts, out bool earlyStop) + { + git_index* index; + int res = NativeMethods.git_cherrypick_commit(out index, repo, cherrypickCommit, ourCommit, mainline, ref opts); + if (res == (int)GitErrorCode.MergeConflict) + { + earlyStop = true; + } + else + { + earlyStop = false; + Ensure.ZeroResult(res); + } + return new IndexHandle(index, true); + } #endregion #region git_clone_ - public static RepositorySafeHandle git_clone( + public static unsafe RepositoryHandle git_clone( string url, string workdir, ref GitCloneOptions opts) { - RepositorySafeHandle repo; + git_repository *repo; int res = NativeMethods.git_clone(out repo, url, workdir, ref opts); Ensure.ZeroResult(res); - return repo; + 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, @@ -353,18 +379,24 @@ public static ObjectId git_commit_create( Tree tree, GitOid[] parentIds) { - using (SignatureSafeHandle authorHandle = author.BuildHandle()) - using (SignatureSafeHandle committerHandle = committer.BuildHandle()) + using (SignatureHandle authorHandle = author.BuildHandle()) + using (SignatureHandle committerHandle = committer.BuildHandle()) using (var parentPtrs = new ArrayMarshaler(parentIds)) { GitOid commitOid; var treeOid = tree.Id.Oid; - int res = NativeMethods.git_commit_create_from_ids( - out commitOid, repo, referenceName, authorHandle, - committerHandle, null, message, - ref treeOid, (UIntPtr)parentPtrs.Count, parentPtrs.ToArray()); + int res = NativeMethods.git_commit_create_from_ids(out commitOid, + repo, + referenceName, + authorHandle, + committerHandle, + null, + message, + ref treeOid, + (UIntPtr)parentPtrs.Count, + parentPtrs.ToArray()); Ensure.ZeroResult(res); @@ -372,27 +404,82 @@ public static ObjectId git_commit_create( } } - public static string git_commit_message(GitObjectSafeHandle obj) + public static unsafe string git_commit_create_buffer( + RepositoryHandle repo, + Signature author, + Signature committer, + string message, + Tree tree, + Commit[] parents) + { + using (SignatureHandle authorHandle = author.BuildHandle()) + using (SignatureHandle committerHandle = committer.BuildHandle()) + using (var treeHandle = Proxy.git_object_lookup(tree.repo.Handle, tree.Id, GitObjectType.Tree)) + using (var buf = new GitBuf()) + { + ObjectHandle[] handles = new ObjectHandle[0]; + try + { + handles = parents.Select(c => Proxy.git_object_lookup(c.repo.Handle, c.Id, GitObjectType.Commit)).ToArray(); + var ptrs = handles.Select(p => p.AsIntPtr()).ToArray(); + int res; + fixed(IntPtr* objs = ptrs) + { + res = NativeMethods.git_commit_create_buffer(buf, + repo, + authorHandle, + committerHandle, + null, + message, + treeHandle, + new UIntPtr((ulong)parents.LongCount()), + objs); + } + Ensure.ZeroResult(res); + } + finally + { + foreach (var handle in handles) + { + handle.Dispose(); + } + } + + return LaxUtf8Marshaler.FromNative(buf.ptr); + } + } + + public static unsafe ObjectId git_commit_create_with_signature(RepositoryHandle repo, string commitContent, + string signature, string field) + { + GitOid id; + int res = NativeMethods.git_commit_create_with_signature(out id, repo, commitContent, signature, field); + Ensure.ZeroResult(res); + + return id; + } + + public static unsafe string git_commit_message(ObjectHandle obj) { return NativeMethods.git_commit_message(obj); } - public static string git_commit_summary(GitObjectSafeHandle obj) + public static unsafe string git_commit_summary(ObjectHandle obj) { return NativeMethods.git_commit_summary(obj); } - public static string git_commit_message_encoding(GitObjectSafeHandle obj) + public static unsafe string git_commit_message_encoding(ObjectHandle obj) { return NativeMethods.git_commit_message_encoding(obj); } - public static ObjectId git_commit_parent_id(GitObjectSafeHandle obj, uint i) + public static unsafe ObjectId git_commit_parent_id(ObjectHandle obj, uint i) { - return NativeMethods.git_commit_parent_id(obj, i).MarshalAsObjectId(); + return ObjectId.BuildFromPtr(NativeMethods.git_commit_parent_id(obj, i)); } - public static int git_commit_parentcount(RepositorySafeHandle repo, ObjectId id) + public static int git_commit_parentcount(RepositoryHandle repo, ObjectId id) { using (var obj = new ObjectSafeWrapper(id, repo)) { @@ -400,27 +487,45 @@ public static int git_commit_parentcount(RepositorySafeHandle repo, ObjectId id) } } - public static int git_commit_parentcount(ObjectSafeWrapper obj) + public static unsafe int git_commit_parentcount(ObjectSafeWrapper obj) { return (int)NativeMethods.git_commit_parentcount(obj.ObjectPtr); } - public static ObjectId git_commit_tree_id(GitObjectSafeHandle obj) + public static unsafe ObjectId git_commit_tree_id(ObjectHandle obj) { - return NativeMethods.git_commit_tree_id(obj).MarshalAsObjectId(); + return ObjectId.BuildFromPtr(NativeMethods.git_commit_tree_id(obj)); + } + + public static unsafe SignatureInfo git_commit_extract_signature(RepositoryHandle repo, ObjectId id, string field) + { + 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) { - int res = NativeMethods.git_config_add_file_ondisk(config, path, (uint)level, true); + // 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) { int res = NativeMethods.git_config_delete_entry(config, name); @@ -435,7 +540,7 @@ public static bool git_config_delete(ConfigurationSafeHandle config, string name const string anyValue = ".*"; - public static bool git_config_delete_multivar(ConfigurationSafeHandle config, string name) + public static unsafe bool git_config_delete_multivar(ConfigurationHandle config, string name) { int res = NativeMethods.git_config_delete_multivar(config, name, anyValue); @@ -463,61 +568,55 @@ 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 void git_config_entry_free(IntPtr entry) + public static unsafe void git_config_free(git_config *config) { - NativeMethods.git_config_entry_free(entry); + NativeMethods.git_config_free(config); } - public static ConfigurationEntry git_config_get_entry(ConfigurationSafeHandle config, string key) + public static unsafe ConfigurationEntry git_config_get_entry(ConfigurationHandle config, string key) { - GitConfigEntryHandle handle = null; - if (!configurationParser.ContainsKey(typeof(T))) { throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "Generic Argument of type '{0}' is not supported.", typeof(T).FullName)); } - GitConfigEntry entry; - + 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); - - entry = handle.MarshalAsGitConfigEntry(); + return new ConfigurationEntry(LaxUtf8Marshaler.FromNative(entry->namePtr), + (T)configurationParser[typeof(T)](LaxUtf8Marshaler.FromNative(entry->valuePtr)), + (ConfigurationLevel)entry->level); } finally { - handle.SafeDispose(); + NativeMethods.git_config_entry_free(entry); } - - return new ConfigurationEntry(LaxUtf8Marshaler.FromNative(entry.namePtr), - (T)configurationParser[typeof(T)](LaxUtf8Marshaler.FromNative(entry.valuePtr)), - (ConfigurationLevel)entry.level); } - public static ConfigurationSafeHandle git_config_new() + public static unsafe ConfigurationHandle git_config_new() { - ConfigurationSafeHandle handle; + 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) { - ConfigurationSafeHandle handle; + git_config* handle; int res = NativeMethods.git_config_open_level(out handle, parent, (uint)level); if (res == (int)GitErrorCode.NotFound) @@ -527,7 +626,7 @@ public static ConfigurationSafeHandle git_config_open_level(ConfigurationSafeHan Ensure.ZeroResult(res); - return handle; + return new ConfigurationHandle(handle, true); } public static bool git_config_parse_bool(string value) @@ -557,82 +656,110 @@ public static long git_config_parse_int64(string value) 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) { 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) { 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) { 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) { int res = NativeMethods.git_config_set_string(config, name, value); Ensure.ZeroResult(res); } - public static ICollection git_config_foreach( - ConfigurationSafeHandle config, + static readonly string non_existing_regex = Guid.NewGuid().ToString(); + + public static unsafe void git_config_add_string(ConfigurationHandle config, string name, string value) + { + int res = NativeMethods.git_config_set_multivar(config, name, non_existing_regex, value); + Ensure.ZeroResult(res); + } + + public static unsafe ICollection git_config_foreach( + ConfigurationHandle config, Func resultSelector) { return git_foreach(resultSelector, c => NativeMethods.git_config_foreach(config, (e, p) => c(e, p), IntPtr.Zero)); } public static IEnumerable> git_config_iterator_glob( - ConfigurationSafeHandle config, - string regexp, - Func> resultSelector) - { - return git_iterator( - (out ConfigurationIteratorSafeHandle iter) => - NativeMethods.git_config_iterator_glob_new(out iter, config, regexp), - (ConfigurationIteratorSafeHandle iter, out SafeHandleBase handle, out int res) => + ConfigurationHandle config, + string regexp) + { + IntPtr iter; + var res = NativeMethods.git_config_iterator_glob_new(out iter, config.AsIntPtr(), regexp); + Ensure.ZeroResult(res); + try + { + while (true) + { + IntPtr entry; + res = NativeMethods.git_config_next(out entry, iter); + if (res == (int)GitErrorCode.IterOver) { - handle = null; + yield break; + } + Ensure.ZeroResult(res); - IntPtr entry; - res = NativeMethods.git_config_next(out entry, iter); - return new { EntryPtr = entry }; - }, - (handle, payload) => resultSelector(payload.EntryPtr) - ); + yield return Configuration.BuildConfigEntry(entry); + } + } + finally + { + NativeMethods.git_config_iterator_free(iter); + } } - public static void git_config_iterator_free(IntPtr iter) + public static unsafe ConfigurationHandle git_config_snapshot(ConfigurationHandle config) { - NativeMethods.git_config_iterator_free(iter); + git_config* handle; + int res = NativeMethods.git_config_snapshot(out handle, config); + Ensure.ZeroResult(res); + + return new ConfigurationHandle(handle, true); } - public static ConfigurationSafeHandle git_config_snapshot(ConfigurationSafeHandle config) + public static unsafe IntPtr git_config_lock(git_config* config) { - ConfigurationSafeHandle handle; - int res = NativeMethods.git_config_snapshot(out handle, config); + IntPtr txn; + int res = NativeMethods.git_config_lock(out txn, config); Ensure.ZeroResult(res); - return handle; + return txn; + } + + #endregion + + #region git_cred_ + + public static void git_cred_free(IntPtr cred) + { + NativeMethods.git_cred_free(cred); } #endregion #region git_describe_ - public static string git_describe_commit( - RepositorySafeHandle repo, + public static unsafe string git_describe_commit( + RepositoryHandle repo, ObjectId committishId, DescribeOptions options) { - Ensure.ArgumentPositiveInt32(options.MinimumCommitIdAbbreviatedSize, - "options.MinimumCommitIdAbbreviatedSize"); + Ensure.ArgumentPositiveInt32(options.MinimumCommitIdAbbreviatedSize, "options.MinimumCommitIdAbbreviatedSize"); using (var osw = new ObjectSafeWrapper(committishId, repo)) { @@ -641,16 +768,18 @@ public static string git_describe_commit( Version = 1, DescribeStrategy = options.Strategy, MaxCandidatesTags = 10, - OnlyFollowFirstParent = false, + OnlyFollowFirstParent = options.OnlyFollowFirstParent, ShowCommitOidAsFallback = options.UseCommitIdAsFallback, }; - DescribeResultSafeHandle describeHandle = null; + DescribeResultHandle describeHandle = null; try { - int res = NativeMethods.git_describe_commit(out describeHandle, osw.ObjectPtr, ref opts); + 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()) { @@ -678,17 +807,12 @@ public static string git_describe_commit( } } - public static void git_describe_free(IntPtr iter) - { - NativeMethods.git_describe_result_free(iter); - } - #endregion #region git_diff_ - public static void git_diff_blobs( - RepositorySafeHandle repo, + public static unsafe void git_diff_blobs( + RepositoryHandle repo, ObjectId oldBlob, ObjectId newBlob, GitDiffOptions options, @@ -699,53 +823,55 @@ public static void git_diff_blobs( using (var osw1 = new ObjectSafeWrapper(oldBlob, repo, true)) using (var osw2 = new ObjectSafeWrapper(newBlob, repo, true)) { - int res = NativeMethods.git_diff_blobs( - osw1.ObjectPtr, null, osw2.ObjectPtr, null, - options, fileCallback, hunkCallback, lineCallback, IntPtr.Zero); + int res = NativeMethods.git_diff_blobs(osw1.ObjectPtr, + null, + osw2.ObjectPtr, + null, + options, + fileCallback, + null, + hunkCallback, + lineCallback, + IntPtr.Zero); Ensure.ZeroResult(res); } } - public static void git_diff_foreach( - DiffSafeHandle diff, + public static unsafe void git_diff_foreach( + git_diff* diff, NativeMethods.git_diff_file_cb fileCallback, NativeMethods.git_diff_hunk_cb hunkCallback, NativeMethods.git_diff_line_cb lineCallback) { - int res = NativeMethods.git_diff_foreach(diff, fileCallback, hunkCallback, lineCallback, IntPtr.Zero); + int res = NativeMethods.git_diff_foreach(diff, fileCallback, null, hunkCallback, lineCallback, IntPtr.Zero); Ensure.ZeroResult(res); } - public static DiffSafeHandle git_diff_tree_to_index( - RepositorySafeHandle repo, - IndexSafeHandle index, + public static unsafe DiffHandle git_diff_tree_to_index( + RepositoryHandle repo, + IndexHandle index, ObjectId oldTree, GitDiffOptions options) { using (var osw = new ObjectSafeWrapper(oldTree, repo, true)) { - DiffSafeHandle diff; + git_diff* diff; int res = NativeMethods.git_diff_tree_to_index(out diff, repo, osw.ObjectPtr, index, options); Ensure.ZeroResult(res); - return diff; + return new DiffHandle(diff, true); } } - public static void git_diff_free(IntPtr diff) - { - NativeMethods.git_diff_free(diff); - } - - public static void git_diff_merge(DiffSafeHandle onto, DiffSafeHandle from) + public static unsafe void git_diff_merge(DiffHandle onto, DiffHandle from) { int res = NativeMethods.git_diff_merge(onto, from); Ensure.ZeroResult(res); } - public static DiffSafeHandle git_diff_tree_to_tree( - RepositorySafeHandle repo, + public static unsafe DiffHandle git_diff_tree_to_tree( + RepositoryHandle repo, ObjectId oldTree, ObjectId newTree, GitDiffOptions options) @@ -753,62 +879,94 @@ public static DiffSafeHandle git_diff_tree_to_tree( using (var osw1 = new ObjectSafeWrapper(oldTree, repo, true)) using (var osw2 = new ObjectSafeWrapper(newTree, repo, true)) { - DiffSafeHandle diff; + git_diff* diff; int res = NativeMethods.git_diff_tree_to_tree(out diff, repo, osw1.ObjectPtr, osw2.ObjectPtr, options); Ensure.ZeroResult(res); - return diff; + return new DiffHandle(diff, true); } } - public static DiffSafeHandle git_diff_index_to_workdir( - RepositorySafeHandle repo, - IndexSafeHandle index, + public static unsafe DiffHandle git_diff_index_to_workdir( + RepositoryHandle repo, + IndexHandle index, GitDiffOptions options) { - DiffSafeHandle diff; + git_diff* diff; int res = NativeMethods.git_diff_index_to_workdir(out diff, repo, index, options); Ensure.ZeroResult(res); - return diff; + return new DiffHandle(diff, true); } - public static DiffSafeHandle git_diff_tree_to_workdir( - RepositorySafeHandle repo, + public static unsafe DiffHandle git_diff_tree_to_workdir( + RepositoryHandle repo, ObjectId oldTree, GitDiffOptions options) { using (var osw = new ObjectSafeWrapper(oldTree, repo, true)) { - DiffSafeHandle diff; + git_diff* diff; int res = NativeMethods.git_diff_tree_to_workdir(out diff, repo, osw.ObjectPtr, options); Ensure.ZeroResult(res); - return diff; + return new DiffHandle(diff, true); } } - public static void git_diff_find_similar(DiffSafeHandle diff, GitDiffFindOptions options) + public static unsafe void git_diff_find_similar(DiffHandle diff, IntPtr options) { int res = NativeMethods.git_diff_find_similar(diff, options); Ensure.ZeroResult(res); } - public static int git_diff_num_deltas(DiffSafeHandle diff) + public static unsafe void git_diff_find_similar(DiffHandle diff, GitDiffFindOptions options) + { + int res = NativeMethods.git_diff_find_similar(diff, ref options); + Ensure.ZeroResult(res); + } + + public static unsafe int git_diff_num_deltas(DiffHandle diff) { return (int)NativeMethods.git_diff_num_deltas(diff); } - public static GitDiffDelta git_diff_get_delta(DiffSafeHandle diff, int idx) + 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_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) { - return NativeMethods.git_diff_get_delta(diff, (UIntPtr) idx).MarshalAs(false); + var res = NativeMethods.git_filter_source_mode(filterSource); + return (FilterMode)res; } #endregion #region git_graph_ - public static Tuple git_graph_ahead_behind(RepositorySafeHandle repo, Commit first, Commit second) + public static unsafe Tuple git_graph_ahead_behind(RepositoryHandle repo, Commit first, Commit second) { if (first == null || second == null) { @@ -828,7 +986,7 @@ public static GitDiffDelta git_diff_get_delta(DiffSafeHandle diff, int idx) return new Tuple((int)ahead, (int)behind); } - public static bool git_graph_descendant_of(RepositorySafeHandle repo, ObjectId commitId, ObjectId ancestorId) + public static unsafe bool git_graph_descendant_of(RepositoryHandle repo, ObjectId commitId, ObjectId ancestorId) { GitOid oid1 = commitId.Oid; GitOid oid2 = ancestorId.Oid; @@ -843,19 +1001,19 @@ public static bool git_graph_descendant_of(RepositorySafeHandle repo, ObjectId c #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 void git_ignore_clear_internal_rules(RepositorySafeHandle repo) + 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 bool git_ignore_path_is_ignored(RepositorySafeHandle repo, string path) + 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); @@ -868,26 +1026,29 @@ public static bool git_ignore_path_is_ignored(RepositorySafeHandle repo, string #region git_index_ - public static void git_index_add(IndexSafeHandle index, GitIndexEntry entry) + public static unsafe void git_index_add(IndexHandle index, git_index_entry* entry) { int res = NativeMethods.git_index_add(index, entry); Ensure.ZeroResult(res); } - public static void git_index_add_bypath(IndexSafeHandle index, FilePath path) + public static unsafe void git_index_add_bypath(IndexHandle index, FilePath path) { int res = NativeMethods.git_index_add_bypath(index, path); Ensure.ZeroResult(res); } - public static Conflict git_index_conflict_get( - IndexSafeHandle index, - FilePath path) + public static unsafe Conflict git_index_conflict_get( + IndexHandle index, + string path) { - IndexEntrySafeHandle ancestor, ours, theirs; + git_index_entry* ancestor, ours, theirs; - int res = NativeMethods.git_index_conflict_get( - out ancestor, out ours, out theirs, index, path); + int res = NativeMethods.git_index_conflict_get(out ancestor, + out ours, + out theirs, + index, + path); if (res == (int)GitErrorCode.NotFound) { @@ -896,24 +1057,23 @@ public static Conflict git_index_conflict_get( Ensure.ZeroResult(res); - return new Conflict( - IndexEntry.BuildFromPtr(ancestor), - IndexEntry.BuildFromPtr(ours), - IndexEntry.BuildFromPtr(theirs)); + return new Conflict(IndexEntry.BuildFromPtr(ancestor), + IndexEntry.BuildFromPtr(ours), + IndexEntry.BuildFromPtr(theirs)); } - public static ConflictIteratorSafeHandle git_index_conflict_iterator_new(IndexSafeHandle index) + public static unsafe ConflictIteratorHandle git_index_conflict_iterator_new(IndexHandle index) { - ConflictIteratorSafeHandle iter; + git_index_conflict_iterator* iter; int res = NativeMethods.git_index_conflict_iterator_new(out iter, index); Ensure.ZeroResult(res); - return iter; + return new ConflictIteratorHandle(iter, true); } - public static Conflict git_index_conflict_next(ConflictIteratorSafeHandle iterator) + public static unsafe Conflict git_index_conflict_next(ConflictIteratorHandle iterator) { - IndexEntrySafeHandle ancestor, ours, theirs; + git_index_entry* ancestor, ours, theirs; int res = NativeMethods.git_index_conflict_next(out ancestor, out ours, out theirs, iterator); @@ -924,51 +1084,33 @@ public static Conflict git_index_conflict_next(ConflictIteratorSafeHandle iterat Ensure.ZeroResult(res); - using (ancestor) - using (ours) - using (theirs) - { - return new Conflict( - IndexEntry.BuildFromPtr(ancestor), - IndexEntry.BuildFromPtr(ours), - IndexEntry.BuildFromPtr(theirs)); - } - } - - public static void git_index_conflict_iterator_free(IntPtr iterator) - { - NativeMethods.git_index_conflict_iterator_free(iterator); + return new Conflict(IndexEntry.BuildFromPtr(ancestor), + IndexEntry.BuildFromPtr(ours), + IndexEntry.BuildFromPtr(theirs)); } - public static int git_index_entrycount(IndexSafeHandle index) + public static unsafe int git_index_entrycount(IndexHandle index) { return NativeMethods.git_index_entrycount(index) .ConvertToInt(); } - public static StageLevel git_index_entry_stage(IndexEntrySafeHandle index) - { - return (StageLevel)NativeMethods.git_index_entry_stage(index); - } - - public static void git_index_free(IntPtr index) + public static unsafe StageLevel git_index_entry_stage(git_index_entry* entry) { - NativeMethods.git_index_free(index); + return (StageLevel)NativeMethods.git_index_entry_stage(entry); } - public static IndexEntrySafeHandle git_index_get_byindex(IndexSafeHandle index, UIntPtr n) + public static unsafe git_index_entry* git_index_get_byindex(IndexHandle index, UIntPtr n) { return NativeMethods.git_index_get_byindex(index, n); } - public static IndexEntrySafeHandle git_index_get_bypath(IndexSafeHandle index, FilePath path, int stage) + public static unsafe git_index_entry* git_index_get_bypath(IndexHandle index, string path, int stage) { - IndexEntrySafeHandle handle = NativeMethods.git_index_get_bypath(index, path, stage); - - return handle.IsZero ? null : handle; + return NativeMethods.git_index_get_bypath(index, path, stage); } - public static bool git_index_has_conflicts(IndexSafeHandle index) + public static unsafe bool git_index_has_conflicts(IndexHandle index) { int res = NativeMethods.git_index_has_conflicts(index); Ensure.BooleanResult(res); @@ -976,61 +1118,61 @@ public static bool git_index_has_conflicts(IndexSafeHandle index) return res != 0; } - public static int git_index_name_entrycount(IndexSafeHandle index) + public static unsafe int git_index_name_entrycount(IndexHandle index) { return NativeMethods.git_index_name_entrycount(index) .ConvertToInt(); } - public static IndexNameEntrySafeHandle git_index_name_get_byindex(IndexSafeHandle index, UIntPtr n) + public static unsafe git_index_name_entry* git_index_name_get_byindex(IndexHandle index, UIntPtr n) { return NativeMethods.git_index_name_get_byindex(index, n); } - public static IndexSafeHandle git_index_open(FilePath indexpath) + public static unsafe IndexHandle git_index_open(FilePath indexpath) { - IndexSafeHandle handle; + git_index* handle; int res = NativeMethods.git_index_open(out handle, indexpath); Ensure.ZeroResult(res); - return handle; + return new IndexHandle(handle, true); } - public static void git_index_read(IndexSafeHandle index) + public static unsafe void git_index_read(IndexHandle index) { int res = NativeMethods.git_index_read(index, false); Ensure.ZeroResult(res); } - public static void git_index_remove_bypath(IndexSafeHandle index, FilePath path) + public static unsafe void git_index_remove_bypath(IndexHandle index, string path) { int res = NativeMethods.git_index_remove_bypath(index, path); Ensure.ZeroResult(res); } - public static int git_index_reuc_entrycount(IndexSafeHandle index) + public static unsafe int git_index_reuc_entrycount(IndexHandle index) { return NativeMethods.git_index_reuc_entrycount(index) .ConvertToInt(); } - public static IndexReucEntrySafeHandle git_index_reuc_get_byindex(IndexSafeHandle index, UIntPtr n) + public static unsafe git_index_reuc_entry* git_index_reuc_get_byindex(IndexHandle index, UIntPtr n) { return NativeMethods.git_index_reuc_get_byindex(index, n); } - public static IndexReucEntrySafeHandle git_index_reuc_get_bypath(IndexSafeHandle index, string path) + public static unsafe git_index_reuc_entry* git_index_reuc_get_bypath(IndexHandle index, string path) { return NativeMethods.git_index_reuc_get_bypath(index, path); } - public static void git_index_write(IndexSafeHandle index) + public static unsafe void git_index_write(IndexHandle index) { int res = NativeMethods.git_index_write(index); Ensure.ZeroResult(res); } - public static ObjectId git_index_write_tree(IndexSafeHandle index) + public static unsafe ObjectId git_index_write_tree(IndexHandle index) { GitOid treeOid; int res = NativeMethods.git_index_write_tree(out treeOid, index); @@ -1039,7 +1181,7 @@ public static ObjectId git_index_write_tree(IndexSafeHandle index) return treeOid; } - public static ObjectId git_index_write_tree_to(IndexSafeHandle index, RepositorySafeHandle repo) + 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); @@ -1048,13 +1190,13 @@ public static ObjectId git_index_write_tree_to(IndexSafeHandle index, Repository return treeOid; } - public static void git_index_read_fromtree(Index index, GitObjectSafeHandle tree) + 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 void git_index_clear(Index index) + public static unsafe void git_index_clear(Index index) { int res = NativeMethods.git_index_clear(index.Handle); Ensure.ZeroResult(res); @@ -1064,16 +1206,24 @@ public static void git_index_clear(Index index) #region git_merge_ - public static IndexSafeHandle git_merge_commits(RepositorySafeHandle repo, GitObjectSafeHandle ourCommit, GitObjectSafeHandle theirCommit, GitMergeOpts opts) + public static unsafe IndexHandle git_merge_commits(RepositoryHandle repo, ObjectHandle ourCommit, ObjectHandle theirCommit, GitMergeOpts opts, out bool earlyStop) { - IndexSafeHandle index; + git_index* index; int res = NativeMethods.git_merge_commits(out index, repo, ourCommit, theirCommit, ref opts); - Ensure.ZeroResult(res); + if (res == (int)GitErrorCode.MergeConflict) + { + earlyStop = true; + } + else + { + earlyStop = false; + Ensure.ZeroResult(res); + } - return index; + return new IndexHandle(index, true); } - public static ObjectId git_merge_base_many(RepositorySafeHandle repo, GitOid[] commitIds) + 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); @@ -1088,7 +1238,7 @@ public static ObjectId git_merge_base_many(RepositorySafeHandle repo, GitOid[] c return ret; } - public static ObjectId git_merge_base_octopus(RepositorySafeHandle repo, GitOid[] commitIds) + public static unsafe ObjectId git_merge_base_octopus(RepositoryHandle repo, GitOid[] commitIds) { GitOid ret; int res = NativeMethods.git_merge_base_octopus(out ret, repo, commitIds.Length, commitIds); @@ -1103,92 +1253,93 @@ public static ObjectId git_merge_base_octopus(RepositorySafeHandle repo, GitOid[ return ret; } - public static GitAnnotatedCommitHandle git_annotated_commit_from_fetchhead(RepositorySafeHandle repo, string branchName, string remoteUrl, GitOid oid) + public static unsafe AnnotatedCommitHandle git_annotated_commit_from_fetchhead(RepositoryHandle repo, string branchName, string remoteUrl, GitOid oid) { - GitAnnotatedCommitHandle merge_head; + git_annotated_commit* commit; - int res = NativeMethods.git_annotated_commit_from_fetchhead(out merge_head, repo, branchName, remoteUrl, ref oid); + int res = NativeMethods.git_annotated_commit_from_fetchhead(out commit, repo, branchName, remoteUrl, ref oid); Ensure.ZeroResult(res); - return merge_head; + return new AnnotatedCommitHandle(commit, true); } - public static GitAnnotatedCommitHandle git_annotated_commit_lookup(RepositorySafeHandle repo, GitOid oid) + public static unsafe AnnotatedCommitHandle git_annotated_commit_lookup(RepositoryHandle repo, GitOid oid) { - GitAnnotatedCommitHandle their_head; + git_annotated_commit* commit; - int res = NativeMethods.git_annotated_commit_lookup(out their_head, repo, ref oid); + int res = NativeMethods.git_annotated_commit_lookup(out commit, repo, ref oid); Ensure.ZeroResult(res); - return their_head; + return new AnnotatedCommitHandle(commit, true); } - public static GitAnnotatedCommitHandle git_annotated_commit_from_ref(RepositorySafeHandle repo, ReferenceSafeHandle reference) + public static unsafe AnnotatedCommitHandle git_annotated_commit_from_ref(RepositoryHandle repo, ReferenceHandle reference) { - GitAnnotatedCommitHandle their_head; + git_annotated_commit* commit; - int res = NativeMethods.git_annotated_commit_from_ref(out their_head, repo, reference); + int res = NativeMethods.git_annotated_commit_from_ref(out commit, repo, reference); Ensure.ZeroResult(res); - return their_head; + return new AnnotatedCommitHandle(commit, true); } - public static GitAnnotatedCommitHandle git_annotated_commit_from_revspec(RepositorySafeHandle repo, string revspec) + public static unsafe AnnotatedCommitHandle git_annotated_commit_from_revspec(RepositoryHandle repo, string revspec) { - GitAnnotatedCommitHandle their_head; + git_annotated_commit* commit; - int res = NativeMethods.git_annotated_commit_from_revspec(out their_head, repo, revspec); + int res = NativeMethods.git_annotated_commit_from_revspec(out commit, repo, revspec); Ensure.ZeroResult(res); - return their_head; + return new AnnotatedCommitHandle(commit, true); } - public static ObjectId git_annotated_commit_id(GitAnnotatedCommitHandle mergeHead) + public static unsafe ObjectId git_annotated_commit_id(AnnotatedCommitHandle mergeHead) { - return NativeMethods.git_annotated_commit_id(mergeHead).MarshalAsObjectId(); + return ObjectId.BuildFromPtr(NativeMethods.git_annotated_commit_id(mergeHead)); } - public static void git_merge(RepositorySafeHandle repo, GitAnnotatedCommitHandle[] heads, GitMergeOpts mergeOptions, GitCheckoutOpts checkoutOptions) + public static unsafe void git_merge(RepositoryHandle repo, AnnotatedCommitHandle[] heads, GitMergeOpts mergeOptions, GitCheckoutOpts checkoutOptions, out bool earlyStop) { - IntPtr[] their_heads = heads.Select(head => head.DangerousGetHandle()).ToArray(); + IntPtr[] their_heads = heads.Select(head => head.AsIntPtr()).ToArray(); - int res = NativeMethods.git_merge( - repo, - their_heads, - (UIntPtr)their_heads.Length, - ref mergeOptions, - ref checkoutOptions); + int res = NativeMethods.git_merge(repo, + their_heads, + (UIntPtr)their_heads.Length, + ref mergeOptions, + ref checkoutOptions); - Ensure.ZeroResult(res); + if (res == (int)GitErrorCode.MergeConflict) + { + earlyStop = true; + } + else + { + earlyStop = false; + Ensure.ZeroResult(res); + } } - public static void git_merge_analysis( - RepositorySafeHandle repo, - GitAnnotatedCommitHandle[] heads, + 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.DangerousGetHandle()).ToArray(); + IntPtr[] their_heads = heads.Select(head => head.AsIntPtr()).ToArray(); - int res = NativeMethods.git_merge_analysis( - out analysis_out, - out preference_out, - repo, - their_heads, - their_heads.Length); + int res = NativeMethods.git_merge_analysis(out analysis_out, + out preference_out, + repo, + their_heads, + their_heads.Length); Ensure.ZeroResult(res); } - public static void git_annotated_commit_free(IntPtr handle) - { - NativeMethods.git_annotated_commit_free(handle); - } - #endregion #region git_message_ @@ -1208,7 +1359,7 @@ public static string git_message_prettify(string message, char? commentChar) using (var buf = new GitBuf()) { - int res = NativeMethods.git_message_prettify(buf, message, false, (sbyte)comment); + int res = NativeMethods.git_message_prettify(buf, message, true, (sbyte)comment); Ensure.Int32Result(res); return LaxUtf8Marshaler.FromNative(buf.ptr) ?? string.Empty; @@ -1219,8 +1370,8 @@ public static string git_message_prettify(string message, char? commentChar) #region git_note_ - public static ObjectId git_note_create( - RepositorySafeHandle repo, + public static unsafe ObjectId git_note_create( + RepositoryHandle repo, string notes_ref, Signature author, Signature committer, @@ -1228,8 +1379,8 @@ public static ObjectId git_note_create( string note, bool force) { - using (SignatureSafeHandle authorHandle = author.BuildHandle()) - using (SignatureSafeHandle committerHandle = committer.BuildHandle()) + using (SignatureHandle authorHandle = author.BuildHandle()) + using (SignatureHandle committerHandle = committer.BuildHandle()) { GitOid noteOid; GitOid oid = targetId.Oid; @@ -1241,7 +1392,7 @@ public static ObjectId git_note_create( } } - public static string git_note_default_ref(RepositorySafeHandle repo) + public static unsafe string git_note_default_ref(RepositoryHandle repo) { using (var buf = new GitBuf()) { @@ -1252,31 +1403,28 @@ public static string git_note_default_ref(RepositorySafeHandle repo) } } - public static ICollection git_note_foreach(RepositorySafeHandle repo, string notes_ref, Func resultSelector) + public static unsafe ICollection git_note_foreach(RepositoryHandle repo, string notes_ref, Func resultSelector) { - return git_foreach(resultSelector, c => NativeMethods.git_note_foreach(repo, notes_ref, - (ref GitOid x, ref GitOid y, IntPtr p) => c(x, y, p), IntPtr.Zero)); + return git_foreach(resultSelector, c => NativeMethods.git_note_foreach(repo, + notes_ref, + (ref GitOid x, ref GitOid y, IntPtr p) => c(x, y, p), + IntPtr.Zero), GitErrorCode.NotFound); } - public static void git_note_free(IntPtr note) - { - NativeMethods.git_note_free(note); - } - - public static string git_note_message(NoteSafeHandle note) + public static unsafe string git_note_message(NoteHandle note) { return NativeMethods.git_note_message(note); } - public static ObjectId git_note_id(NoteSafeHandle note) + public static unsafe ObjectId git_note_id(NoteHandle note) { - return NativeMethods.git_note_id(note).MarshalAsObjectId(); + return ObjectId.BuildFromPtr(NativeMethods.git_note_id(note)); } - public static NoteSafeHandle git_note_read(RepositorySafeHandle repo, string notes_ref, ObjectId id) + public static unsafe NoteHandle git_note_read(RepositoryHandle repo, string notes_ref, ObjectId id) { GitOid oid = id.Oid; - NoteSafeHandle note; + git_note* note; int res = NativeMethods.git_note_read(out note, repo, notes_ref, ref oid); @@ -1287,13 +1435,13 @@ public static NoteSafeHandle git_note_read(RepositorySafeHandle repo, string not Ensure.ZeroResult(res); - return note; + return new NoteHandle(note, true); } - public static void git_note_remove(RepositorySafeHandle repo, string notes_ref, Signature author, Signature committer, ObjectId targetId) + public static unsafe void git_note_remove(RepositoryHandle repo, string notes_ref, Signature author, Signature committer, ObjectId targetId) { - using (SignatureSafeHandle authorHandle = author.BuildHandle()) - using (SignatureSafeHandle committerHandle = committer.BuildHandle()) + using (SignatureHandle authorHandle = author.BuildHandle()) + using (SignatureHandle committerHandle = committer.BuildHandle()) { GitOid oid = targetId.Oid; @@ -1312,19 +1460,14 @@ public static void git_note_remove(RepositorySafeHandle repo, string notes_ref, #region git_object_ - public static ObjectId git_object_id(GitObjectSafeHandle obj) - { - return NativeMethods.git_object_id(obj).MarshalAsObjectId(); - } - - public static void git_object_free(IntPtr obj) + public static unsafe ObjectId git_object_id(ObjectHandle obj) { - NativeMethods.git_object_free(obj); + return ObjectId.BuildFromPtr(NativeMethods.git_object_id(obj)); } - public static GitObjectSafeHandle git_object_lookup(RepositorySafeHandle repo, ObjectId id, GitObjectType type) + public static unsafe ObjectHandle git_object_lookup(RepositoryHandle repo, ObjectId id, GitObjectType type) { - GitObjectSafeHandle handle; + git_object* handle; GitOid oid = id.Oid; int res = NativeMethods.git_object_lookup(out handle, repo, ref oid, type); @@ -1338,12 +1481,12 @@ public static GitObjectSafeHandle git_object_lookup(RepositorySafeHandle repo, O break; } - return handle; + return new ObjectHandle(handle, true); } - public static GitObjectSafeHandle git_object_peel(RepositorySafeHandle repo, ObjectId id, GitObjectType type, bool throwsIfCanNotPeel) + public static unsafe ObjectHandle git_object_peel(RepositoryHandle repo, ObjectId id, GitObjectType type, bool throwsIfCanNotPeel) { - GitObjectSafeHandle peeled; + git_object* peeled; int res; using (var obj = new ObjectSafeWrapper(id, repo)) @@ -1359,10 +1502,10 @@ public static GitObjectSafeHandle git_object_peel(RepositorySafeHandle repo, Obj } Ensure.ZeroResult(res); - return peeled; + return new ObjectHandle(peeled, true); } - public static string git_object_short_id(RepositorySafeHandle repo, ObjectId id) + public static unsafe string git_object_short_id(RepositoryHandle repo, ObjectId id) { using (var obj = new ObjectSafeWrapper(id, repo)) using (var buf = new GitBuf()) @@ -1374,7 +1517,7 @@ public static string git_object_short_id(RepositorySafeHandle repo, ObjectId id) } } - public static GitObjectType git_object_type(GitObjectSafeHandle obj) + public static unsafe GitObjectType git_object_type(ObjectHandle obj) { return NativeMethods.git_object_type(obj); } @@ -1383,7 +1526,7 @@ public static GitObjectType git_object_type(GitObjectSafeHandle obj) #region git_odb_ - public static void git_odb_add_backend(ObjectDatabaseSafeHandle odb, IntPtr backend, int priority) + public static unsafe void git_odb_add_backend(ObjectDatabaseHandle odb, IntPtr backend, int priority) { Ensure.ZeroResult(NativeMethods.git_odb_add_backend(odb, backend, priority)); } @@ -1394,16 +1537,16 @@ public static IntPtr git_odb_backend_malloc(IntPtr backend, UIntPtr len) if (IntPtr.Zero == toReturn) { - throw new LibGit2SharpException(String.Format(CultureInfo.InvariantCulture, - "Unable to allocate {0} bytes; out of memory", - len), - GitErrorCode.Error, GitErrorCategory.NoMemory); + throw new LibGit2SharpException("Unable to allocate {0} bytes; out of memory", + len, + GitErrorCode.Error, + GitErrorCategory.NoMemory); } return toReturn; } - public static bool git_odb_exists(ObjectDatabaseSafeHandle odb, ObjectId id) + public static unsafe bool git_odb_exists(ObjectDatabaseHandle odb, ObjectId id) { GitOid oid = id.Oid; @@ -1413,7 +1556,7 @@ public static bool git_odb_exists(ObjectDatabaseSafeHandle odb, ObjectId id) return (res == 1); } - public static GitObjectMetadata git_odb_read_header(ObjectDatabaseSafeHandle odb, ObjectId id) + public static unsafe GitObjectMetadata git_odb_read_header(ObjectDatabaseHandle odb, ObjectId id) { GitOid oid = id.Oid; UIntPtr length; @@ -1425,33 +1568,29 @@ public static GitObjectMetadata git_odb_read_header(ObjectDatabaseSafeHandle odb return new GitObjectMetadata((long)length, objectType); } - public static ICollection git_odb_foreach( - ObjectDatabaseSafeHandle odb, - Func resultSelector) + public static unsafe ICollection git_odb_foreach(ObjectDatabaseHandle odb) { - return git_foreach( - resultSelector, - c => NativeMethods.git_odb_foreach( - odb, - (x, p) => c(x, p), - IntPtr.Zero)); + var list = new List(); + + NativeMethods.git_odb_foreach(odb, (p, _data) => + { + list.Add(ObjectId.BuildFromPtr(p)); + return 0; + }, IntPtr.Zero); + + return list; } - public static OdbStreamSafeHandle git_odb_open_wstream(ObjectDatabaseSafeHandle odb, long size, GitObjectType type) + public static unsafe OdbStreamHandle git_odb_open_wstream(ObjectDatabaseHandle odb, long size, GitObjectType type) { - OdbStreamSafeHandle stream; + git_odb_stream* stream; int res = NativeMethods.git_odb_open_wstream(out stream, odb, size, type); Ensure.ZeroResult(res); - return stream; - } - - public static void git_odb_free(IntPtr odb) - { - NativeMethods.git_odb_free(odb); + return new OdbStreamHandle(stream, true); } - public static void git_odb_stream_write(OdbStreamSafeHandle stream, byte[] data, int len) + public static void git_odb_stream_write(OdbStreamHandle stream, byte[] data, int len) { int res; unsafe @@ -1465,7 +1604,7 @@ public static void git_odb_stream_write(OdbStreamSafeHandle stream, byte[] data, Ensure.ZeroResult(res); } - public static ObjectId git_odb_stream_finalize_write(OdbStreamSafeHandle stream) + public static unsafe ObjectId git_odb_stream_finalize_write(OdbStreamHandle stream) { GitOid id; int res = NativeMethods.git_odb_stream_finalize_write(out id, stream); @@ -1474,35 +1613,38 @@ public static ObjectId git_odb_stream_finalize_write(OdbStreamSafeHandle stream) return id; } - public static void git_odb_stream_free(IntPtr stream) + public static unsafe ObjectId git_odb_write(ObjectDatabaseHandle odb, byte[] data, ObjectType type) { - NativeMethods.git_odb_stream_free(stream); - } + GitOid id; + int res; + fixed(byte* p = data) + { + res = NativeMethods.git_odb_write(out id, odb, p, new UIntPtr((ulong)data.LongLength), type.ToGitObjectType()); + } + Ensure.ZeroResult(res); - #endregion + return id; + } - #region git_patch_ +#endregion - public static void git_patch_free(IntPtr patch) - { - NativeMethods.git_patch_free(patch); - } +#region git_patch_ - public static PatchSafeHandle git_patch_from_diff(DiffSafeHandle diff, int idx) + public static unsafe PatchHandle git_patch_from_diff(DiffHandle diff, int idx) { - PatchSafeHandle handle; + git_patch* handle; int res = NativeMethods.git_patch_from_diff(out handle, diff, (UIntPtr)idx); Ensure.ZeroResult(res); - return handle; + return new PatchHandle(handle, true); } - public static void git_patch_print(PatchSafeHandle patch, NativeMethods.git_diff_line_cb printCallback) + 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 Tuple git_patch_line_stats(PatchSafeHandle patch) + 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); @@ -1510,204 +1652,438 @@ public static Tuple git_patch_line_stats(PatchSafeHandle patch) return new Tuple((int)add, (int)del); } - #endregion +#endregion - #region git_reference_ +#region git_packbuilder_ - public static ReferenceSafeHandle git_reference_create(RepositorySafeHandle repo, string name, ObjectId targetId, bool allowOverwrite, - string logMessage) + public static unsafe PackBuilderHandle git_packbuilder_new(RepositoryHandle repo) { - GitOid oid = targetId.Oid; - ReferenceSafeHandle handle; + git_packbuilder* handle; - int res = NativeMethods.git_reference_create(out handle, repo, name, ref oid, allowOverwrite, logMessage); + int res = NativeMethods.git_packbuilder_new(out handle, repo); Ensure.ZeroResult(res); - return handle; + return new PackBuilderHandle(handle, true); } - public static ReferenceSafeHandle git_reference_symbolic_create(RepositorySafeHandle repo, string name, string target, bool allowOverwrite, - string logMessage) + public static unsafe void git_packbuilder_insert(PackBuilderHandle packbuilder, ObjectId targetId, string name) { - ReferenceSafeHandle handle; - int res = NativeMethods.git_reference_symbolic_create(out handle, repo, name, target, allowOverwrite, - logMessage); - Ensure.ZeroResult(res); + GitOid oid = targetId.Oid; - return handle; + int res = NativeMethods.git_packbuilder_insert(packbuilder, ref oid, name); + Ensure.ZeroResult(res); } - public static ICollection git_reference_foreach_glob( - RepositorySafeHandle repo, - string glob, - Func resultSelector) + internal static unsafe void git_packbuilder_insert_commit(PackBuilderHandle packbuilder, ObjectId targetId) { - return git_foreach(resultSelector, c => NativeMethods.git_reference_foreach_glob(repo, glob, (x, p) => c(x, p), IntPtr.Zero)); - } + GitOid oid = targetId.Oid; - public static void git_reference_free(IntPtr reference) - { - NativeMethods.git_reference_free(reference); + int res = NativeMethods.git_packbuilder_insert_commit(packbuilder, ref oid); + Ensure.ZeroResult(res); } - public static bool git_reference_is_valid_name(string refname) + internal static unsafe void git_packbuilder_insert_tree(PackBuilderHandle packbuilder, ObjectId targetId) { - int res = NativeMethods.git_reference_is_valid_name(refname); - Ensure.BooleanResult(res); + GitOid oid = targetId.Oid; - return (res == 1); + int res = NativeMethods.git_packbuilder_insert_tree(packbuilder, ref oid); + Ensure.ZeroResult(res); } - public static IList git_reference_list(RepositorySafeHandle repo) + public static unsafe void git_packbuilder_insert_recur(PackBuilderHandle packbuilder, ObjectId targetId, string name) { - var array = new GitStrArrayNative(); - - try - { - int res = NativeMethods.git_reference_list(out array.Array, repo); - Ensure.ZeroResult(res); + GitOid oid = targetId.Oid; - return array.ReadStrings(); - } - finally - { - array.Dispose(); - } + int res = NativeMethods.git_packbuilder_insert_recur(packbuilder, ref oid, name); + Ensure.ZeroResult(res); } - public static ReferenceSafeHandle git_reference_lookup(RepositorySafeHandle repo, string name, bool shouldThrowIfNotFound) + public static unsafe uint git_packbuilder_set_threads(PackBuilderHandle packbuilder, uint numThreads) { - ReferenceSafeHandle handle; - int res = NativeMethods.git_reference_lookup(out handle, repo, name); - - if (!shouldThrowIfNotFound && res == (int)GitErrorCode.NotFound) - { - return null; - } - - Ensure.ZeroResult(res); - - return handle; + return NativeMethods.git_packbuilder_set_threads(packbuilder, numThreads); } - public static string git_reference_name(ReferenceSafeHandle reference) + public static unsafe void git_packbuilder_write(PackBuilderHandle packbuilder, FilePath path) { - return NativeMethods.git_reference_name(reference); + int res = NativeMethods.git_packbuilder_write(packbuilder, path, 0, IntPtr.Zero, IntPtr.Zero); + Ensure.ZeroResult(res); } - public static void git_reference_remove(RepositorySafeHandle repo, string name) + public static unsafe UIntPtr git_packbuilder_object_count(PackBuilderHandle packbuilder) { - int res = NativeMethods.git_reference_remove(repo, name); - Ensure.ZeroResult(res); + return NativeMethods.git_packbuilder_object_count(packbuilder); } - public static ObjectId git_reference_target(ReferenceSafeHandle reference) + public static unsafe UIntPtr git_packbuilder_written(PackBuilderHandle packbuilder) { - return NativeMethods.git_reference_target(reference).MarshalAsObjectId(); + return NativeMethods.git_packbuilder_written(packbuilder); } +#endregion - public static ReferenceSafeHandle git_reference_rename(ReferenceSafeHandle reference, string newName, bool allowOverwrite, - string logMessage) +#region git_rebase + + public static unsafe RebaseHandle git_rebase_init( + RepositoryHandle repo, + AnnotatedCommitHandle branch, + AnnotatedCommitHandle upstream, + AnnotatedCommitHandle onto, + GitRebaseOptions options) { - ReferenceSafeHandle ref_out; + git_rebase* rebase = null; - int res = NativeMethods.git_reference_rename(out ref_out, reference, newName, allowOverwrite, logMessage); - Ensure.ZeroResult(res); + int result = NativeMethods.git_rebase_init(out rebase, repo, branch, upstream, onto, options); + Ensure.ZeroResult(result); - return ref_out; + return new RebaseHandle(rebase, true); } - public static ReferenceSafeHandle git_reference_set_target(ReferenceSafeHandle reference, ObjectId id, string logMessage) + public static unsafe RebaseHandle git_rebase_open(RepositoryHandle repo, GitRebaseOptions options) { - GitOid oid = id.Oid; - ReferenceSafeHandle ref_out; + git_rebase* rebase = null; - int res = NativeMethods.git_reference_set_target(out ref_out, reference, ref oid, logMessage); - Ensure.ZeroResult(res); + int result = NativeMethods.git_rebase_open(out rebase, repo, options); + Ensure.ZeroResult(result); - return ref_out; + return new RebaseHandle(rebase, true); } - public static ReferenceSafeHandle git_reference_symbolic_set_target(ReferenceSafeHandle reference, string target, string logMessage) + public static unsafe long git_rebase_operation_entrycount(RebaseHandle rebase) { - ReferenceSafeHandle ref_out; + return NativeMethods.git_rebase_operation_entrycount(rebase).ConvertToLong(); + } - int res = NativeMethods.git_reference_symbolic_set_target(out ref_out, reference, target, logMessage); + 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 ref_out; + return new ReferenceHandle(handle, true); } - public static string git_reference_symbolic_target(ReferenceSafeHandle reference) + public static unsafe ReferenceHandle git_reference_symbolic_create( + RepositoryHandle repo, + string name, + string target, + bool allowOverwrite, + string logMessage) { - return NativeMethods.git_reference_symbolic_target(reference); + 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 GitReferenceType git_reference_type(ReferenceSafeHandle reference) + public static unsafe ICollection git_reference_foreach_glob( + RepositoryHandle repo, + string glob, + Func resultSelector) { - return NativeMethods.git_reference_type(reference); + return git_foreach(resultSelector, c => NativeMethods.git_reference_foreach_glob(repo, glob, (x, p) => c(x, p), IntPtr.Zero)); } - public static void git_reference_ensure_log(RepositorySafeHandle repo, string refname) + public static bool git_reference_is_valid_name(string refname) { - int res = NativeMethods.git_reference_ensure_log(repo, 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); } - #endregion + 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); - #region git_reflog_ + 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 void git_reflog_free(IntPtr reflog) + public static unsafe void git_reference_ensure_log(RepositoryHandle repo, string refname) { - NativeMethods.git_reflog_free(reflog); + int res = NativeMethods.git_reference_ensure_log(repo, refname); + Ensure.ZeroResult(res); } - public static ReflogSafeHandle git_reflog_read(RepositorySafeHandle repo, string canonicalName) +#endregion + +#region git_reflog_ + + public static unsafe ReflogHandle git_reflog_read(RepositoryHandle repo, string canonicalName) { - ReflogSafeHandle reflog_out; + git_reflog* reflog_out; int res = NativeMethods.git_reflog_read(out reflog_out, repo, canonicalName); Ensure.ZeroResult(res); - return reflog_out; + return new ReflogHandle(reflog_out, true); } - public static int git_reflog_entrycount(ReflogSafeHandle reflog) + public static unsafe int git_reflog_entrycount(ReflogHandle reflog) { return (int)NativeMethods.git_reflog_entrycount(reflog); } - public static ReflogEntrySafeHandle git_reflog_entry_byindex(ReflogSafeHandle reflog, int idx) + public static unsafe git_reflog_entry* git_reflog_entry_byindex(ReflogHandle reflog, int idx) { return NativeMethods.git_reflog_entry_byindex(reflog, (UIntPtr)idx); } - public static ObjectId git_reflog_entry_id_old(SafeHandle entry) + public static unsafe ObjectId git_reflog_entry_id_old(git_reflog_entry* entry) { - return NativeMethods.git_reflog_entry_id_old(entry).MarshalAsObjectId(); + return ObjectId.BuildFromPtr(NativeMethods.git_reflog_entry_id_old(entry)); } - public static ObjectId git_reflog_entry_id_new(SafeHandle entry) + public static unsafe ObjectId git_reflog_entry_id_new(git_reflog_entry* entry) { - return NativeMethods.git_reflog_entry_id_new(entry).MarshalAsObjectId(); + return ObjectId.BuildFromPtr(NativeMethods.git_reflog_entry_id_new(entry)); } - public static Signature git_reflog_entry_committer(SafeHandle entry) + public static unsafe Signature git_reflog_entry_committer(git_reflog_entry* entry) { return new Signature(NativeMethods.git_reflog_entry_committer(entry)); } - public static string git_reflog_entry_message(SafeHandle entry) + public static unsafe string git_reflog_entry_message(git_reflog_entry* entry) { return NativeMethods.git_reflog_entry_message(entry); } - #endregion +#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); - #region git_refspec + return LaxUtf8Marshaler.FromNative(buf.ptr) ?? string.Empty; + } + } - public static string git_refspec_rtransform(GitRefSpecHandle refSpecPtr, string name) + public static unsafe string git_refspec_rtransform(IntPtr refSpecPtr, string name) { using (var buf = new GitBuf()) { @@ -1718,74 +2094,93 @@ public static string git_refspec_rtransform(GitRefSpecHandle refSpecPtr, string } } - public static string git_refspec_string(GitRefSpecHandle refSpec) + public static unsafe string git_refspec_string(IntPtr refspec) { - return NativeMethods.git_refspec_string(refSpec); + return NativeMethods.git_refspec_string(refspec); } - public static string git_refspec_src(GitRefSpecHandle refSpec) + public static unsafe string git_refspec_src(IntPtr refSpec) { return NativeMethods.git_refspec_src(refSpec); } - public static string git_refspec_dst(GitRefSpecHandle refSpec) + public static unsafe string git_refspec_dst(IntPtr refSpec) { return NativeMethods.git_refspec_dst(refSpec); } - public static RefSpecDirection git_refspec_direction(GitRefSpecHandle refSpec) + public static unsafe RefSpecDirection git_refspec_direction(IntPtr refSpec) { return NativeMethods.git_refspec_direction(refSpec); } - public static bool git_refspec_force(GitRefSpecHandle refSpec) + public static unsafe bool git_refspec_force(IntPtr refSpec) { return NativeMethods.git_refspec_force(refSpec); } - #endregion + 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_ +#region git_remote_ - public static TagFetchMode git_remote_autotag(RemoteSafeHandle remote) + public static unsafe TagFetchMode git_remote_autotag(RemoteHandle remote) { - return (TagFetchMode) NativeMethods.git_remote_autotag(remote); + return (TagFetchMode)NativeMethods.git_remote_autotag(remote); } - public static RemoteSafeHandle git_remote_create(RepositorySafeHandle repo, string name, string url) + public static unsafe RemoteHandle git_remote_create(RepositoryHandle repo, string name, string url) { - RemoteSafeHandle handle; + git_remote* handle; int res = NativeMethods.git_remote_create(out handle, repo, name, url); Ensure.ZeroResult(res); - return handle; + return new RemoteHandle(handle, true); } - public static RemoteSafeHandle git_remote_create_with_fetchspec(RepositorySafeHandle repo, string name, string url, string refspec) + public static unsafe RemoteHandle git_remote_create_with_fetchspec(RepositoryHandle repo, string name, string url, string refspec) { - RemoteSafeHandle handle; + git_remote* handle; int res = NativeMethods.git_remote_create_with_fetchspec(out handle, repo, name, url, refspec); Ensure.ZeroResult(res); - return handle; + return new RemoteHandle(handle, true); } - public static RemoteSafeHandle git_remote_create_anonymous(RepositorySafeHandle repo, string url) + public static unsafe RemoteHandle git_remote_create_anonymous(RepositoryHandle repo, string url) { - RemoteSafeHandle handle; + git_remote* handle; int res = NativeMethods.git_remote_create_anonymous(out handle, repo, url); Ensure.ZeroResult(res); - return handle; + return new RemoteHandle(handle, true); } - public static void git_remote_connect(RemoteSafeHandle remote, GitDirection direction, ref GitRemoteCallbacks remoteCallbacks) + public static unsafe void git_remote_connect(RemoteHandle remote, GitDirection direction, ref GitRemoteCallbacks remoteCallbacks, ref GitProxyOptions proxyOptions) { - int res = NativeMethods.git_remote_connect(remote, direction, ref remoteCallbacks); - Ensure.ZeroResult(res); + 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(); + } } - public static void git_remote_delete(RepositorySafeHandle repo, string name) + public static unsafe void git_remote_delete(RepositoryHandle repo, string name) { int res = NativeMethods.git_remote_delete(repo, name); @@ -1797,17 +2192,17 @@ public static void git_remote_delete(RepositorySafeHandle repo, string name) Ensure.ZeroResult(res); } - public static GitRefSpecHandle git_remote_get_refspec(RemoteSafeHandle remote, int n) + public static unsafe git_refspec* git_remote_get_refspec(RemoteHandle remote, int n) { return NativeMethods.git_remote_get_refspec(remote, (UIntPtr)n); } - public static int git_remote_refspec_count(RemoteSafeHandle remote) + public static unsafe int git_remote_refspec_count(RemoteHandle remote) { return (int)NativeMethods.git_remote_refspec_count(remote); } - public static IList git_remote_get_fetch_refspecs(RemoteSafeHandle remote) + public static unsafe IList git_remote_get_fetch_refspecs(RemoteHandle remote) { var array = new GitStrArrayNative(); @@ -1824,7 +2219,7 @@ public static IList git_remote_get_fetch_refspecs(RemoteSafeHandle remot } } - public static IList git_remote_get_push_refspecs(RemoteSafeHandle remote) + public static unsafe IList git_remote_get_push_refspecs(RemoteHandle remote) { var array = new GitStrArrayNative(); @@ -1841,7 +2236,7 @@ public static IList git_remote_get_push_refspecs(RemoteSafeHandle remote } } - public static void git_remote_push(RemoteSafeHandle remote, IEnumerable refSpecs, GitPushOptions opts) + public static unsafe void git_remote_push(RemoteHandle remote, IEnumerable refSpecs, GitPushOptions opts) { var array = new GitStrArrayManaged(); @@ -1858,32 +2253,32 @@ public static void git_remote_push(RemoteSafeHandle remote, IEnumerable } } - public static void git_remote_set_url(RepositorySafeHandle repo, string remote, string url) + public static unsafe void git_remote_set_url(RepositoryHandle repo, string remote, string url) { int res = NativeMethods.git_remote_set_url(repo, remote, url); Ensure.ZeroResult(res); } - public static void git_remote_add_fetch(RepositorySafeHandle repo, string remote, string url) + public static unsafe void git_remote_add_fetch(RepositoryHandle repo, string remote, string url) { int res = NativeMethods.git_remote_add_fetch(repo, remote, url); Ensure.ZeroResult(res); } - public static void git_remote_set_pushurl(RepositorySafeHandle repo, string remote, string url) + public static unsafe void git_remote_set_pushurl(RepositoryHandle repo, string remote, string url) { int res = NativeMethods.git_remote_set_pushurl(repo, remote, url); Ensure.ZeroResult(res); } - public static void git_remote_add_push(RepositorySafeHandle repo, string remote, string url) + public static unsafe void git_remote_add_push(RepositoryHandle repo, string remote, string url) { int res = NativeMethods.git_remote_add_push(repo, remote, url); Ensure.ZeroResult(res); } - public static void git_remote_fetch( - RemoteSafeHandle remote, IEnumerable refSpecs, + public static unsafe void git_remote_fetch( + RemoteHandle remote, IEnumerable refSpecs, GitFetchOptions fetchOptions, string logMessage) { var array = new GitStrArrayManaged(); @@ -1901,11 +2296,6 @@ public static void git_remote_fetch( } } - public static void git_remote_free(IntPtr remote) - { - NativeMethods.git_remote_free(remote); - } - public static bool git_remote_is_valid_name(string refname) { int res = NativeMethods.git_remote_is_valid_name(refname); @@ -1914,7 +2304,7 @@ public static bool git_remote_is_valid_name(string refname) return (res == 1); } - public static IList git_remote_list(RepositorySafeHandle repo) + public static unsafe IList git_remote_list(RepositoryHandle repo) { var array = new GitStrArrayNative(); @@ -1931,47 +2321,62 @@ public static IList git_remote_list(RepositorySafeHandle repo) } } - public static IEnumerable git_remote_ls(Repository repository, RemoteSafeHandle remote) + public static unsafe IEnumerable git_remote_ls(Repository repository, RemoteHandle remote) { - IntPtr heads; + git_remote_head** heads; UIntPtr count; int res = NativeMethods.git_remote_ls(out heads, out count, remote); Ensure.ZeroResult(res); - var intCount = (int)count.ToUInt32(); - - if (intCount < 0) - { - throw new OverflowException(); - } - - var refs = new List(); - IntPtr currentHead = heads; + var intCount = checked(count.ToUInt32()); + var directRefs = new Dictionary(); + var symRefs = new Dictionary(); for (int i = 0; i < intCount; i++) { - var remoteHead = Marshal.ReadIntPtr(currentHead).MarshalAs(); + git_remote_head* currentHead = heads[i]; + string name = LaxUtf8Marshaler.FromNative(currentHead->Name); + string symRefTarget = LaxUtf8Marshaler.FromNative(currentHead->SymrefTarget); // The name pointer should never be null - if it is, // this indicates a bug somewhere (libgit2, server, etc). - if (remoteHead.NamePtr == IntPtr.Zero) + if (string.IsNullOrEmpty(name)) { throw new InvalidOperationException("Not expecting null value for reference name."); } - string name = LaxUtf8Marshaler.FromNative(remoteHead.NamePtr); - refs.Add(new DirectReference(name, repository, remoteHead.Oid)); + if (!string.IsNullOrEmpty(symRefTarget)) + { + symRefs.Add(name, symRefTarget); + } + else + { + directRefs.Add(name, new DirectReference(name, repository, new ObjectId(currentHead->Oid.Id))); + } + } - currentHead = IntPtr.Add(currentHead, IntPtr.Size); + for (int i = 0; i < symRefs.Count; i++) + { + var symRef = symRefs.ElementAt(i); + + if (!directRefs.ContainsKey(symRef.Value)) + { + throw new InvalidOperationException("Symbolic reference target not found in direct reference results."); + } + + directRefs.Add(symRef.Key, new SymbolicReference(repository, symRef.Key, symRef.Value, directRefs[symRef.Value])); } + var refs = directRefs.Values.ToList(); + refs.Sort((r1, r2) => String.CompareOrdinal(r1.CanonicalName, r2.CanonicalName)); + return refs; } - public static RemoteSafeHandle git_remote_lookup(RepositorySafeHandle repo, string name, bool throwsIfNotFound) + public static unsafe RemoteHandle git_remote_lookup(RepositoryHandle repo, string name, bool throwsIfNotFound) { - RemoteSafeHandle handle; + git_remote* handle; int res = NativeMethods.git_remote_lookup(out handle, repo, name); if (res == (int)GitErrorCode.NotFound && !throwsIfNotFound) @@ -1980,15 +2385,15 @@ public static RemoteSafeHandle git_remote_lookup(RepositorySafeHandle repo, stri } Ensure.ZeroResult(res); - return handle; + return new RemoteHandle(handle, true); } - public static string git_remote_name(RemoteSafeHandle remote) + public static unsafe string git_remote_name(RemoteHandle remote) { return NativeMethods.git_remote_name(remote); } - public static void git_remote_rename(RepositorySafeHandle repo, string name, string new_name, RemoteRenameFailureHandler callback) + public static unsafe void git_remote_rename(RepositoryHandle repo, string name, string new_name, RemoteRenameFailureHandler callback) { if (callback == null) { @@ -1999,16 +2404,14 @@ public static void git_remote_rename(RepositorySafeHandle repo, string name, str try { - int res = NativeMethods.git_remote_rename( - ref array.Array, - repo, - name, - new_name); + int res = NativeMethods.git_remote_rename(ref array.Array, + repo, + name, + new_name); if (res == (int)GitErrorCode.NotFound) { - throw new NotFoundException( - string.Format("Remote '{0}' does not exist and cannot be renamed.", name)); + throw new NotFoundException("Remote '{0}' does not exist and cannot be renamed.", name); } Ensure.ZeroResult(res); @@ -2024,110 +2427,103 @@ public static void git_remote_rename(RepositorySafeHandle repo, string name, str } } - public static void git_remote_set_autotag(RepositorySafeHandle repo, string remote, TagFetchMode value) + public static unsafe void git_remote_set_autotag(RepositoryHandle repo, string remote, TagFetchMode value) { NativeMethods.git_remote_set_autotag(repo, remote, value); } - public static string git_remote_url(RemoteSafeHandle remote) + public static unsafe string git_remote_url(RemoteHandle remote) { return NativeMethods.git_remote_url(remote); } - public static string git_remote_pushurl(RemoteSafeHandle remote) + public static unsafe string git_remote_pushurl(RemoteHandle remote) { return NativeMethods.git_remote_pushurl(remote); } - #endregion +#endregion - #region git_repository_ +#region git_repository_ public static FilePath git_repository_discover(FilePath start_path) { return ConvertPath(buf => NativeMethods.git_repository_discover(buf, start_path, false, null)); } - public static bool git_repository_head_detached(RepositorySafeHandle repo) + public static unsafe bool git_repository_head_detached(RepositoryHandle repo) { return RepositoryStateChecker(repo, NativeMethods.git_repository_head_detached); } - public static ICollection git_repository_fetchhead_foreach( - RepositorySafeHandle repo, + public static unsafe ICollection git_repository_fetchhead_foreach( + RepositoryHandle repo, Func resultSelector) { - return git_foreach( - resultSelector, - c => NativeMethods.git_repository_fetchhead_foreach( - repo, - (IntPtr w, IntPtr x, ref GitOid y, bool z, IntPtr p) - => c(LaxUtf8Marshaler.FromNative(w), LaxUtf8Marshaler.FromNative(x), y, z, p), IntPtr.Zero), - GitErrorCode.NotFound); + return git_foreach(resultSelector, + c => NativeMethods.git_repository_fetchhead_foreach(repo, + (IntPtr w, IntPtr x, ref GitOid y, bool z, IntPtr p) + => c(LaxUtf8Marshaler.FromNative(w), LaxUtf8Marshaler.FromNative(x), y, z, p), + IntPtr.Zero), + GitErrorCode.NotFound); } - public static void git_repository_free(IntPtr repo) - { - NativeMethods.git_repository_free(repo); - } - - public static bool git_repository_head_unborn(RepositorySafeHandle repo) + public static bool git_repository_head_unborn(RepositoryHandle repo) { return RepositoryStateChecker(repo, NativeMethods.git_repository_head_unborn); } - public static IndexSafeHandle git_repository_index(RepositorySafeHandle repo) + public static unsafe IndexHandle git_repository_index(RepositoryHandle repo) { - IndexSafeHandle handle; + git_index* handle; int res = NativeMethods.git_repository_index(out handle, repo); Ensure.ZeroResult(res); - return handle; + return new IndexHandle(handle, true); } - public static RepositorySafeHandle git_repository_init_ext( + public static unsafe RepositoryHandle git_repository_init_ext( FilePath workdirPath, FilePath gitdirPath, bool isBare) { using (var opts = GitRepositoryInitOptions.BuildFrom(workdirPath, isBare)) { - RepositorySafeHandle repo; + git_repository* repo; int res = NativeMethods.git_repository_init_ext(out repo, gitdirPath, opts); Ensure.ZeroResult(res); - return repo; + return new RepositoryHandle(repo, true); } } - public static bool git_repository_is_bare(RepositorySafeHandle repo) + public static unsafe bool git_repository_is_bare(RepositoryHandle repo) { return RepositoryStateChecker(repo, NativeMethods.git_repository_is_bare); } - public static bool git_repository_is_shallow(RepositorySafeHandle repo) + public static unsafe bool git_repository_is_shallow(RepositoryHandle repo) { return RepositoryStateChecker(repo, NativeMethods.git_repository_is_shallow); } - public static void git_repository_state_cleanup(RepositorySafeHandle repo) + public static unsafe void git_repository_state_cleanup(RepositoryHandle repo) { int res = NativeMethods.git_repository_state_cleanup(repo); Ensure.ZeroResult(res); } - public static ICollection git_repository_mergehead_foreach( - RepositorySafeHandle repo, + public static unsafe ICollection git_repository_mergehead_foreach( + RepositoryHandle repo, Func resultSelector) { - return git_foreach( - resultSelector, - c => NativeMethods.git_repository_mergehead_foreach( - repo, (ref GitOid x, IntPtr p) => c(x, p), IntPtr.Zero), - GitErrorCode.NotFound); + return git_foreach(resultSelector, + c => NativeMethods.git_repository_mergehead_foreach(repo, + (ref GitOid x, IntPtr p) => c(x, p), IntPtr.Zero), + GitErrorCode.NotFound); } - public static string git_repository_message(RepositorySafeHandle repo) + public static unsafe string git_repository_message(RepositoryHandle repo) { using (var buf = new GitBuf()) { @@ -2142,113 +2538,127 @@ public static string git_repository_message(RepositorySafeHandle repo) } } - public static ObjectDatabaseSafeHandle git_repository_odb(RepositorySafeHandle repo) + public static unsafe ObjectDatabaseHandle git_repository_odb(RepositoryHandle repo) { - ObjectDatabaseSafeHandle handle; + git_odb* handle; int res = NativeMethods.git_repository_odb(out handle, repo); Ensure.ZeroResult(res); - return handle; + return new ObjectDatabaseHandle(handle, true); } - public static RepositorySafeHandle git_repository_open(string path) + public static unsafe RepositoryHandle git_repository_open(string path) { - RepositorySafeHandle repo; + git_repository* repo; int res = NativeMethods.git_repository_open(out repo, path); if (res == (int)GitErrorCode.NotFound) { - throw new RepositoryNotFoundException(String.Format(CultureInfo.InvariantCulture, - "Path '{0}' doesn't point at a valid Git repository or workdir.", path)); + throw new RepositoryNotFoundException("Path '{0}' doesn't point at a valid Git repository or workdir.", + path); } Ensure.ZeroResult(res); - return repo; + return new RepositoryHandle(repo, true); + } + + public static unsafe RepositoryHandle git_repository_new() + { + git_repository* repo; + int res = NativeMethods.git_repository_new(out repo); + + Ensure.ZeroResult(res); + + return new RepositoryHandle(repo, true); } - public static void git_repository_open_ext(string path, RepositoryOpenFlags flags, string ceilingDirs) + public static unsafe void git_repository_open_ext(string path, RepositoryOpenFlags flags, string ceilingDirs) { int res; + git_repository *repo; - using (var repo = new NullRepositorySafeHandle()) - { - res = NativeMethods.git_repository_open_ext(repo, path, flags, ceilingDirs); - } + res = NativeMethods.git_repository_open_ext(out repo, path, flags, ceilingDirs); + NativeMethods.git_repository_free(repo); 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)); + throw new RepositoryNotFoundException("Path '{0}' doesn't point at a valid Git repository or workdir.", + path); } Ensure.ZeroResult(res); } - public static FilePath git_repository_path(RepositorySafeHandle repo) + public static unsafe FilePath git_repository_path(RepositoryHandle repo) { return NativeMethods.git_repository_path(repo); } - public static void git_repository_set_config(RepositorySafeHandle repo, ConfigurationSafeHandle config) + public static unsafe void git_repository_set_config(RepositoryHandle repo, ConfigurationHandle config) { NativeMethods.git_repository_set_config(repo, config); } - public static void git_repository_set_ident(RepositorySafeHandle repo, string name, string email) + public static unsafe void git_repository_set_ident(RepositoryHandle repo, string name, string email) { int res = NativeMethods.git_repository_set_ident(repo, name, email); Ensure.ZeroResult(res); } - public static void git_repository_set_index(RepositorySafeHandle repo, IndexSafeHandle index) + public static unsafe void git_repository_set_index(RepositoryHandle repo, IndexHandle index) { NativeMethods.git_repository_set_index(repo, index); } - public static void git_repository_set_workdir(RepositorySafeHandle repo, FilePath workdir) + public static unsafe void git_repository_set_workdir(RepositoryHandle repo, FilePath workdir) { int res = NativeMethods.git_repository_set_workdir(repo, workdir, false); Ensure.ZeroResult(res); } - public static CurrentOperation git_repository_state(RepositorySafeHandle repo) + public static unsafe CurrentOperation git_repository_state(RepositoryHandle repo) { int res = NativeMethods.git_repository_state(repo); Ensure.Int32Result(res); return (CurrentOperation)res; } - public static FilePath git_repository_workdir(RepositorySafeHandle repo) + public static unsafe FilePath git_repository_workdir(RepositoryHandle repo) + { + return NativeMethods.git_repository_workdir(repo); + } + + public static FilePath git_repository_workdir(IntPtr repo) { return NativeMethods.git_repository_workdir(repo); } - public static void git_repository_set_head_detached(RepositorySafeHandle repo, ObjectId commitish) + public static unsafe void git_repository_set_head_detached(RepositoryHandle repo, ObjectId commitish) { GitOid oid = commitish.Oid; int res = NativeMethods.git_repository_set_head_detached(repo, ref oid); Ensure.ZeroResult(res); } - public static void git_repository_set_head_detached_from_annotated(RepositorySafeHandle repo, GitAnnotatedCommitHandle commit) + public static unsafe void git_repository_set_head_detached_from_annotated(RepositoryHandle repo, AnnotatedCommitHandle commit) { int res = NativeMethods.git_repository_set_head_detached_from_annotated(repo, commit); Ensure.ZeroResult(res); } - public static void git_repository_set_head(RepositorySafeHandle repo, string refname) + public static unsafe void git_repository_set_head(RepositoryHandle repo, string refname) { int res = NativeMethods.git_repository_set_head(repo, refname); Ensure.ZeroResult(res); } - #endregion +#endregion - #region git_reset_ +#region git_reset_ - public static void git_reset( - RepositorySafeHandle repo, + public static unsafe void git_reset( + RepositoryHandle repo, ObjectId committishId, ResetMode resetKind, ref GitCheckoutOpts checkoutOptions) @@ -2260,12 +2670,12 @@ public static void git_reset( } } - #endregion +#endregion - #region git_revert_ +#region git_revert_ - public static void git_revert( - RepositorySafeHandle repo, + public static unsafe void git_revert( + RepositoryHandle repo, ObjectId commit, GitRevertOpts opts) { @@ -2276,14 +2686,29 @@ public static void git_revert( } } + internal static unsafe IndexHandle git_revert_commit(RepositoryHandle repo, ObjectHandle revertCommit, ObjectHandle ourCommit, uint mainline, GitMergeOpts opts, out bool earlyStop) + { + git_index* index; + int res = NativeMethods.git_revert_commit(out index, repo, revertCommit, ourCommit, mainline, ref opts); + if (res == (int)GitErrorCode.MergeConflict) + { + earlyStop = true; + } + else + { + earlyStop = false; + Ensure.ZeroResult(res); + } + return new IndexHandle(index, true); + } #endregion #region git_revparse_ - public static Tuple git_revparse_ext(RepositorySafeHandle repo, string objectish) + public static unsafe Tuple git_revparse_ext(RepositoryHandle repo, string objectish) { - GitObjectSafeHandle obj; - ReferenceSafeHandle reference; + git_object* obj; + git_reference* reference; int res = NativeMethods.git_revparse_ext(out obj, out reference, repo, objectish); switch (res) @@ -2292,18 +2717,18 @@ public static Tuple git_revparse_ext(R return null; case (int)GitErrorCode.Ambiguous: - throw new AmbiguousSpecificationException(string.Format(CultureInfo.InvariantCulture, - "Provided abbreviated ObjectId '{0}' is too short.", objectish)); + throw new AmbiguousSpecificationException("Provided abbreviated ObjectId '{0}' is too short.", + objectish); default: Ensure.ZeroResult(res); break; } - return new Tuple(obj, reference); + return new Tuple(new ObjectHandle(obj, true), new ReferenceHandle(reference, true)); } - public static GitObjectSafeHandle git_revparse_single(RepositorySafeHandle repo, string objectish) + public static ObjectHandle git_revparse_single(RepositoryHandle repo, string objectish) { var handles = git_revparse_ext(repo, objectish); @@ -2317,32 +2742,27 @@ public static GitObjectSafeHandle git_revparse_single(RepositorySafeHandle repo, return handles.Item1; } - #endregion +#endregion - #region git_revwalk_ +#region git_revwalk_ - public static void git_revwalk_free(IntPtr walker) - { - NativeMethods.git_revwalk_free(walker); - } - - public static void git_revwalk_hide(RevWalkerSafeHandle walker, ObjectId commit_id) + public static unsafe void git_revwalk_hide(RevWalkerHandle walker, ObjectId commit_id) { GitOid oid = commit_id.Oid; int res = NativeMethods.git_revwalk_hide(walker, ref oid); Ensure.ZeroResult(res); } - public static RevWalkerSafeHandle git_revwalk_new(RepositorySafeHandle repo) + public static unsafe RevWalkerHandle git_revwalk_new(RepositoryHandle repo) { - RevWalkerSafeHandle handle; + git_revwalk* handle; int res = NativeMethods.git_revwalk_new(out handle, repo); Ensure.ZeroResult(res); - return handle; + return new RevWalkerHandle(handle, true); } - public static ObjectId git_revwalk_next(RevWalkerSafeHandle walker) + public static unsafe ObjectId git_revwalk_next(RevWalkerHandle walker) { GitOid ret; int res = NativeMethods.git_revwalk_next(out ret, walker); @@ -2357,68 +2777,71 @@ public static ObjectId git_revwalk_next(RevWalkerSafeHandle walker) return ret; } - public static void git_revwalk_push(RevWalkerSafeHandle walker, ObjectId id) + public static unsafe void git_revwalk_push(RevWalkerHandle walker, ObjectId id) { GitOid oid = id.Oid; int res = NativeMethods.git_revwalk_push(walker, ref oid); Ensure.ZeroResult(res); } - public static void git_revwalk_reset(RevWalkerSafeHandle walker) + public static unsafe void git_revwalk_reset(RevWalkerHandle walker) { NativeMethods.git_revwalk_reset(walker); } - public static void git_revwalk_sorting(RevWalkerSafeHandle walker, CommitSortStrategies options) + public static unsafe void git_revwalk_sorting(RevWalkerHandle walker, CommitSortStrategies options) { NativeMethods.git_revwalk_sorting(walker, options); } - public static void git_revwalk_simplify_first_parent(RevWalkerSafeHandle walker) + public static unsafe void git_revwalk_simplify_first_parent(RevWalkerHandle walker) { NativeMethods.git_revwalk_simplify_first_parent(walker); } - #endregion +#endregion - #region git_signature_ +#region git_signature_ - public static void git_signature_free(IntPtr signature) + public static unsafe SignatureHandle git_signature_new(string name, string email, DateTimeOffset when) { - NativeMethods.git_signature_free(signature); - } + git_signature* ptr; - public static SignatureSafeHandle git_signature_new(string name, string email, DateTimeOffset when) - { - SignatureSafeHandle handle; + int res = NativeMethods.git_signature_new(out ptr, name, email, when.ToUnixTimeSeconds(), + (int)when.Offset.TotalMinutes); + Ensure.ZeroResult(res); - int res = NativeMethods.git_signature_new(out handle, name, email, when.ToSecondsSinceEpoch(), - (int)when.Offset.TotalMinutes); + return new SignatureHandle(ptr, true); + } + public static unsafe SignatureHandle git_signature_now(string name, string email) + { + git_signature* ptr; + int res = NativeMethods.git_signature_now(out ptr, name, email); Ensure.ZeroResult(res); - return handle; + return new SignatureHandle(ptr, true); } - public static IntPtr git_signature_dup(IntPtr sig) + public static unsafe git_signature* git_signature_dup(git_signature* sig) { - IntPtr handle; + git_signature* handle; int res = NativeMethods.git_signature_dup(out handle, sig); Ensure.ZeroResult(res); return handle; } - #endregion +#endregion - #region git_stash_ +#region git_stash_ - public static ObjectId git_stash_save( - RepositorySafeHandle repo, + public static unsafe ObjectId git_stash_save( + RepositoryHandle repo, Signature stasher, string prettifiedMessage, StashModifiers options) { - using (SignatureSafeHandle sigHandle = stasher.BuildHandle()) + using (SignatureHandle sigHandle = stasher.BuildHandle()) { GitOid stashOid; @@ -2435,28 +2858,66 @@ public static ObjectId git_stash_save( } } - public static ICollection git_stash_foreach( - RepositorySafeHandle repo, + public static unsafe ICollection git_stash_foreach( + RepositoryHandle repo, Func resultSelector) { - return git_foreach( - resultSelector, - c => NativeMethods.git_stash_foreach( - repo, (UIntPtr i, IntPtr m, ref GitOid x, IntPtr p) => c((int)i, m, x, p), IntPtr.Zero), - GitErrorCode.NotFound); + return git_foreach(resultSelector, + c => NativeMethods.git_stash_foreach(repo, + (UIntPtr i, IntPtr m, ref GitOid x, IntPtr p) + => c((int)i, m, x, p), + IntPtr.Zero), + GitErrorCode.NotFound); } - public static void git_stash_drop(RepositorySafeHandle repo, int index) + public static unsafe void git_stash_drop(RepositoryHandle repo, int index) { int res = NativeMethods.git_stash_drop(repo, (UIntPtr)index); Ensure.BooleanResult(res); } - #endregion + private static StashApplyStatus get_stash_status(int res) + { + if (res == (int)GitErrorCode.Conflict) + { + return StashApplyStatus.Conflicts; + } + + if (res == (int)GitErrorCode.Uncommitted) + { + return StashApplyStatus.UncommittedChanges; + } + + if (res == (int)GitErrorCode.NotFound) + { + return StashApplyStatus.NotFound; + } - #region git_status_ + Ensure.ZeroResult(res); + return StashApplyStatus.Applied; + } - public static FileStatus git_status_file(RepositorySafeHandle repo, FilePath path) + 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 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) { FileStatus status; int res = NativeMethods.git_status_file(out status, repo, path); @@ -2467,10 +2928,10 @@ public static FileStatus git_status_file(RepositorySafeHandle repo, FilePath pat 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)); + throw new AmbiguousSpecificationException("More than one file matches the pathspec '{0}'. " + + "You can either force a literal path evaluation " + + "(GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH), or use git_status_foreach().", + path); default: Ensure.ZeroResult(res); @@ -2480,43 +2941,38 @@ public static FileStatus git_status_file(RepositorySafeHandle repo, FilePath pat return status; } - public static StatusListSafeHandle git_status_list_new(RepositorySafeHandle repo, GitStatusOptions options) + public static unsafe StatusListHandle git_status_list_new(RepositoryHandle repo, GitStatusOptions options) { - StatusListSafeHandle handle; - int res = NativeMethods.git_status_list_new(out handle, repo, options); + git_status_list* ptr; + int res = NativeMethods.git_status_list_new(out ptr, repo, options); Ensure.ZeroResult(res); - return handle; + return new StatusListHandle(ptr, true); } - public static int git_status_list_entrycount(StatusListSafeHandle list) + public static unsafe int git_status_list_entrycount(StatusListHandle list) { int res = NativeMethods.git_status_list_entrycount(list); Ensure.Int32Result(res); return res; } - public static StatusEntrySafeHandle git_status_byindex(StatusListSafeHandle list, long idx) + public static unsafe git_status_entry* git_status_byindex(StatusListHandle list, long idx) { return NativeMethods.git_status_byindex(list, (UIntPtr)idx); } - public static void git_status_list_free(IntPtr statusList) - { - NativeMethods.git_status_list_free(statusList); - } +#endregion - #endregion - - #region git_submodule_ +#region git_submodule_ /// /// Returns a handle to the corresponding submodule, /// or an invalid handle if a submodule is not found. /// - public static SubmoduleSafeHandle git_submodule_lookup(RepositorySafeHandle repo, FilePath name) + public static unsafe SubmoduleHandle git_submodule_lookup(RepositoryHandle repo, string name) { - SubmoduleSafeHandle reference; - var res = NativeMethods.git_submodule_lookup(out reference, repo, name); + git_submodule* submodule; + var res = NativeMethods.git_submodule_lookup(out submodule, repo, name); switch (res) { @@ -2527,11 +2983,11 @@ public static SubmoduleSafeHandle git_submodule_lookup(RepositorySafeHandle repo default: Ensure.ZeroResult(res); - return reference; + return new SubmoduleHandle(submodule, true); } } - public static string git_submodule_resolve_url(RepositorySafeHandle repo, string url) + public static unsafe string git_submodule_resolve_url(RepositoryHandle repo, string url) { using (var buf = new GitBuf()) { @@ -2542,107 +2998,96 @@ public static string git_submodule_resolve_url(RepositorySafeHandle repo, string } } - public static ICollection git_submodule_foreach(RepositorySafeHandle repo, Func resultSelector) + public static unsafe ICollection git_submodule_foreach(RepositoryHandle repo, Func resultSelector) { return git_foreach(resultSelector, c => NativeMethods.git_submodule_foreach(repo, (x, y, p) => c(x, y, p), IntPtr.Zero)); } - public static void git_submodule_add_to_index(SubmoduleSafeHandle submodule, bool write_index) + public static unsafe void git_submodule_add_to_index(SubmoduleHandle submodule, bool write_index) { var res = NativeMethods.git_submodule_add_to_index(submodule, write_index); Ensure.ZeroResult(res); } - public static void git_submodule_save(SubmoduleSafeHandle submodule) - { - var res = NativeMethods.git_submodule_save(submodule); - Ensure.ZeroResult(res); - } - - public static void git_submodule_update(SubmoduleSafeHandle submodule, bool init, ref GitSubmoduleOptions options) + public static unsafe void git_submodule_update(SubmoduleHandle submodule, bool init, ref GitSubmoduleUpdateOptions options) { var res = NativeMethods.git_submodule_update(submodule, init, ref options); Ensure.ZeroResult(res); } - public static void git_submodule_free(IntPtr submodule) - { - NativeMethods.git_submodule_free(submodule); - } - - public static string git_submodule_path(SubmoduleSafeHandle submodule) + public static unsafe string git_submodule_path(SubmoduleHandle submodule) { return NativeMethods.git_submodule_path(submodule); } - public static string git_submodule_url(SubmoduleSafeHandle submodule) + public static unsafe string git_submodule_url(SubmoduleHandle submodule) { return NativeMethods.git_submodule_url(submodule); } - public static ObjectId git_submodule_index_id(SubmoduleSafeHandle submodule) + public static unsafe ObjectId git_submodule_index_id(SubmoduleHandle submodule) { - return NativeMethods.git_submodule_index_id(submodule).MarshalAsObjectId(); + return ObjectId.BuildFromPtr(NativeMethods.git_submodule_index_id(submodule)); } - public static ObjectId git_submodule_head_id(SubmoduleSafeHandle submodule) + public static unsafe ObjectId git_submodule_head_id(SubmoduleHandle submodule) { - return NativeMethods.git_submodule_head_id(submodule).MarshalAsObjectId(); + return ObjectId.BuildFromPtr(NativeMethods.git_submodule_head_id(submodule)); } - public static ObjectId git_submodule_wd_id(SubmoduleSafeHandle submodule) + public static unsafe ObjectId git_submodule_wd_id(SubmoduleHandle submodule) { - return NativeMethods.git_submodule_wd_id(submodule).MarshalAsObjectId(); + return ObjectId.BuildFromPtr(NativeMethods.git_submodule_wd_id(submodule)); } - public static SubmoduleIgnore git_submodule_ignore(SubmoduleSafeHandle submodule) + public static unsafe SubmoduleIgnore git_submodule_ignore(SubmoduleHandle submodule) { return NativeMethods.git_submodule_ignore(submodule); } - public static SubmoduleUpdate git_submodule_update_strategy(SubmoduleSafeHandle submodule) + public static unsafe SubmoduleUpdate git_submodule_update_strategy(SubmoduleHandle submodule) { return NativeMethods.git_submodule_update_strategy(submodule); } - public static SubmoduleRecurse git_submodule_fetch_recurse_submodules(SubmoduleSafeHandle submodule) + public static unsafe SubmoduleRecurse git_submodule_fetch_recurse_submodules(SubmoduleHandle submodule) { return NativeMethods.git_submodule_fetch_recurse_submodules(submodule); } - public static void git_submodule_reload(SubmoduleSafeHandle submodule) + public static unsafe void git_submodule_reload(SubmoduleHandle submodule) { var res = NativeMethods.git_submodule_reload(submodule, false); Ensure.ZeroResult(res); } - public static SubmoduleStatus git_submodule_status(SubmoduleSafeHandle submodule) + public static unsafe SubmoduleStatus git_submodule_status(RepositoryHandle repo, string name) { SubmoduleStatus status; - var res = NativeMethods.git_submodule_status(out status, submodule); + var res = NativeMethods.git_submodule_status(out status, repo, name, GitSubmoduleIgnore.Unspecified); Ensure.ZeroResult(res); return status; } - public static void git_submodule_init(SubmoduleSafeHandle submodule, bool overwrite) + public static unsafe void git_submodule_init(SubmoduleHandle submodule, bool overwrite) { var res = NativeMethods.git_submodule_init(submodule, overwrite); Ensure.ZeroResult(res); } - #endregion +#endregion - #region git_tag_ +#region git_tag_ - public static ObjectId git_tag_annotation_create( - RepositorySafeHandle repo, + public static unsafe ObjectId git_tag_annotation_create( + RepositoryHandle repo, string name, GitObject target, Signature tagger, string message) { using (var objectPtr = new ObjectSafeWrapper(target.Id, repo)) - using (SignatureSafeHandle sigHandle = tagger.BuildHandle()) + using (SignatureHandle sigHandle = tagger.BuildHandle()) { GitOid oid; int res = NativeMethods.git_tag_annotation_create(out oid, repo, name, objectPtr.ObjectPtr, sigHandle, message); @@ -2652,8 +3097,8 @@ public static ObjectId git_tag_annotation_create( } } - public static ObjectId git_tag_create( - RepositorySafeHandle repo, + public static unsafe ObjectId git_tag_create( + RepositoryHandle repo, string name, GitObject target, Signature tagger, @@ -2661,7 +3106,7 @@ public static ObjectId git_tag_create( bool allowOverwrite) { using (var objectPtr = new ObjectSafeWrapper(target.Id, repo)) - using (SignatureSafeHandle sigHandle = tagger.BuildHandle()) + using (SignatureHandle sigHandle = tagger.BuildHandle()) { GitOid oid; int res = NativeMethods.git_tag_create(out oid, repo, name, objectPtr.ObjectPtr, sigHandle, message, allowOverwrite); @@ -2671,7 +3116,7 @@ public static ObjectId git_tag_create( } } - public static ObjectId git_tag_create_lightweight(RepositorySafeHandle repo, string name, GitObject target, bool allowOverwrite) + public static unsafe ObjectId git_tag_create_lightweight(RepositoryHandle repo, string name, GitObject target, bool allowOverwrite) { using (var objectPtr = new ObjectSafeWrapper(target.Id, repo)) { @@ -2683,13 +3128,13 @@ public static ObjectId git_tag_create_lightweight(RepositorySafeHandle repo, str } } - public static void git_tag_delete(RepositorySafeHandle repo, string name) + public static unsafe void git_tag_delete(RepositoryHandle repo, string name) { int res = NativeMethods.git_tag_delete(repo, name); Ensure.ZeroResult(res); } - public static IList git_tag_list(RepositorySafeHandle repo) + public static unsafe IList git_tag_list(RepositoryHandle repo) { var array = new GitStrArrayNative(); @@ -2706,24 +3151,24 @@ public static IList git_tag_list(RepositorySafeHandle repo) } } - public static string git_tag_message(GitObjectSafeHandle tag) + public static unsafe string git_tag_message(ObjectHandle tag) { return NativeMethods.git_tag_message(tag); } - public static string git_tag_name(GitObjectSafeHandle tag) + public static unsafe string git_tag_name(ObjectHandle tag) { return NativeMethods.git_tag_name(tag); } - public static Signature git_tag_tagger(GitObjectSafeHandle tag) + public static unsafe Signature git_tag_tagger(ObjectHandle tag) { - IntPtr taggerHandle = NativeMethods.git_tag_tagger(tag); + git_signature* taggerHandle = NativeMethods.git_tag_tagger(tag); // Not all tags have a tagger signature - we need to handle // this case. Signature tagger = null; - if (taggerHandle != IntPtr.Zero) + if (taggerHandle != null) { tagger = new Signature(taggerHandle); } @@ -2731,19 +3176,19 @@ public static Signature git_tag_tagger(GitObjectSafeHandle tag) return tagger; } - public static ObjectId git_tag_target_id(GitObjectSafeHandle tag) + public static unsafe ObjectId git_tag_target_id(ObjectHandle tag) { - return NativeMethods.git_tag_target_id(tag).MarshalAsObjectId(); + return ObjectId.BuildFromPtr(NativeMethods.git_tag_target_id(tag)); } - public static GitObjectType git_tag_target_type(GitObjectSafeHandle tag) + public static unsafe GitObjectType git_tag_target_type(ObjectHandle tag) { return NativeMethods.git_tag_target_type(tag); } - #endregion +#endregion - #region git_trace_ +#region git_trace_ /// /// Install/Enable logging inside of LibGit2 to send messages back to LibGit2Sharp. @@ -2763,9 +3208,9 @@ public static void git_trace_set(LogLevel level, NativeMethods.git_trace_cb call Ensure.ZeroResult(res); } - #endregion +#endregion - #region git_transport_ +#region git_transport_ public static void git_transport_register(String prefix, IntPtr transport_cb, IntPtr param) { @@ -2773,8 +3218,8 @@ public static void git_transport_register(String prefix, IntPtr transport_cb, In if (res == (int)GitErrorCode.Exists) { - throw new EntryExistsException(String.Format("A custom transport for '{0}' is already registered", - prefix)); + throw new EntryExistsException("A custom transport for '{0}' is already registered", + prefix); } Ensure.ZeroResult(res); @@ -2792,25 +3237,40 @@ public static void git_transport_unregister(String prefix) Ensure.ZeroResult(res); } - #endregion +#endregion + +#region git_transport_smart_ + + public static int git_transport_smart_credentials(out IntPtr cred, IntPtr transport, string user, int methods) + { + return NativeMethods.git_transport_smart_credentials(out cred, transport, user, methods); + } + +#endregion - #region git_tree_ +#region git_tree_ - public static Mode git_tree_entry_attributes(SafeHandle entry) + public static unsafe Mode git_tree_entry_attributes(git_tree_entry* entry) { return (Mode)NativeMethods.git_tree_entry_filemode(entry); } - public static TreeEntrySafeHandle git_tree_entry_byindex(GitObjectSafeHandle tree, long idx) + public static unsafe TreeEntryHandle git_tree_entry_byindex(ObjectHandle tree, long idx) { - return NativeMethods.git_tree_entry_byindex(tree, (UIntPtr)idx); + var handle = NativeMethods.git_tree_entry_byindex(tree, (UIntPtr)idx); + if (handle == null) + { + return null; + } + + return new TreeEntryHandle(handle, false); } - public static TreeEntrySafeHandle_Owned git_tree_entry_bypath(RepositorySafeHandle repo, ObjectId id, FilePath treeentry_path) + public static unsafe TreeEntryHandle git_tree_entry_bypath(RepositoryHandle repo, ObjectId id, string treeentry_path) { using (var obj = new ObjectSafeWrapper(id, repo)) { - TreeEntrySafeHandle_Owned treeEntryPtr; + git_tree_entry* treeEntryPtr; int res = NativeMethods.git_tree_entry_bypath(out treeEntryPtr, obj.ObjectPtr, treeentry_path); if (res == (int)GitErrorCode.NotFound) @@ -2820,54 +3280,44 @@ public static TreeEntrySafeHandle_Owned git_tree_entry_bypath(RepositorySafeHand Ensure.ZeroResult(res); - return treeEntryPtr; + return new TreeEntryHandle(treeEntryPtr, true); } } - public static void git_tree_entry_free(IntPtr treeEntry) - { - NativeMethods.git_tree_entry_free(treeEntry); - } - - public static ObjectId git_tree_entry_id(SafeHandle entry) + public static unsafe ObjectId git_tree_entry_id(git_tree_entry* entry) { - return NativeMethods.git_tree_entry_id(entry).MarshalAsObjectId(); + return ObjectId.BuildFromPtr(NativeMethods.git_tree_entry_id(entry)); } - public static string git_tree_entry_name(SafeHandle entry) + public static unsafe string git_tree_entry_name(git_tree_entry* entry) { return NativeMethods.git_tree_entry_name(entry); } - public static GitObjectType git_tree_entry_type(SafeHandle entry) + public static unsafe GitObjectType git_tree_entry_type(git_tree_entry* entry) { return NativeMethods.git_tree_entry_type(entry); } - public static int git_tree_entrycount(GitObjectSafeHandle tree) + public static unsafe int git_tree_entrycount(ObjectHandle tree) { return (int)NativeMethods.git_tree_entrycount(tree); } - #endregion +#endregion - #region git_treebuilder_ +#region git_treebuilder_ - public static TreeBuilderSafeHandle git_treebuilder_new(RepositorySafeHandle repo) + public static unsafe TreeBuilderHandle git_treebuilder_new(RepositoryHandle repo) { - TreeBuilderSafeHandle builder; + git_treebuilder* builder; int res = NativeMethods.git_treebuilder_new(out builder, repo, IntPtr.Zero); Ensure.ZeroResult(res); - return builder; + return new TreeBuilderHandle(builder, true); } - public static void git_treebuilder_free(IntPtr bld) - { - NativeMethods.git_treebuilder_free(bld); - } - - public static void git_treebuilder_insert(TreeBuilderSafeHandle builder, string treeentry_name, TreeEntryDefinition treeEntryDefinition) + public static unsafe void git_treebuilder_insert(TreeBuilderHandle builder, string treeentry_name, TreeEntryDefinition treeEntryDefinition) { GitOid oid = treeEntryDefinition.TargetId.Oid; int res = NativeMethods.git_treebuilder_insert(IntPtr.Zero, builder, treeentry_name, ref oid, @@ -2875,7 +3325,7 @@ public static void git_treebuilder_insert(TreeBuilderSafeHandle builder, string Ensure.ZeroResult(res); } - public static ObjectId git_treebuilder_write(TreeBuilderSafeHandle bld) + public static unsafe ObjectId git_treebuilder_write(TreeBuilderHandle bld) { GitOid oid; int res = NativeMethods.git_treebuilder_write(out oid, bld); @@ -2884,9 +3334,23 @@ public static ObjectId git_treebuilder_write(TreeBuilderSafeHandle bld) return oid; } - #endregion +#endregion + +#region git_transaction_ + + public static void git_transaction_commit(IntPtr txn) + { + NativeMethods.git_transaction_commit(txn); + } + + public static void git_transaction_free(IntPtr txn) + { + NativeMethods.git_transaction_free(txn); + } - #region git_libgit2_ +#endregion + +#region git_libgit2_ /// /// Returns the features with which libgit2 was compiled. @@ -2896,6 +3360,261 @@ 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 + } + + /// + /// 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()) + { + var res = NativeMethods.git_libgit2_opts((int)LibGit2Option.GetSearchPath, (uint)level, buf); + Ensure.ZeroResult(res); + + path = LaxUtf8Marshaler.FromNative(buf.ptr) ?? string.Empty; + } + + return path; + } + + public static void git_libgit2_opts_enable_strict_hash_verification(bool enabled) + { + 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) + { + var res = NativeMethods.git_libgit2_opts((int)LibGit2Option.SetSearchPath, (uint)level, path); + Ensure.ZeroResult(res); + } + + /// + /// 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 + var res = NativeMethods.git_libgit2_opts((int)LibGit2Option.EnableCaching, enabled ? 1 : 0); + Ensure.ZeroResult(res); + } + + /// + /// 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 + var res = NativeMethods.git_libgit2_opts((int)LibGit2Option.EnableOfsDelta, enabled ? 1 : 0); + Ensure.ZeroResult(res); + } + + /// + /// Enable or disable the strict_object_creation capabilty + /// + /// true to enable the strict_object_creation capabilty, false otherwise + public static void git_libgit2_opts_set_enable_strictobjectcreation(bool enabled) + { + // libgit2 expects non-zero value for true + var res = NativeMethods.git_libgit2_opts((int)LibGit2Option.EnableStrictObjectCreation, enabled ? 1 : 0); + Ensure.ZeroResult(res); + } + + /// + /// Sets the user-agent string to be used by the HTTP(S) transport. + /// Note that "git/2.0" will be prepended for compatibility. + /// + /// The user-agent string to use + public static void git_libgit2_opts_set_user_agent(string userAgent) + { + var res = NativeMethods.git_libgit2_opts((int)LibGit2Option.SetUserAgent, userAgent); + Ensure.ZeroResult(res); + } + + /// + /// Gets the user-agent string used by libgit2. + /// + /// The user-agent string. + /// + /// + public static string git_libgit2_opts_get_user_agent() + { + string userAgent; + + using (var buf = new GitBuf()) + { + var res = NativeMethods.git_libgit2_opts((int)LibGit2Option.GetUserAgent, buf); + Ensure.ZeroResult(res); + + userAgent = LaxUtf8Marshaler.FromNative(buf.ptr) ?? string.Empty; + } + + return userAgent; + } + + #endregion + + #region git_worktree_ + + /// + /// Returns a handle to the corresponding worktree, + /// or an invalid handle if a worktree is not found. + /// + public static unsafe WorktreeHandle git_worktree_lookup(RepositoryHandle repo, string name) + { + git_worktree* worktree; + var res = NativeMethods.git_worktree_lookup(out worktree, repo, name); + + switch (res) + { + case (int)GitErrorCode.Error: + case (int)GitErrorCode.NotFound: + case (int)GitErrorCode.Exists: + case (int)GitErrorCode.OrphanedHead: + return null; + + default: + Ensure.ZeroResult(res); + return new WorktreeHandle(worktree, true); + } + } + + public static unsafe IList git_worktree_list(RepositoryHandle repo) + { + 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 unsafe RepositoryHandle git_repository_open_from_worktree(WorktreeHandle handle) + { + 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 unsafe WorktreeLock git_worktree_is_locked(WorktreeHandle worktree) + { + using (var buf = new GitBuf()) + { + int res = NativeMethods.git_worktree_is_locked(buf, worktree); + + if(res < 0) + { + // error + return null; + } + + if (res == (int)GitErrorCode.Ok) + { + return new WorktreeLock(); + } + + return new WorktreeLock(true, LaxUtf8Marshaler.FromNative(buf.ptr)); + } + } + + public static unsafe bool git_worktree_validate(WorktreeHandle worktree) + { + int res = NativeMethods.git_worktree_validate(worktree); + + return res == (int)GitErrorCode.Ok; + } + + public static unsafe bool git_worktree_unlock(WorktreeHandle worktree) + { + int res = NativeMethods.git_worktree_unlock(worktree); + + return res == (int)GitErrorCode.Ok; + } + + public static unsafe bool git_worktree_lock(WorktreeHandle worktree, string reason) + { + int res = NativeMethods.git_worktree_lock(worktree, reason); + + return res == (int)GitErrorCode.Ok; + } + + public static unsafe WorktreeHandle git_worktree_add( + RepositoryHandle repo, + string name, + string path, + git_worktree_add_options options) + { + 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 unsafe bool git_worktree_prune(WorktreeHandle worktree, + git_worktree_prune_options options) + { + int res = NativeMethods.git_worktree_prune(worktree, options); + Ensure.ZeroResult(res); + return true; + } + #endregion private static ICollection git_foreach( @@ -2984,67 +3703,9 @@ private static ICollection git_foreach( return result; } - private delegate int IteratorNew(out THandle iter); - - private delegate TPayload IteratorNext(TIterator iter, out THandle next, out int res); - - private static THandle git_iterator_new(IteratorNew newFunc) - where THandle : SafeHandleBase - { - THandle iter; - Ensure.ZeroResult(newFunc(out iter)); - return iter; - } - - private static IEnumerable git_iterator_next( - TIterator iter, - IteratorNext nextFunc, - Func resultSelector) - where THandle : SafeHandleBase - { - while (true) - { - var next = default(THandle); - try - { - int res; - var payload = nextFunc(iter, out next, out res); - - if (res == (int)GitErrorCode.IterOver) - { - yield break; - } - - Ensure.ZeroResult(res); - yield return resultSelector(next, payload); - } - finally - { - next.SafeDispose(); - } - } - } - - private static IEnumerable git_iterator( - IteratorNew newFunc, - IteratorNext nextFunc, - Func resultSelector - ) - where TIterator : SafeHandleBase - where THandle : SafeHandleBase + private static unsafe bool RepositoryStateChecker(RepositoryHandle repo, Func checker) { - using (var iter = git_iterator_new(newFunc)) - { - foreach (var next in git_iterator_next(iter, nextFunc, resultSelector)) - { - yield return next; - } - } - } - - private static bool RepositoryStateChecker(RepositorySafeHandle repo, Func checker) - { - int res = checker(repo); + int res = checker(repo.AsIntPtr()); Ensure.BooleanResult(res); return (res == 1); @@ -3109,6 +3770,24 @@ public static int ConvertToInt(this UIntPtr input) return (int)input; } + + + /// + /// Convert a UIntPtr to a long value. Will throw + /// exception if there is an overflow. + /// + /// + /// + public static long ConvertToLong(this UIntPtr input) + { + ulong ulongValue = (ulong)input; + if (ulongValue > long.MaxValue) + { + throw new LibGit2SharpException("value exceeds size of long"); + } + + return (long)input; + } } } // ReSharper restore InconsistentNaming diff --git a/LibGit2Sharp/Core/RawContentStream.cs b/LibGit2Sharp/Core/RawContentStream.cs index d02fe8b81..92b4b3bf0 100644 --- a/LibGit2Sharp/Core/RawContentStream.cs +++ b/LibGit2Sharp/Core/RawContentStream.cs @@ -7,13 +7,13 @@ namespace LibGit2Sharp.Core { internal class RawContentStream : UnmanagedMemoryStream { - private readonly GitObjectSafeHandle handle; + private readonly ObjectHandle handle; private readonly ICollection linkedResources; internal unsafe RawContentStream( - GitObjectSafeHandle handle, - Func bytePtrProvider, - Func sizeProvider, + ObjectHandle handle, + Func bytePtrProvider, + Func sizeProvider, ICollection linkedResources = null) : base((byte*)Wrap(handle, bytePtrProvider, linkedResources).ToPointer(), Wrap(handle, sizeProvider, linkedResources)) @@ -23,8 +23,8 @@ internal unsafe RawContentStream( } private static T Wrap( - GitObjectSafeHandle handle, - Func provider, + ObjectHandle handle, + Func provider, IEnumerable linkedResources) { T value; @@ -43,7 +43,7 @@ private static T Wrap( } private static void Dispose( - GitObjectSafeHandle handle, + ObjectHandle handle, IEnumerable linkedResources) { handle.SafeDispose(); @@ -64,5 +64,5 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); Dispose(handle, linkedResources); } - } + } } diff --git a/LibGit2Sharp/Core/SubmoduleLazyGroup.cs b/LibGit2Sharp/Core/SubmoduleLazyGroup.cs index 0591574d4..42e40e07b 100644 --- a/LibGit2Sharp/Core/SubmoduleLazyGroup.cs +++ b/LibGit2Sharp/Core/SubmoduleLazyGroup.cs @@ -3,7 +3,7 @@ namespace LibGit2Sharp.Core { - internal class SubmoduleLazyGroup : LazyGroup + internal class SubmoduleLazyGroup : LazyGroup { private readonly string name; @@ -13,13 +13,15 @@ public SubmoduleLazyGroup(Repository repo, string name) this.name = name; } - protected override void EvaluateInternal(Action evaluator) + protected override void EvaluateInternal(Action evaluator) { - repo.Submodules.Lookup(name, handle => - { - evaluator(handle); - return default(object); - }, true); + repo.Submodules.Lookup(name, + handle => + { + evaluator(handle); + return default(object); + }, + true); } } } diff --git a/LibGit2Sharp/Core/TarWriter.cs b/LibGit2Sharp/Core/TarWriter.cs index 9f71db44a..609449316 100644 --- a/LibGit2Sharp/Core/TarWriter.cs +++ b/LibGit2Sharp/Core/TarWriter.cs @@ -75,8 +75,21 @@ public void Write( WriteExtendedHeader(fileNameExtendedHeader, linkExtendedHeader, entrySha, modificationTime); // Note: in case of links, we won't add a content, but the size in the header will still be != 0. It seems strange, but it seem to be what git.git is doing? - WriteHeader(fileNameExtendedHeader.Name, fileNameExtendedHeader.Prefix, modificationTime, (data != null) ? data.Length : 0, mode, - userId, groupId, typeflag, linkExtendedHeader.Link, userName, groupName, deviceMajorNumber, deviceMinorNumber); + WriteHeader(fileNameExtendedHeader.Name, + fileNameExtendedHeader.Prefix, + modificationTime, + (data != null) + ? data.Length + : 0, + mode, + userId, + groupId, + typeflag, + linkExtendedHeader.Link, + userName, + groupName, + deviceMajorNumber, + deviceMinorNumber); // folders have no data, and so do links if (data != null && !isLink) @@ -94,7 +107,9 @@ protected static void WriteContent(long count, Stream data, Stream dest) { int bytesRead = data.Read(buffer, 0, buffer.Length); if (bytesRead < 0) + { throw new IOException("TarWriter unable to read from provided stream"); + } dest.Write(buffer, 0, bytesRead); count -= bytesRead; @@ -103,7 +118,10 @@ protected static void WriteContent(long count, Stream data, Stream dest) { int bytesRead = data.Read(buffer, 0, (int)count); if (bytesRead < 0) + { throw new IOException("TarWriter unable to read from provided stream"); + } + if (bytesRead == 0) { while (count > 0) @@ -113,7 +131,9 @@ protected static void WriteContent(long count, Stream data, Stream dest) } } else + { dest.Write(buffer, 0, bytesRead); + } } } @@ -143,8 +163,19 @@ protected void WriteHeader( string deviceMajorNumber, string deviceMinorNumber) { - var tarHeader = new UsTarHeader(fileName, namePrefix, lastModificationTime, count, mode, - userId, groupId, typeflag, link, userName, groupName, deviceMajorNumber, deviceMinorNumber); + var tarHeader = new UsTarHeader(fileName, + namePrefix, + lastModificationTime, + count, + mode, + userId, + groupId, + typeflag, + link, + userName, + groupName, + deviceMajorNumber, + deviceMinorNumber); var header = tarHeader.GetHeaderValue(); OutStream.Write(header, 0, header.Length); } @@ -168,7 +199,10 @@ private static LinkExtendedHeader ParseLink(bool isLink, Stream data, string ent if (data.Length > 100) { return new LinkExtendedHeader(link, - string.Format(CultureInfo.InvariantCulture, "see %s.paxheader{0}", entrySha), true); + string.Format(CultureInfo.InvariantCulture, + "see %s.paxheader{0}", + entrySha), + true); } return new LinkExtendedHeader(link, link, false); @@ -198,8 +232,20 @@ private void WriteExtendedHeader(FileNameExtendedHeader fileNameExtendedHeader, using (var stream = new MemoryStream(Encoding.ASCII.GetBytes(extHeader))) { - Write(string.Format(CultureInfo.InvariantCulture, "{0}.paxheader", entrySha), stream, modificationTime, "666".OctalToInt32(), - "0", "0", 'x', "root", "root", "0", "0", entrySha, false); + Write(string.Format(CultureInfo.InvariantCulture, + "{0}.paxheader", + entrySha), + stream, modificationTime, + "666".OctalToInt32(), + "0", + "0", + 'x', + "root", + "root", + "0", + "0", + entrySha, + false); } } @@ -208,7 +254,9 @@ private static string BuildKeyValueExtHeader(string key, string value) // "%u %s=%s\n" int len = key.Length + value.Length + 3; for (int i = len; i > 9; i /= 10) + { len++; + } return string.Format(CultureInfo.InvariantCulture, "{0} {1}={2}\n", len, key, value); } @@ -284,7 +332,7 @@ public UsTarHeader( this.mode = Convert.ToString(mode, 8).PadLeft(7, '0'); this.size = size; - unixTime = Convert.ToString(lastModificationTime.ToSecondsSinceEpoch(), 8).PadLeft(11, '0'); + unixTime = Convert.ToString(lastModificationTime.ToUnixTimeSeconds(), 8).PadLeft(11, '0'); this.userId = userId.PadLeft(7, '0'); this.groupId = userId.PadLeft(7, '0'); this.userName = userName; @@ -410,8 +458,12 @@ public static FileNameExtendedHeader Parse(string posixPath, string entrySha) return new FileNameExtendedHeader(posixPath, posixPath.Substring(0, position), posixPath.Substring(position, posixPath.Length - position), false); } - return new FileNameExtendedHeader(posixPath, string.Empty, - string.Format(CultureInfo.InvariantCulture, "{0}.data", entrySha), true); + return new FileNameExtendedHeader(posixPath, + string.Empty, + string.Format(CultureInfo.InvariantCulture, + "{0}.data", + entrySha), + true); } return new FileNameExtendedHeader(posixPath, string.Empty, posixPath, false); diff --git a/LibGit2Sharp/Core/Utf8Marshaler.cs b/LibGit2Sharp/Core/Utf8Marshaler.cs index 0080bc4c0..a6fddb808 100644 --- a/LibGit2Sharp/Core/Utf8Marshaler.cs +++ b/LibGit2Sharp/Core/Utf8Marshaler.cs @@ -19,7 +19,7 @@ internal class LaxUtf8NoCleanupMarshaler : LaxUtf8Marshaler { private static readonly LaxUtf8NoCleanupMarshaler staticInstance = new LaxUtf8NoCleanupMarshaler(); - public new static ICustomMarshaler GetInstance(String cookie) + public new static ICustomMarshaler GetInstance(string cookie) { return staticInstance; } @@ -27,8 +27,7 @@ internal class LaxUtf8NoCleanupMarshaler : LaxUtf8Marshaler #region ICustomMarshaler public override void CleanUpNativeData(IntPtr pNativeData) - { - } + { } #endregion } @@ -43,7 +42,7 @@ public override void CleanUpNativeData(IntPtr pNativeData) /// Use this marshaler for function parameters, for example: /// [DllImport(libgit2)] /// internal static extern int git_tag_delete(RepositorySafeHandle repo, - /// [MarshalAs(UnmanagedType.CustomMarshaler, + /// [MarshalAs(UnmanagedType.CustomMarshaler /// MarshalCookie = UniqueId.UniqueIdentifier, /// MarshalTypeRef = typeof(StrictUtf8Marshaler))] String tagName); /// @@ -61,7 +60,7 @@ static StrictUtf8Marshaler() public StrictUtf8Marshaler() : base(encoding) { } - public static ICustomMarshaler GetInstance(String cookie) + public static ICustomMarshaler GetInstance(string cookie) { return staticInstance; } @@ -70,8 +69,9 @@ public static ICustomMarshaler GetInstance(String cookie) public override Object MarshalNativeToManaged(IntPtr pNativeData) { - throw new InvalidOperationException( - string.Format(CultureInfo.InvariantCulture, "{0} cannot be used to retrieve data from libgit2.", GetType().Name)); + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, + "{0} cannot be used to retrieve data from libgit2.", + GetType().Name)); } #endregion @@ -96,7 +96,7 @@ internal class LaxUtf8Marshaler : EncodingMarshaler public LaxUtf8Marshaler() : base(Encoding) { } - public static ICustomMarshaler GetInstance(String cookie) + public static ICustomMarshaler GetInstance(string cookie) { return staticInstance; } @@ -105,12 +105,18 @@ public static ICustomMarshaler GetInstance(String cookie) public override IntPtr MarshalManagedToNative(object managedObj) { - throw new InvalidOperationException( - string.Format(CultureInfo.InvariantCulture, "{0} cannot be used to pass data to libgit2.", GetType().Name)); + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, + "{0} cannot be used to pass data to libgit2.", + GetType().Name)); } #endregion + public static unsafe string FromNative(char* pNativeData) + { + return FromNative(Encoding, (byte*)pNativeData); + } + public static string FromNative(IntPtr pNativeData) { return FromNative(Encoding, pNativeData); 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/CurrentOperation.cs b/LibGit2Sharp/CurrentOperation.cs index 58f7064ae..9050e8235 100644 --- a/LibGit2Sharp/CurrentOperation.cs +++ b/LibGit2Sharp/CurrentOperation.cs @@ -21,39 +21,49 @@ public enum CurrentOperation /// Revert = 2, + /// + /// A sequencer revert is in progress. + /// + RevertSequence = 3, + /// /// A cherry-pick is in progress. /// - CherryPick = 3, + CherryPick = 4, + + /// + /// A sequencer cherry-pick is in progress. + /// + CherryPickSequence = 5, /// /// A bisect is in progress. /// - Bisect = 4, + Bisect = 6, /// /// A rebase is in progress. /// - Rebase = 5, + Rebase = 7, /// /// A rebase --interactive is in progress. /// - RebaseInteractive = 6, + RebaseInteractive = 8, /// /// A rebase --merge is in progress. /// - RebaseMerge = 7, + RebaseMerge = 9, /// /// A mailbox application (am) is in progress. /// - ApplyMailbox = 8, + ApplyMailbox = 10, /// /// A mailbox application (am) or rebase is in progress. /// - ApplyMailboxOrRebase = 9, + ApplyMailboxOrRebase = 11, } } diff --git a/LibGit2Sharp/DescribeOptions.cs b/LibGit2Sharp/DescribeOptions.cs index db8f04655..3ca6f31eb 100644 --- a/LibGit2Sharp/DescribeOptions.cs +++ b/LibGit2Sharp/DescribeOptions.cs @@ -21,6 +21,7 @@ public DescribeOptions() { Strategy = DescribeStrategy.Default; MinimumCommitIdAbbreviatedSize = 7; + OnlyFollowFirstParent = false; } /// @@ -54,5 +55,14 @@ public DescribeOptions() /// /// 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/DetachedHead.cs b/LibGit2Sharp/DetachedHead.cs index 094d9d263..d934db2c4 100644 --- a/LibGit2Sharp/DetachedHead.cs +++ b/LibGit2Sharp/DetachedHead.cs @@ -4,8 +4,7 @@ internal class DetachedHead : Branch { internal DetachedHead(Repository repo, Reference reference) : base(repo, reference, "(no branch)") - { - } + { } protected override string Shorten() { diff --git a/LibGit2Sharp/Diff.cs b/LibGit2Sharp/Diff.cs index 265aa0cd6..e4dfa5abb 100644 --- a/LibGit2Sharp/Diff.cs +++ b/LibGit2Sharp/Diff.cs @@ -53,12 +53,21 @@ private static GitDiffOptions BuildOptions(DiffModifiers diffOptions, FilePath[] { options.Flags |= GitDiffOptionFlags.GIT_DIFF_PATIENCE; } + else if (compareOptions.Algorithm == DiffAlgorithm.Minimal) + { + options.Flags |= GitDiffOptionFlags.GIT_DIFF_MINIMAL; + } if (diffOptions.HasFlag(DiffModifiers.DisablePathspecMatch)) { options.Flags |= GitDiffOptionFlags.GIT_DIFF_DISABLE_PATHSPEC_MATCH; } + if (compareOptions.IndentHeuristic) + { + options.Flags |= GitDiffOptionFlags.GIT_DIFF_INDENT_HEURISTIC; + } + if (matchedPathsAggregator != null) { options.NotifyCallback = matchedPathsAggregator.OnGitDiffNotify; @@ -95,13 +104,27 @@ private static IDictionary> ChangesBuilders = new Dictionary> + private static readonly IDictionary> ChangesBuilders = new Dictionary> { { typeof(Patch), diff => new Patch(diff) }, { typeof(TreeChanges), diff => new TreeChanges(diff) }, { typeof(PatchStats), diff => new PatchStats(diff) }, }; + + private static T BuildDiffResult(DiffHandle diff) where T : class, IDiffResult + { + Func builder; + + if (!ChangesBuilders.TryGetValue(typeof(T), out builder)) + { + throw new LibGit2SharpException("User-defined types passed to Compare are not supported. Supported values are: {0}", + string.Join(", ", ChangesBuilders.Keys.Select(x => x.Name))); + } + + return (T)builder(diff); + } + /// /// Show changes between two s. /// @@ -134,7 +157,7 @@ public virtual ContentChanges Compare(Blob oldBlob, Blob newBlob, CompareOptions /// 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 + public virtual T Compare(Tree oldTree, Tree newTree) where T : class, IDiffResult { return Compare(oldTree, newTree, null, null, null); } @@ -146,7 +169,7 @@ public virtual T Compare(Tree oldTree, Tree newTree) where T : class /// 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 + public virtual T Compare(Tree oldTree, Tree newTree, IEnumerable paths) where T : class, IDiffResult { return Compare(oldTree, newTree, paths, null, null); } @@ -163,7 +186,7 @@ public virtual T Compare(Tree oldTree, Tree newTree, IEnumerable path /// /// A containing the changes between the and the . public virtual T Compare(Tree oldTree, Tree newTree, IEnumerable paths, - ExplicitPathsOptions explicitPathsOptions) where T : class + ExplicitPathsOptions explicitPathsOptions) where T : class, IDiffResult { return Compare(oldTree, newTree, paths, explicitPathsOptions, null); } @@ -176,7 +199,7 @@ public virtual T Compare(Tree oldTree, Tree newTree, IEnumerable path /// 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 + public virtual T Compare(Tree oldTree, Tree newTree, IEnumerable paths, CompareOptions compareOptions) where T : class, IDiffResult { return Compare(oldTree, newTree, paths, null, compareOptions); } @@ -188,7 +211,7 @@ public virtual T Compare(Tree oldTree, Tree newTree, IEnumerable path /// 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 + public virtual T Compare(Tree oldTree, Tree newTree, CompareOptions compareOptions) where T : class, IDiffResult { return Compare(oldTree, newTree, null, null, compareOptions); } @@ -206,17 +229,8 @@ public virtual T Compare(Tree oldTree, Tree newTree, CompareOptions compareOp /// 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 + CompareOptions compareOptions) where T : class, IDiffResult { - Func builder; - - if (!ChangesBuilders.TryGetValue(typeof (T), out builder)) - { - throw new LibGit2SharpException(string.Format(CultureInfo.InvariantCulture, - "Unexpected type '{0}' passed to Compare. Supported values are either '{1}' or '{2}'.", typeof (T), - typeof (TreeChanges), typeof (Patch))); - } - var comparer = TreeToTree(repo); ObjectId oldTreeId = oldTree != null ? oldTree.Id : null; ObjectId newTreeId = newTree != null ? newTree.Id : null; @@ -226,17 +240,22 @@ public virtual T Compare(Tree oldTree, Tree newTree, IEnumerable path { diffOptions |= DiffModifiers.DisablePathspecMatch; - if (explicitPathsOptions.ShouldFailOnUnmatchedPath || - explicitPathsOptions.OnUnmatchedPath != null) + if (explicitPathsOptions.ShouldFailOnUnmatchedPath || explicitPathsOptions.OnUnmatchedPath != null) { diffOptions |= DiffModifiers.IncludeUnmodified; } } - using (DiffSafeHandle diff = BuildDiffList(oldTreeId, newTreeId, comparer, - diffOptions, paths, explicitPathsOptions, compareOptions)) + DiffHandle diff = BuildDiffList(oldTreeId, newTreeId, comparer, diffOptions, paths, explicitPathsOptions, compareOptions); + + try + { + return BuildDiffResult(diff); + } + catch { - return (T)builder(diff); + diff.SafeDispose(); + throw; } } @@ -252,7 +271,7 @@ public virtual T Compare(Tree oldTree, Tree newTree, IEnumerable path /// 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 + public virtual T Compare(Tree oldTree, DiffTargets diffTargets) where T : class, IDiffResult { return Compare(oldTree, diffTargets, null, null, null); } @@ -270,7 +289,7 @@ public virtual T Compare(Tree oldTree, DiffTargets diffTargets) where T : cla /// 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 + public virtual T Compare(Tree oldTree, DiffTargets diffTargets, IEnumerable paths) where T : class, IDiffResult { return Compare(oldTree, diffTargets, paths, null, null); } @@ -293,7 +312,7 @@ public virtual T Compare(Tree oldTree, DiffTargets diffTargets, IEnumerable 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 + ExplicitPathsOptions explicitPathsOptions) where T : class, IDiffResult { return Compare(oldTree, diffTargets, paths, explicitPathsOptions, null); } @@ -317,17 +336,8 @@ public virtual T Compare(Tree oldTree, DiffTargets diffTargets, IEnumerable 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 + ExplicitPathsOptions explicitPathsOptions, CompareOptions compareOptions) where T : class, IDiffResult { - Func builder; - - if (!ChangesBuilders.TryGetValue(typeof (T), out builder)) - { - throw new LibGit2SharpException(string.Format(CultureInfo.InvariantCulture, - "Unexpected type '{0}' passed to Compare. Supported values are either '{1}' or '{2}'.", typeof (T), - typeof (TreeChanges), typeof (Patch))); - } - var comparer = HandleRetrieverDispatcher[diffTargets](repo); ObjectId oldTreeId = oldTree != null ? oldTree.Id : null; @@ -339,17 +349,22 @@ public virtual T Compare(Tree oldTree, DiffTargets diffTargets, IEnumerable(diff); + } + catch + { + diff.SafeDispose(); + throw; } } @@ -363,7 +378,7 @@ public virtual T Compare(Tree oldTree, DiffTargets diffTargets, IEnumerableCan 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 + public virtual T Compare() where T : class, IDiffResult { return Compare(DiffModifiers.None); } @@ -379,7 +394,7 @@ public virtual T Compare() where T : class /// 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 + public virtual T Compare(IEnumerable paths) where T : class, IDiffResult { return Compare(DiffModifiers.None, paths); } @@ -396,7 +411,7 @@ public virtual T Compare(IEnumerable paths) where T : class /// 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 + public virtual T Compare(IEnumerable paths, bool includeUntracked) where T : class, IDiffResult { return Compare(includeUntracked ? DiffModifiers.IncludeUntracked : DiffModifiers.None, paths); } @@ -417,8 +432,7 @@ public virtual T Compare(IEnumerable paths, bool includeUntracked) wh /// 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 + public virtual T Compare(IEnumerable paths, bool includeUntracked, ExplicitPathsOptions explicitPathsOptions) where T : class, IDiffResult { return Compare(includeUntracked ? DiffModifiers.IncludeUntracked : DiffModifiers.None, paths, explicitPathsOptions); } @@ -440,45 +454,47 @@ public virtual T Compare(IEnumerable paths, bool includeUntracked, /// 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 + 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 + internal virtual T Compare( + DiffModifiers diffOptions, + IEnumerable paths = null, + ExplicitPathsOptions explicitPathsOptions = null, + CompareOptions compareOptions = null) where T : class, IDiffResult { - Func builder; - - if (!ChangesBuilders.TryGetValue(typeof (T), out builder)) - { - throw new LibGit2SharpException(string.Format(CultureInfo.InvariantCulture, - "Unexpected type '{0}' passed to Compare. Supported values are either '{1}' or '{2}'.", typeof (T), - typeof (TreeChanges), typeof (Patch))); - } - var comparer = WorkdirToIndex(repo); if (explicitPathsOptions != null) { diffOptions |= DiffModifiers.DisablePathspecMatch; - if (explicitPathsOptions.ShouldFailOnUnmatchedPath || - explicitPathsOptions.OnUnmatchedPath != null) + if (explicitPathsOptions.ShouldFailOnUnmatchedPath || explicitPathsOptions.OnUnmatchedPath != null) { diffOptions |= DiffModifiers.IncludeUnmodified; } } - using (DiffSafeHandle diff = BuildDiffList(null, null, comparer, - diffOptions, paths, explicitPathsOptions, compareOptions)) + DiffHandle diff = BuildDiffList(null, null, comparer, diffOptions, paths, explicitPathsOptions, compareOptions); + + try { - return (T)builder(diff); + return BuildDiffResult(diff); + } + catch + { + diff.SafeDispose(); + throw; } } - internal delegate DiffSafeHandle TreeComparisonHandleRetriever(ObjectId oldTreeId, ObjectId newTreeId, GitDiffOptions options); + internal delegate DiffHandle TreeComparisonHandleRetriever(ObjectId oldTreeId, ObjectId newTreeId, GitDiffOptions options); private static TreeComparisonHandleRetriever TreeToTree(Repository repo) { @@ -499,9 +515,9 @@ private static TreeComparisonHandleRetriever WorkdirAndIndexToTree(Repository re { TreeComparisonHandleRetriever comparisonHandleRetriever = (oh, nh, o) => { - DiffSafeHandle diff = Proxy.git_diff_tree_to_index(repo.Handle, repo.Index.Handle, oh, o); + DiffHandle diff = Proxy.git_diff_tree_to_index(repo.Handle, repo.Index.Handle, oh, o); - using (DiffSafeHandle diff2 = Proxy.git_diff_index_to_workdir(repo.Handle, repo.Index.Handle, o)) + using (DiffHandle diff2 = Proxy.git_diff_index_to_workdir(repo.Handle, repo.Index.Handle, o)) { Proxy.git_diff_merge(diff, diff2); } @@ -517,18 +533,34 @@ private static TreeComparisonHandleRetriever IndexToTree(Repository repo) return (oh, nh, o) => Proxy.git_diff_tree_to_index(repo.Handle, repo.Index.Handle, oh, o); } - private DiffSafeHandle BuildDiffList(ObjectId oldTreeId, ObjectId newTreeId, TreeComparisonHandleRetriever comparisonHandleRetriever, - DiffModifiers diffOptions, IEnumerable paths, ExplicitPathsOptions explicitPathsOptions, + private DiffHandle BuildDiffList( + ObjectId oldTreeId, + ObjectId newTreeId, + TreeComparisonHandleRetriever comparisonHandleRetriever, + DiffModifiers diffOptions, + IEnumerable paths, + ExplicitPathsOptions explicitPathsOptions, CompareOptions compareOptions) { - var matchedPaths = new MatchedPathsAggregator(); var filePaths = repo.ToFilePaths(paths); + MatchedPathsAggregator matchedPaths = null; + + // We can't match paths unless we've got something to match + // against and we're told to do so. + if (filePaths != null && explicitPathsOptions != null) + { + if (explicitPathsOptions.OnUnmatchedPath != null || explicitPathsOptions.ShouldFailOnUnmatchedPath) + { + matchedPaths = new MatchedPathsAggregator(); + } + } + using (GitDiffOptions options = BuildOptions(diffOptions, filePaths, matchedPaths, compareOptions)) { var diffList = comparisonHandleRetriever(oldTreeId, newTreeId, options); - if (explicitPathsOptions != null) + if (matchedPaths != null) { try { @@ -547,13 +579,12 @@ private DiffSafeHandle BuildDiffList(ObjectId oldTreeId, ObjectId newTreeId, Tre } } - private static void DetectRenames(DiffSafeHandle diffList, CompareOptions compareOptions) + private static void DetectRenames(DiffHandle diffList, CompareOptions compareOptions) { var similarityOptions = (compareOptions == null) ? null : compareOptions.Similarity; - if (similarityOptions == null || - similarityOptions.RenameDetectionMode == RenameDetectionMode.Default) + if (similarityOptions == null || similarityOptions.RenameDetectionMode == RenameDetectionMode.Default) { - Proxy.git_diff_find_similar(diffList, null); + Proxy.git_diff_find_similar(diffList, IntPtr.Zero); return; } @@ -564,6 +595,7 @@ private static void DetectRenames(DiffSafeHandle diffList, CompareOptions compar var opts = new GitDiffFindOptions { + Version = 1, RenameThreshold = (ushort)similarityOptions.RenameThreshold, RenameFromRewriteThreshold = (ushort)similarityOptions.RenameFromRewriteThreshold, CopyThreshold = (ushort)similarityOptions.CopyThreshold, @@ -614,9 +646,10 @@ private static void DetectRenames(DiffSafeHandle diffList, CompareOptions compar Proxy.git_diff_find_similar(diffList, opts); } - private static void DispatchUnmatchedPaths(ExplicitPathsOptions explicitPathsOptions, - IEnumerable filePaths, - IEnumerable matchedPaths) + private static void DispatchUnmatchedPaths( + ExplicitPathsOptions explicitPathsOptions, + IEnumerable filePaths, + IEnumerable matchedPaths) { List unmatchedPaths = (filePaths != null ? filePaths.Except(matchedPaths) : Enumerable.Empty()).ToList(); diff --git a/LibGit2Sharp/DiffAlgorithm.cs b/LibGit2Sharp/DiffAlgorithm.cs index 9e89e68e6..07370e5a9 100644 --- a/LibGit2Sharp/DiffAlgorithm.cs +++ b/LibGit2Sharp/DiffAlgorithm.cs @@ -8,7 +8,12 @@ public enum DiffAlgorithm /// /// The basic greedy diff algorithm. /// - Meyers = 0, + Myers = 0, + + /// + /// Use "minimal diff" algorithm when generating patches. + /// + Minimal = 1, /// /// Use "patience diff" algorithm when generating patches. diff --git a/LibGit2Sharp/DiffModifiers.cs b/LibGit2Sharp/DiffModifiers.cs index af9ccb75c..2dd05e43b 100644 --- a/LibGit2Sharp/DiffModifiers.cs +++ b/LibGit2Sharp/DiffModifiers.cs @@ -36,5 +36,16 @@ internal enum DiffModifiers /// diffing against the working directory. /// IncludeIgnored = (1 << 4), + + /// + /// Specifies that no rename heuristics will be used when checking for + /// renames, renames being matched only on unmodified renamed files. + /// + FindExactRenames = (1 << 5), + + /// + /// Specifies that no renames will be searched for when running blame. + /// + FindNoRenames = (1 << 6), } } diff --git a/LibGit2Sharp/DirectReference.cs b/LibGit2Sharp/DirectReference.cs index 00de258a6..b9cc304b3 100644 --- a/LibGit2Sharp/DirectReference.cs +++ b/LibGit2Sharp/DirectReference.cs @@ -18,12 +18,21 @@ protected DirectReference() internal DirectReference(string canonicalName, IRepository repo, ObjectId targetId) : base(repo, canonicalName, targetId.Sha) { - targetBuilder = new Lazy(() => repo.Lookup(targetId)); + targetBuilder = new Lazy(() => + { + if (repo == null) + { + throw new InvalidOperationException("Target requires a local repository"); + } + + return repo.Lookup(targetId); + }); } /// /// Gets the target of this /// + /// Throws if Local Repository is not set. public virtual GitObject Target { get { return targetBuilder.Value; } diff --git a/LibGit2Sharp/EmptyCommitException.cs b/LibGit2Sharp/EmptyCommitException.cs index aa54a5f3c..8cd48e49f 100644 --- a/LibGit2Sharp/EmptyCommitException.cs +++ b/LibGit2Sharp/EmptyCommitException.cs @@ -14,8 +14,7 @@ public class EmptyCommitException : LibGit2SharpException /// Initializes a new instance of the class. /// public EmptyCommitException() - { - } + { } /// /// Initializes a new instance of the class with a specified error message. @@ -23,8 +22,16 @@ public EmptyCommitException() /// A message that describes the error. public EmptyCommitException(string message) : base(message) - { - } + { } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// A composite format string for use in . + /// An object array that contains zero or more objects to format. + public EmptyCommitException(string format, params object[] args) + : base(format, args) + { } /// /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. @@ -33,8 +40,7 @@ public EmptyCommitException(string message) /// The exception that is the cause of the current exception. If the parameter is not a null reference, the current exception is raised in a catch block that handles the inner exception. public EmptyCommitException(string message, Exception innerException) : base(message, innerException) - { - } + { } /// /// Initializes a new instance of the class with a serialized data. @@ -43,7 +49,6 @@ public EmptyCommitException(string message, Exception innerException) /// The that contains contextual information about the source or destination. protected EmptyCommitException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } + { } } } diff --git a/LibGit2Sharp/EntryExistsException.cs b/LibGit2Sharp/EntryExistsException.cs index 9dff4d750..2c46e4acd 100644 --- a/LibGit2Sharp/EntryExistsException.cs +++ b/LibGit2Sharp/EntryExistsException.cs @@ -14,8 +14,7 @@ public class EntryExistsException : LibGit2SharpException /// Initializes a new instance of the class. /// public EntryExistsException() - { - } + { } /// /// Initializes a new instance of the class with a specified error message. @@ -23,8 +22,16 @@ public EntryExistsException() /// A message that describes the error. public EntryExistsException(string message) : base(message) - { - } + { } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// A composite format string for use in . + /// An object array that contains zero or more objects to format. + public EntryExistsException(string format, params object[] args) + : base(format, args) + { } /// /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. @@ -33,8 +40,7 @@ public EntryExistsException(string message) /// The exception that is the cause of the current exception. If the parameter is not a null reference, the current exception is raised in a catch block that handles the inner exception. public EntryExistsException(string message, Exception innerException) : base(message, innerException) - { - } + { } /// /// Initializes a new instance of the class with a serialized data. @@ -43,12 +49,10 @@ public EntryExistsException(string message, Exception innerException) /// The that contains contextual information about the source or destination. protected EntryExistsException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } + { } internal EntryExistsException(string message, GitErrorCode code, GitErrorCategory category) : base(message, code, category) - { - } + { } } } diff --git a/LibGit2Sharp/ExtraDefine.targets b/LibGit2Sharp/ExtraDefine.targets deleted file mode 100644 index b5ba508b5..000000000 --- a/LibGit2Sharp/ExtraDefine.targets +++ /dev/null @@ -1,7 +0,0 @@ - - - - - $(DefineConstants);$(ExtraDefine) - - diff --git a/LibGit2Sharp/FetchHead.cs b/LibGit2Sharp/FetchHead.cs index 75f12ae01..456abedc2 100644 --- a/LibGit2Sharp/FetchHead.cs +++ b/LibGit2Sharp/FetchHead.cs @@ -15,11 +15,20 @@ internal class FetchHead : ReferenceWrapper protected FetchHead() { } - internal FetchHead(Repository repo, string remoteCanonicalName, - string url, ObjectId targetId, bool forMerge, int index) - : base(repo, new DirectReference( - string.Format(CultureInfo.InvariantCulture, "FETCH_HEAD[{0}]", index), - repo, targetId), r => r.CanonicalName) + internal FetchHead( + Repository repo, + string remoteCanonicalName, + string url, + ObjectId targetId, + bool forMerge, + int index) + : base(repo, + new DirectReference(string.Format(CultureInfo.InvariantCulture, + "FETCH_HEAD[{0}]", + index), + repo, + targetId), + r => r.CanonicalName) { Url = url; ForMerge = forMerge; diff --git a/LibGit2Sharp/FetchOptions.cs b/LibGit2Sharp/FetchOptions.cs index fba0a96f8..487baed97 100644 --- a/LibGit2Sharp/FetchOptions.cs +++ b/LibGit2Sharp/FetchOptions.cs @@ -16,5 +16,35 @@ public sealed class FetchOptions : FetchOptionsBase /// 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; } + + /// + /// 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 index 7ad3673e0..751678cf9 100644 --- a/LibGit2Sharp/FetchOptionsBase.cs +++ b/LibGit2Sharp/FetchOptionsBase.cs @@ -8,8 +8,7 @@ namespace LibGit2Sharp public abstract class FetchOptionsBase { internal FetchOptionsBase() - { - } + { } /// /// Handler for network transfer and indexing progress information. @@ -34,6 +33,12 @@ internal FetchOptionsBase() /// 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; } + /// /// Starting to operate on a new repository. /// diff --git a/LibGit2Sharp/FileStatus.cs b/LibGit2Sharp/FileStatus.cs index 191972f35..fbd32affd 100644 --- a/LibGit2Sharp/FileStatus.cs +++ b/LibGit2Sharp/FileStatus.cs @@ -18,34 +18,16 @@ public enum FileStatus /// Unaltered = 0, /* GIT_STATUS_CURRENT */ - /// - /// New file has been added to the Index. It's unknown from the Head. - /// - [Obsolete("This enum member will be removed in the next release. Please use NewInIndex instead.")] - Added = (1 << 0), /* GIT_STATUS_INDEX_NEW */ - /// /// New file has been added to the Index. It's unknown from the Head. /// 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. - /// - [Obsolete("This enum member will be removed in the next release. Please use ModifiedInIndex instead.")] - Staged = (1 << 1), /* GIT_STATUS_INDEX_MODIFIED */ - /// /// New version of a file has been added to the Index. A previous version exists in the Head. /// 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. - /// - [Obsolete("This enum member will be removed in the next release. Please use DeletedFromIndex instead.")] - Removed = (1 << 2), /* GIT_STATUS_INDEX_DELETED */ - /// /// The deletion of a file has been promoted from the working directory to the Index. A previous version exists in the Head. /// @@ -56,56 +38,26 @@ public enum FileStatus /// 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. - /// - [Obsolete("This enum member will be removed in the next release. Please use TypeChangeInIndex instead.")] - StagedTypeChange = (1 << 4), /* GIT_STATUS_INDEX_TYPECHANGE */ - /// /// A change in type for a file has been promoted from the working directory to the Index. A previous version exists in the Head. /// TypeChangeInIndex = (1 << 4), /* GIT_STATUS_INDEX_TYPECHANGE */ - /// - /// New file in the working directory, unknown from the Index and the Head. - /// - [Obsolete("This enum member will be removed in the next release. Please use NewInWorkdir instead.")] - Untracked = (1 << 7), /* GIT_STATUS_WT_NEW */ - /// /// New file in the working directory, unknown from the Index and the Head. /// NewInWorkdir = (1 << 7), /* GIT_STATUS_WT_NEW */ - /// - /// The file has been updated in the working directory. A previous version exists in the Index. - /// - [Obsolete("This enum member will be removed in the next release. Please use ModifiedInWorkdir instead.")] - Modified = (1 << 8), /* GIT_STATUS_WT_MODIFIED */ - /// /// The file has been updated in the working directory. A previous version exists in the Index. /// ModifiedInWorkdir = (1 << 8), /* GIT_STATUS_WT_MODIFIED */ - /// - /// The file has been deleted from the working directory. A previous version exists in the Index. - /// - [Obsolete("This enum member will be removed in the next release. Please use DeletedFromWorkdir instead.")] - Missing = (1 << 9), /* GIT_STATUS_WT_DELETED */ - /// /// The file has been deleted from the working directory. A previous version exists in the Index. /// DeletedFromWorkdir = (1 << 9), /* GIT_STATUS_WT_DELETED */ - /// - /// The file type has been changed in the working directory. A previous version exists in the Index. - /// - [Obsolete("This enum member will be removed in the next release. Please use TypeChangeInWorkdir instead.")] - TypeChanged = (1 << 10), /* GIT_STATUS_WT_TYPECHANGE */ - /// /// The file type has been changed in the working directory. A previous version exists in the Index. /// @@ -125,5 +77,10 @@ public enum FileStatus /// The file is but its name and/or path matches an exclude pattern in a gitignore file. /// Ignored = (1 << 14), /* GIT_STATUS_IGNORED */ + + /// + /// The file is due to a merge. + /// + Conflicted = (1 << 15), /* GIT_STATUS_CONFLICTED */ } } diff --git a/LibGit2Sharp/Filter.cs b/LibGit2Sharp/Filter.cs new file mode 100644 index 000000000..1fd0587e5 --- /dev/null +++ b/LibGit2Sharp/Filter.cs @@ -0,0 +1,403 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + /// + /// A filter is a way to execute code against a file as it moves to and from the git + /// repository and into the working directory. + /// + public abstract class Filter : IEquatable + { + private static readonly LambdaEqualityHelper equalityHelper = + new LambdaEqualityHelper(x => x.Name, x => x.Attributes); + // 64K is optimal buffer size per https://technet.microsoft.com/en-us/library/cc938632.aspx + private const int BufferSize = 64 * 1024; + + /// + /// Initializes a new instance of the class. + /// And allocates the filter natively. + /// The unique name with which this filtered is registered with + /// A list of attributes which this filter applies to + /// + protected Filter(string name, IEnumerable attributes) + { + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + Ensure.ArgumentNotNull(attributes, "attributes"); + + this.name = name; + this.attributes = attributes; + var attributesAsString = string.Join(",", this.attributes.Select(attr => attr.FilterDefinition)); + + gitFilter = new GitFilter + { + attributes = EncodingMarshaler.FromManaged(Encoding.UTF8, attributesAsString), + init = InitializeCallback, + stream = StreamCreateCallback, + }; + } + /// + /// Finalizer called by the , deregisters and frees native memory associated with the registered filter in libgit2. + /// + ~Filter() + { + GlobalSettings.DeregisterFilter(this); + +#if LEAKS_IDENTIFYING + int activeStreamCount = activeStreams.Count; + if (activeStreamCount > 0) + { + Trace.WriteLine(string.Format(CultureInfo.InvariantCulture, "{0} leaked {1} stream handles at finalization", GetType().Name, activeStreamCount)); + } +#endif + } + + private readonly string name; + private readonly IEnumerable attributes; + private readonly GitFilter gitFilter; + private readonly ConcurrentDictionary activeStreams = new ConcurrentDictionary(); + + /// + /// State bag used to keep necessary reference from being + /// garbage collected during filter processing. + /// + private class StreamState + { + public GitWriteStream thisStream; + public GitWriteStream nextStream; + public IntPtr thisPtr; + public IntPtr nextPtr; + public FilterSource filterSource; + public Stream output; + } + + /// + /// The name that this filter was registered with + /// + public string Name + { + get { return name; } + } + + /// + /// The filter filterForAttributes. + /// + public IEnumerable Attributes + { + get { return attributes; } + } + + /// + /// The marshalled filter + /// + internal GitFilter GitFilter + { + get { return gitFilter; } + } + + /// + /// Complete callback on filter + /// + /// This optional callback will be invoked when the upstream filter is + /// closed. Gives the filter a chance to perform any final actions or + /// necissary clean up. + /// + /// The path of the file being filtered + /// The path of the working directory for the owning repository + /// Output to the downstream filter or output writer + protected virtual void Complete(string path, string root, Stream output) + { } + + /// + /// Initialize callback on filter + /// + /// Specified as `filter.initialize`, this is an optional callback invoked + /// before a filter is first used. It will be called once at most. + /// + /// If non-NULL, the filter's `initialize` callback will be invoked right + /// before the first use of the filter, so you can defer expensive + /// initialization operations (in case the library is being used in a way + /// that doesn't need the filter. + /// + protected virtual void Initialize() + { } + + /// + /// Indicates that a filter is going to be applied for the given file for + /// the given mode. + /// + /// The path of the file being filtered + /// The path of the working directory for the owning repository + /// The filter mode + protected virtual void Create(string path, string root, FilterMode mode) + { } + + /// + /// Clean the input stream and write to the output stream. + /// + /// The path of the file being filtered + /// The path of the working directory for the owning repository + /// Input from the upstream filter or input reader + /// Output to the downstream filter or output writer + protected virtual void Clean(string path, string root, Stream input, Stream output) + { + input.CopyTo(output); + } + + /// + /// Smudge the input stream and write to the output stream. + /// + /// The path of the file being filtered + /// The path of the working directory for the owning repository + /// Input from the upstream filter or input reader + /// Output to the downstream filter or output writer + protected virtual void Smudge(string path, string root, Stream input, Stream output) + { + input.CopyTo(output); + } + + /// + /// Determines whether the specified is equal to the current . + /// + /// The to compare with the current . + /// True if the specified is equal to the current ; otherwise, false. + public override bool Equals(object obj) + { + return Equals(obj as Filter); + } + + /// + /// Determines whether the specified is equal to the current . + /// + /// The to compare with the current . + /// True if the specified is equal to the current ; otherwise, false. + public bool Equals(Filter other) + { + return equalityHelper.Equals(this, other); + } + + /// + /// Returns the hash code for this instance. + /// + /// A 32-bit signed integer hash code. + public override int GetHashCode() + { + return equalityHelper.GetHashCode(this); + } + + /// + /// Tests if two are equal. + /// + /// First to compare. + /// Second to compare. + /// True if the two objects are equal; false otherwise. + public static bool operator ==(Filter left, Filter right) + { + return Equals(left, right); + } + + /// + /// Tests if two are different. + /// + /// First to compare. + /// Second to compare. + /// True if the two objects are different; false otherwise. + public static bool operator !=(Filter left, Filter right) + { + return !Equals(left, right); + } + + /// + /// Initialize callback on filter + /// + /// Specified as `filter.initialize`, this is an optional callback invoked + /// before a filter is first used. It will be called once at most. + /// + /// If non-NULL, the filter's `initialize` callback will be invoked right + /// before the first use of the filter, so you can defer expensive + /// initialization operations (in case libgit2 is being used in a way that doesn't need the filter). + /// + int InitializeCallback(IntPtr filterPointer) + { + int result = 0; + try + { + Initialize(); + } + catch (Exception exception) + { + Log.Write(LogLevel.Error, "Filter.InitializeCallback exception"); + Log.Write(LogLevel.Error, exception.ToString()); + Proxy.giterr_set_str(GitErrorCategory.Filter, exception); + result = (int)GitErrorCode.Error; + } + return result; + } + + int StreamCreateCallback(out IntPtr git_writestream_out, GitFilter self, IntPtr payload, IntPtr filterSourcePtr, IntPtr git_writestream_next) + { + int result = 0; + var state = new StreamState(); + + try + { + Ensure.ArgumentNotZeroIntPtr(filterSourcePtr, "filterSourcePtr"); + Ensure.ArgumentNotZeroIntPtr(git_writestream_next, "git_writestream_next"); + + state.thisStream = new GitWriteStream(); + state.thisStream.close = StreamCloseCallback; + state.thisStream.write = StreamWriteCallback; + state.thisStream.free = StreamFreeCallback; + + state.thisPtr = Marshal.AllocHGlobal(Marshal.SizeOf(state.thisStream)); + Marshal.StructureToPtr(state.thisStream, state.thisPtr, false); + + state.nextPtr = git_writestream_next; + state.nextStream = Marshal.PtrToStructure(state.nextPtr); + + state.filterSource = FilterSource.FromNativePtr(filterSourcePtr); + state.output = new WriteStream(state.nextStream, state.nextPtr); + + Create(state.filterSource.Path, state.filterSource.Root, state.filterSource.SourceMode); + + if (!activeStreams.TryAdd(state.thisPtr, state)) + { + // AFAICT this is a theoretical error that could only happen if we manage + // to free the stream pointer but fail to remove the dictionary entry. + throw new InvalidOperationException("Overlapping stream pointers"); + } + } + catch (Exception exception) + { + // unexpected failures means memory clean up required + if (state.thisPtr != IntPtr.Zero) + { + Marshal.FreeHGlobal(state.thisPtr); + state.thisPtr = IntPtr.Zero; + } + + Log.Write(LogLevel.Error, "Filter.StreamCreateCallback exception"); + Log.Write(LogLevel.Error, exception.ToString()); + Proxy.giterr_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", "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.giterr_set_str(GitErrorCategory.Filter, exception); + result = (int)GitErrorCode.Error; + } + + return result; + } + + void StreamFreeCallback(IntPtr stream) + { + StreamState state; + + try + { + Ensure.ArgumentNotZeroIntPtr(stream, "stream"); + + if (!activeStreams.TryRemove(stream, out state)) + { + throw new ArgumentException("Double free or invalid stream pointer", "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", "stream"); + } + + Ensure.ArgumentIsExpectedIntPtr(stream, state.thisPtr, "stream"); + + using (UnmanagedMemoryStream input = new UnmanagedMemoryStream((byte*)buffer.ToPointer(), (long)len)) + using (BufferedStream outputBuffer = new BufferedStream(state.output, BufferSize)) + { + switch (state.filterSource.SourceMode) + { + case FilterMode.Clean: + Clean(state.filterSource.Path, state.filterSource.Root, input, outputBuffer); + break; + + case FilterMode.Smudge: + Smudge(state.filterSource.Path, state.filterSource.Root, input, outputBuffer); + break; + + default: + Proxy.giterr_set_str(GitErrorCategory.Filter, "Unexpected filter mode."); + return (int)GitErrorCode.Ambiguous; + } + } + } + catch (Exception exception) + { + Log.Write(LogLevel.Error, "Filter.StreamWriteCallback exception"); + Log.Write(LogLevel.Error, exception.ToString()); + Proxy.giterr_set_str(GitErrorCategory.Filter, exception); + result = (int)GitErrorCode.Error; + } + + return result; + } + } +} diff --git a/LibGit2Sharp/FilterAttributeEntry.cs b/LibGit2Sharp/FilterAttributeEntry.cs new file mode 100644 index 000000000..117523d3e --- /dev/null +++ b/LibGit2Sharp/FilterAttributeEntry.cs @@ -0,0 +1,53 @@ +using System; +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + /// + /// The definition for a given filter found in the .gitattributes file. + /// The filter definition will result as 'filter=filterName' + /// + /// In the .gitattributes file a filter will be matched to a pathspec like so + /// '*.txt filter=filterName' + /// + public class FilterAttributeEntry + { + private const string AttributeFilterDefinition = "filter="; + + private readonly string filterDefinition; + + /// + /// For testing purposes + /// + protected FilterAttributeEntry() { } + + /// + /// The name of the filter found in a .gitattributes file. + /// + /// The name of the filter as found in the .gitattributes file without the "filter=" prefix + /// + /// "filter=" will be prepended to the filterDefinition, therefore the "filter=" portion of the filter + /// name shouldbe omitted on declaration. Inclusion of the "filter=" prefix will cause the FilterDefinition to + /// fail to match the .gitattributes entry and thefore no be invoked correctly. + /// + public FilterAttributeEntry(string filterName) + { + Ensure.ArgumentNotNullOrEmptyString(filterName, "filterName"); + if (filterName.StartsWith("filter=", StringComparison.OrdinalIgnoreCase)) + { + throw new ArgumentException("The filterName parameter should not begin with \"filter=\"", filterName); + } + + filterName = AttributeFilterDefinition + filterName; + this.filterDefinition = filterName; + } + + /// + /// The filter name in the form of 'filter=filterName' + /// + public virtual string FilterDefinition + { + get { return filterDefinition; } + } + } +} diff --git a/LibGit2Sharp/FilterMode.cs b/LibGit2Sharp/FilterMode.cs new file mode 100644 index 000000000..31a9546c1 --- /dev/null +++ b/LibGit2Sharp/FilterMode.cs @@ -0,0 +1,23 @@ +namespace LibGit2Sharp +{ + /// + /// These values control which direction of change is with which which a filter is being applied. + /// + /// + /// These enum values must be identical to the values in Libgit2 filter_mode_t found in filter.h + /// + public enum FilterMode + { + /// + /// Smudge occurs when exporting a file from the Git object database to the working directory. + /// For example, a file would be smudged during a checkout operation. + /// + Smudge = 0, + + /// + /// Clean occurs when importing a file from the working directory to the Git object database. + /// For example, a file would be cleaned when staging a file. + /// + Clean = 1, + } +} diff --git a/LibGit2Sharp/FilterRegistration.cs b/LibGit2Sharp/FilterRegistration.cs new file mode 100644 index 000000000..f6c1e31d0 --- /dev/null +++ b/LibGit2Sharp/FilterRegistration.cs @@ -0,0 +1,86 @@ +using System; +using System.Runtime.InteropServices; +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + /// + /// An object representing the registration of a Filter type with libgit2 + /// + public sealed class FilterRegistration + { + /// + /// Maximum priority value a filter can have. A value of 200 will be run last on checkout and first on checkin. + /// + public const int FilterPriorityMax = 200; + /// + /// Minimum priority value a filter can have. A value of 0 will be run first on checkout and last on checkin. + /// + public const int FilterPriorityMin = 0; + + /// + /// + /// + /// + /// + internal FilterRegistration(Filter filter, int priority) + { + System.Diagnostics.Debug.Assert(filter != null); + System.Diagnostics.Debug.Assert(priority >= FilterPriorityMin && priority <= FilterPriorityMax); + + Filter = filter; + Priority = priority; + + // marshal the git_filter strucutre into native memory + FilterPointer = Marshal.AllocHGlobal(Marshal.SizeOf(filter.GitFilter)); + Marshal.StructureToPtr(filter.GitFilter, FilterPointer, false); + + // register the filter with the native libary + Proxy.git_filter_register(filter.Name, FilterPointer, priority); + } + /// + /// Finalizer called by the , deregisters and frees native memory associated with the registered filter in libgit2. + /// + ~FilterRegistration() + { + // deregister the filter + GlobalSettings.DeregisterFilter(this); + // clean up native allocations + Free(); + } + + /// + /// Gets if the registration and underlying filter are valid. + /// + public bool IsValid { get { return !freed; } } + /// + /// The registerd filters + /// + public readonly Filter Filter; + /// + /// The name of the filter in the libgit2 registry + /// + public string Name { get { return Filter.Name; } } + /// + /// The priority of the registered filter + /// + public readonly int Priority; + + private readonly IntPtr FilterPointer; + + private bool freed; + + internal void Free() + { + if (!freed) + { + // unregister the filter with the native libary + Proxy.git_filter_unregister(Filter.Name); + // release native memory + Marshal.FreeHGlobal(FilterPointer); + // remember to not do this twice + freed = true; + } + } + } +} diff --git a/LibGit2Sharp/FilterSource.cs b/LibGit2Sharp/FilterSource.cs new file mode 100644 index 000000000..ed551aba8 --- /dev/null +++ b/LibGit2Sharp/FilterSource.cs @@ -0,0 +1,66 @@ +using System; +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + /// + /// A filter source - describes the direction of filtering and the file being filtered. + /// + public class FilterSource + { + /// + /// Needed for mocking purposes + /// + protected FilterSource() { } + + internal unsafe FilterSource(FilePath path, FilterMode mode, git_filter_source* source) + { + SourceMode = mode; + ObjectId = ObjectId.BuildFromPtr(&source->oid); + Path = path.Native; + Root = Proxy.git_repository_workdir(new IntPtr(source->repository)).Native; + } + + /// + /// Take an unmanaged pointer and convert it to filter source callback paramater + /// + /// + /// + internal static unsafe FilterSource FromNativePtr(IntPtr ptr) + { + return FromNativePtr((git_filter_source*) ptr.ToPointer()); + } + + /// + /// Take an unmanaged pointer and convert it to filter source callback paramater + /// + /// + /// + internal static unsafe FilterSource FromNativePtr(git_filter_source* ptr) + { + FilePath path = LaxFilePathMarshaler.FromNative(ptr->path) ?? FilePath.Empty; + FilterMode gitFilterSourceMode = Proxy.git_filter_source_mode(ptr); + return new FilterSource(path, gitFilterSourceMode, ptr); + } + + /// + /// The filter mode for current file being filtered + /// + public virtual FilterMode SourceMode { get; private set; } + + /// + /// The relative path to the file + /// + public virtual string Path { get; private set; } + + /// + /// The blob id + /// + public virtual ObjectId ObjectId { get; private set; } + + /// + /// The working directory + /// + public virtual string Root { get; private set; } + } +} diff --git a/LibGit2Sharp/FilteringOptions.cs b/LibGit2Sharp/FilteringOptions.cs index f06d5a2a4..22988e62e 100644 --- a/LibGit2Sharp/FilteringOptions.cs +++ b/LibGit2Sharp/FilteringOptions.cs @@ -22,6 +22,6 @@ public FilteringOptions(string hintPath) /// The path to "hint" to the filters will be used to apply /// attributes. /// - public string HintPath { get; private set; } + public string HintPath { get; private set; } } } diff --git a/LibGit2Sharp/FollowFilter.cs b/LibGit2Sharp/FollowFilter.cs deleted file mode 100644 index 7bd607082..000000000 --- a/LibGit2Sharp/FollowFilter.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace LibGit2Sharp -{ - /// - /// Criteria used to order the commits of the repository when querying its history. - /// - /// The commits will be enumerated from the current HEAD of the repository. - /// - /// - public sealed class FollowFilter - { - private static readonly List AllowedSortStrategies = new List - { - CommitSortStrategies.Topological, - CommitSortStrategies.Time, - CommitSortStrategies.Topological | CommitSortStrategies.Time - }; - - private CommitSortStrategies _sortBy; - - /// - /// Initializes a new instance of . - /// - public FollowFilter() - { - SortBy = CommitSortStrategies.Time; - } - - /// - /// The ordering strategy to use. - /// - /// By default, the commits are shown in reverse chronological order. - /// - /// - /// Only 'Topological', 'Time', or 'Topological | Time' are allowed. - /// - /// - public CommitSortStrategies SortBy - { - get { return _sortBy; } - - set - { - if (!AllowedSortStrategies.Contains(value)) - { - throw new ArgumentException( - "Unsupported sort strategy. Only 'Topological', 'Time', or 'Topological | Time' are allowed.", - "value"); - } - - _sortBy = value; - } - } - } -} diff --git a/LibGit2Sharp/GitLink.cs b/LibGit2Sharp/GitLink.cs index 398ab3217..f03b1d719 100644 --- a/LibGit2Sharp/GitLink.cs +++ b/LibGit2Sharp/GitLink.cs @@ -16,8 +16,7 @@ protected GitLink() internal GitLink(Repository repo, ObjectId id) : base(repo, id) - { - } + { } private string DebuggerDisplay { diff --git a/LibGit2Sharp/GitObject.cs b/LibGit2Sharp/GitObject.cs index 5e11489a2..218f8f141 100644 --- a/LibGit2Sharp/GitObject.cs +++ b/LibGit2Sharp/GitObject.cs @@ -15,12 +15,20 @@ public abstract class GitObject : IEquatable, IBelongToARepository { internal static IDictionary TypeToKindMap = new Dictionary - { - { typeof(Commit), ObjectType.Commit }, - { typeof(Tree), ObjectType.Tree }, - { typeof(Blob), ObjectType.Blob }, - { typeof(TagAnnotation), ObjectType.Tag }, - }; + { + { typeof(Commit), ObjectType.Commit }, + { typeof(Tree), ObjectType.Tree }, + { typeof(Blob), ObjectType.Blob }, + { typeof(TagAnnotation), ObjectType.Tag }, + }; + internal static IDictionary TypeToGitKindMap = + new Dictionary + { + { typeof(Commit), GitObjectType.Commit }, + { typeof(Tree), GitObjectType.Tree }, + { typeof(Blob), GitObjectType.Blob }, + { typeof(TagAnnotation), GitObjectType.Tag }, + }; private static readonly LambdaEqualityHelper equalityHelper = new LambdaEqualityHelper(x => x.Id); @@ -28,7 +36,7 @@ public abstract class GitObject : IEquatable, IBelongToARepository /// /// The containing the object. /// - protected readonly Repository repo; + internal readonly Repository repo; /// /// Needed for mocking purposes. @@ -60,36 +68,60 @@ public virtual string Sha get { return 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); } } + /// + /// 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 . /// diff --git a/LibGit2Sharp/GitObjectMetadata.cs b/LibGit2Sharp/GitObjectMetadata.cs index 3e0e939e6..348fa4daa 100644 --- a/LibGit2Sharp/GitObjectMetadata.cs +++ b/LibGit2Sharp/GitObjectMetadata.cs @@ -10,7 +10,7 @@ public sealed class GitObjectMetadata private readonly GitObjectType type; /// - /// Size of the Object + /// Size of the Object /// public long Size { get; private set; } diff --git a/LibGit2Sharp/GlobalSettings.cs b/LibGit2Sharp/GlobalSettings.cs index 0aebfc51d..fda9e93a0 100644 --- a/LibGit2Sharp/GlobalSettings.cs +++ b/LibGit2Sharp/GlobalSettings.cs @@ -1,6 +1,8 @@ using System; +using System.Collections.Generic; using System.IO; using System.Reflection; +using System.Runtime.InteropServices; using LibGit2Sharp.Core; namespace LibGit2Sharp @@ -11,39 +13,121 @@ namespace LibGit2Sharp public static class GlobalSettings { private static readonly Lazy version = new Lazy(Version.Build); + private static readonly Dictionary registeredFilters; + private static readonly bool nativeLibraryPathAllowed; private static LogConfiguration logConfiguration = LogConfiguration.None; private static string nativeLibraryPath; private static bool nativeLibraryPathLocked; + private static string nativeLibraryDefaultPath; + + internal class SmartSubtransportData + { + internal bool isCustom; + internal SmartSubtransportRegistrationData defaultSubtransport; + } + + private static readonly Dictionary smartSubtransportData = new Dictionary(); static GlobalSettings() { - if (Platform.OperatingSystem == OperatingSystemType.Windows) + bool netFX = Platform.IsRunningOnNetFramework(); + bool netCore = Platform.IsRunningOnNetCore(); + + nativeLibraryPathAllowed = netFX || netCore; + + if (netFX) { - string managedPath = new Uri(Assembly.GetExecutingAssembly().EscapedCodeBase).LocalPath; - nativeLibraryPath = Path.Combine(Path.GetDirectoryName(managedPath), "NativeBinaries"); + // For .NET Framework apps the dependencies are deployed to lib/win32/{architecture} directory + nativeLibraryDefaultPath = Path.Combine(GetExecutingAssemblyDirectory(), "lib", "win32"); } + else + { + nativeLibraryDefaultPath = null; + } + + registeredFilters = new Dictionary(); + } + + 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; } /// /// Returns information related to the current LibGit2Sharp /// library. /// - public static Version Version + public static Version Version => version.Value; + + private static SmartSubtransportData GetOrCreateSmartSubtransportData(string scheme) { - get + Ensure.ArgumentNotNull(scheme, "scheme"); + + lock (smartSubtransportData) { - return version.Value; + if (!smartSubtransportData.ContainsKey(scheme)) + { + smartSubtransportData[scheme] = new SmartSubtransportData(); + } + + return smartSubtransportData[scheme]; + } + } + + internal static SmartSubtransportRegistration RegisterDefaultSmartSubtransport(string scheme) + where T : SmartSubtransport, new() + { + Ensure.ArgumentNotNull(scheme, "scheme"); + + lock (smartSubtransportData) + { + var data = GetOrCreateSmartSubtransportData(scheme); + + if (data.defaultSubtransport != null) + { + throw new Exception(string.Format("A default subtransport is already configured for {0}", scheme)); + } + + var registration = new SmartSubtransportRegistration(scheme); + + if (!data.isCustom) + { + RegisterSmartSubtransportInternal(registration); + } + + data.defaultSubtransport = registration; + + return registration; } } /// /// Registers a new as a custom - /// smart-protocol transport with libgit2. Any Git remote with + /// smart-protocol transport with libgit2. Any Git remote with /// the scheme registered will delegate to the given transport - /// for all communication with the server. use this transport to communicate - /// with the server This is not commonly + /// for all communication with the server. This is not commonly /// used: some callers may want to re-use an existing connection to /// perform fetch / push operations to a remote. /// @@ -57,22 +141,53 @@ public static SmartSubtransportRegistration RegisterSmartSubtransport(stri { Ensure.ArgumentNotNull(scheme, "scheme"); - var registration = new SmartSubtransportRegistration(scheme); + lock (smartSubtransportData) + { + var data = GetOrCreateSmartSubtransportData(scheme); + + if (data.isCustom) + { + throw new EntryExistsException(string.Format("A smart subtransport is already registered for {0}", scheme)); + } + + if (data.defaultSubtransport != null) + { + Proxy.git_transport_unregister(scheme); + } + + var previousValue = data.isCustom; + data.isCustom = true; + var registration = new SmartSubtransportRegistration(scheme); + + try + { + RegisterSmartSubtransportInternal(registration); + } + catch + { + data.isCustom = previousValue; + throw; + } + + return registration; + } + } + + private static void RegisterSmartSubtransportInternal(SmartSubtransportRegistration registration) + where T : SmartSubtransport, new() + { try { - Proxy.git_transport_register( - registration.Scheme, - registration.FunctionPointer, - registration.RegistrationPointer); + Proxy.git_transport_register(registration.Scheme, + registration.FunctionPointer, + registration.RegistrationPointer); } - catch (Exception) + catch { registration.Free(); throw; } - - return registration; } /// @@ -86,6 +201,59 @@ public static void UnregisterSmartSubtransport(SmartSubtransportRegistration< { Ensure.ArgumentNotNull(registration, "registration"); + var scheme = registration.Scheme; + + lock (smartSubtransportData) + { + var data = GetOrCreateSmartSubtransportData(scheme); + + if (!data.isCustom) + { + throw new NotFoundException(string.Format("No smart subtransport has been registered for {0}", scheme)); + } + + UnregisterSmartSubtransportInternal(registration); + + data.isCustom = false; + + if (data.defaultSubtransport != null) + { + var defaultRegistration = data.defaultSubtransport; + + Proxy.git_transport_register(defaultRegistration.Scheme, + defaultRegistration.FunctionPointer, + defaultRegistration.RegistrationPointer); + } + } + } + + internal static void UnregisterDefaultSmartSubtransport(SmartSubtransportRegistration registration) + where T : SmartSubtransport, new() + { + Ensure.ArgumentNotNull(registration, "registration"); + + var scheme = registration.Scheme; + + lock (smartSubtransportData) + { + if (!smartSubtransportData.ContainsKey(scheme)) + { + throw new Exception(string.Format("No default smart subtransport has been registered for {0}", scheme)); + } + + if (registration != smartSubtransportData[scheme].defaultSubtransport) + { + throw new Exception(string.Format("The given smart subtransport is not the default for {0}", scheme)); + } + + smartSubtransportData.Remove(scheme); + UnregisterSmartSubtransportInternal(registration); + } + } + + private static void UnregisterSmartSubtransportInternal(SmartSubtransportRegistration registration) + where T : SmartSubtransport, new() + { Proxy.git_transport_unregister(registration.Scheme); registration.Free(); } @@ -124,35 +292,40 @@ public static LogConfiguration LogConfiguration } /// - /// Sets a hint path for searching for native binaries: when - /// specified, native binaries will first be searched in a - /// subdirectory of the given path corresponding to the architecture - /// (eg, "x86" or "amd64") before falling back to the default - /// path ("NativeBinaries\x86" or "NativeBinaries\amd64" next - /// to the application). + /// 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. + /// On .NET Framework a subdirectory corresponding to the architecture ("x86" or "x64") is appended, + /// otherwise the native library is expected to be found in the directory as specified. + /// + /// 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 Unix platforms: see your dynamic - /// library loader's documentation for details. + /// and is not available on other platforms than .NET Framework and .NET Core. + /// + /// + /// If not specified on .NET Framework it defaults to lib/win32 subdirectory + /// of the directory where this assembly is loaded from. /// /// public static string NativeLibraryPath { - get - { - if (Platform.OperatingSystem != OperatingSystemType.Windows) - { - throw new LibGit2SharpException("Querying the native hint path is only supported on Windows platforms"); - } + get { + lock (smartSubtransportData) { + if (!nativeLibraryPathAllowed) { + throw new LibGit2SharpException ("Querying the native hint path is only supported on .NET Framework and .NET Core platforms"); + } - return nativeLibraryPath; + return nativeLibraryPath ?? nativeLibraryDefaultPath; + } } - set { - if (Platform.OperatingSystem != OperatingSystemType.Windows) + if (!nativeLibraryPathAllowed) { - throw new LibGit2SharpException("Setting the native hint path is only supported on Windows platforms"); + throw new LibGit2SharpException("Setting the native hint path is only supported on .NET Framework and .NET Core platforms"); } if (nativeLibraryPathLocked) @@ -160,14 +333,206 @@ public static string NativeLibraryPath throw new LibGit2SharpException("You cannot set the native library path after it has been loaded"); } - nativeLibraryPath = value; + try + { + nativeLibraryPath = Path.GetFullPath(value); + } + catch (Exception e) + { + throw new LibGit2SharpException(e.Message); + } } } internal static string GetAndLockNativeLibraryPath() { nativeLibraryPathLocked = true; - return nativeLibraryPath; + string result = nativeLibraryPath ?? nativeLibraryDefaultPath; + return Platform.IsRunningOnNetFramework() ? Path.Combine(result, Platform.ProcessorArchitecture) : result; + } + + /// + /// 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("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); + } + + /// + /// Gets the user-agent string used by libgit2. + /// + /// The user-agent string. + /// + /// + public static string GetUserAgent() + { + return Proxy.git_libgit2_opts_get_user_agent(); } } } diff --git a/LibGit2Sharp/Handlers.cs b/LibGit2Sharp/Handlers.cs index 196b438fd..7e0b572c4 100644 --- a/LibGit2Sharp/Handlers.cs +++ b/LibGit2Sharp/Handlers.cs @@ -1,4 +1,6 @@ -using System; +using System.Collections.Generic; +using System.Security.Cryptography.X509Certificates; + namespace LibGit2Sharp.Handlers { /// @@ -31,6 +33,15 @@ namespace LibGit2Sharp.Handlers /// 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. /// @@ -71,6 +82,13 @@ namespace LibGit2Sharp.Handlers /// True to continue, false to cancel. public delegate bool PackBuilderProgressHandler(PackBuilderStage stage, int current, int total); + /// + /// Provides information about what updates will be performed before a push occurs + /// + /// List of updates about to be performed via push + /// True to continue, false to cancel. + public delegate bool PrePushHandler(IEnumerable updates); + /// /// Delegate definition to handle reporting errors when updating references on the remote. /// @@ -112,6 +130,25 @@ namespace LibGit2Sharp.Handlers /// 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. /// diff --git a/LibGit2Sharp/HistoryDivergence.cs b/LibGit2Sharp/HistoryDivergence.cs index 7c54b6bec..262c09c15 100644 --- a/LibGit2Sharp/HistoryDivergence.cs +++ b/LibGit2Sharp/HistoryDivergence.cs @@ -64,10 +64,7 @@ internal HistoryDivergence(Repository repo, Commit one, Commit another) /// public virtual Commit CommonAncestor { - get - { - return commonAncestor.Value; - } + get { return commonAncestor.Value; } } } diff --git a/LibGit2Sharp/IDiffResult.cs b/LibGit2Sharp/IDiffResult.cs new file mode 100644 index 000000000..ed6e521fd --- /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 457ad2fa6..ab8a92136 100644 --- a/LibGit2Sharp/IQueryableCommitLog.cs +++ b/LibGit2Sharp/IQueryableCommitLog.cs @@ -28,24 +28,7 @@ public interface IQueryableCommitLog : ICommitLog /// 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, FollowFilter filter); + IEnumerable QueryBy(string path, CommitFilter filter); - /// - /// Find the best possible merge base given two s. - /// - /// The first . - /// The second . - /// The merge base or null if none found. - [Obsolete("This method will be removed in the next release. Please use ObjectDatabase.FindMergeBase() instead.")] - Commit FindMergeBase(Commit first, Commit second); - - /// - /// 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. - [Obsolete("This method will be removed in the next release. Please use ObjectDatabase.FindMergeBase() instead.")] - Commit FindMergeBase(IEnumerable commits, MergeBaseFindingStrategy strategy); } } diff --git a/LibGit2Sharp/IRepository.cs b/LibGit2Sharp/IRepository.cs index 391cce1e1..fd19f9659 100644 --- a/LibGit2Sharp/IRepository.cs +++ b/LibGit2Sharp/IRepository.cs @@ -52,7 +52,7 @@ public interface IRepository : IDisposable /// /// Provides access to diffing functionalities to show changes between the working tree and the index or a tree, changes between the index and a tree, changes between two trees, or changes between two files on disk. /// - Diff Diff {get;} + Diff Diff { get; } /// /// Gets the database. @@ -70,39 +70,17 @@ public interface IRepository : IDisposable SubmoduleCollection Submodules { get; } /// - /// Checkout the commit pointed at by the tip of the specified . - /// - /// If this commit is the current tip of the branch as it exists in the repository, the HEAD - /// will point to this branch. Otherwise, the HEAD will be detached, pointing at the commit sha. - /// - /// - /// The to check out. - /// controlling checkout behavior. - /// The that was checked out. - Branch Checkout(Branch branch, CheckoutOptions options); - - /// - /// Checkout the specified branch, reference or SHA. - /// - /// If the committishOrBranchSpec parameter resolves to a branch name, then the checked out HEAD will - /// will point to the branch. Otherwise, the HEAD will be detached, pointing at the commit sha. - /// + /// Worktrees in the repository. /// - /// A revparse spec for the commit or branch to checkout. - /// controlling checkout behavior. - /// The that was checked out. - Branch Checkout(string committishOrBranchSpec, CheckoutOptions options); + WorktreeCollection Worktrees { get; } /// - /// Checkout the specified . - /// - /// Will detach the HEAD and make it point to this commit sha. - /// + /// Checkout the specified tree. /// - /// The to check out. - /// controlling checkout behavior. - /// The that was checked out. - Branch Checkout(Commit commit, CheckoutOptions options); + /// 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. @@ -166,16 +144,13 @@ public interface IRepository : IDisposable void Reset(ResetMode resetMode, Commit commit); /// - /// Replaces entries in the with entries from the specified commit. + /// Sets to the specified commit and optionally resets the and + /// the content of the working tree to match. /// + /// Flavor of reset operation to perform. /// The target commit object. - /// The list of paths (either files or directories) that should be considered. - /// - /// If set, the passed will be treated as explicit paths. - /// Use these options to determine how unmatched explicit paths should be handled. - /// - [Obsolete("This method will be removed in the next release. Please use Index.Replace() instead.")] - void Reset(Commit commit, IEnumerable paths, ExplicitPathsOptions explicitPathsOptions); + /// Collection of parameters controlling checkout behavior. + void Reset(ResetMode resetMode, Commit commit, CheckoutOptions options); /// /// Clean the working tree by removing files that are not under version control. @@ -218,6 +193,11 @@ public interface IRepository : IDisposable /// The of the merge. MergeResult Merge(string committish, Signature merger, MergeOptions options); + /// + /// Access to Rebase functionality. + /// + Rebase Rebase { get; } + /// /// Merge the reference that was recently fetched. This will merge /// the branch on the fetched remote that corresponded to the @@ -262,104 +242,6 @@ public interface IRepository : IDisposable /// The blame for the file. BlameHunkCollection Blame(string path, BlameOptions options); - /// - /// Promotes to the staging area the latest modifications of a file in the working directory (addition, updation or removal). - /// - /// If this path is ignored by configuration then it will not be staged unless is unset. - /// - /// The path of the file within the working directory. - /// Determines how paths will be staged. - void Stage(string path, StageOptions 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 collection of paths of the files within the working directory. - /// Determines how paths will be staged. - void Stage(IEnumerable paths, StageOptions stageOptions); - - /// - /// Removes from the staging area all the modifications of a file since the latest commit (addition, updation or removal). - /// - /// The path of the file within the working directory. - /// - /// The passed will be treated as explicit paths. - /// Use these options to determine how unmatched explicit paths should be handled. - /// - void Unstage(string path, ExplicitPathsOptions explicitPathsOptions); - - /// - /// 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. - /// - /// The passed will be treated as explicit paths. - /// Use these options to determine how unmatched explicit paths should be handled. - /// - void Unstage(IEnumerable paths, ExplicitPathsOptions explicitPathsOptions); - - /// - /// Moves and/or renames a file in the working directory and promotes the change to the staging area. - /// - /// The path of the file within the working directory which has to be moved/renamed. - /// The target path of the file within the working directory. - void Move(string sourcePath, string destinationPath); - - /// - /// Moves and/or renames a collection of files in the working directory and promotes the changes to the staging area. - /// - /// 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. - void Move(IEnumerable sourcePaths, IEnumerable destinationPaths); - - /// - /// 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 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. - /// - void Remove(string path, bool removeFromWorkingDirectory, ExplicitPathsOptions 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. - /// - /// - /// 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 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. - /// - void Remove(IEnumerable paths, bool removeFromWorkingDirectory, ExplicitPathsOptions explicitPathsOptions); - /// /// Retrieves the state of a file in the working directory, comparing it against the staging area and the latest commit. /// @@ -383,7 +265,7 @@ public interface IRepository : IDisposable /// /// /// Optionally, the parameter allow to tweak the - /// search strategy (considering lightweith tags, or even branches as reference points) + /// search strategy (considering lightweight tags, or even branches as reference points) /// and the formatting of the returned identifier. /// /// @@ -391,5 +273,14 @@ public interface IRepository : IDisposable /// 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 index 207e24267..faa4ec884 100644 --- a/LibGit2Sharp/Identity.cs +++ b/LibGit2Sharp/Identity.cs @@ -1,4 +1,5 @@ using LibGit2Sharp.Core; +using LibGit2Sharp.Core.Handles; namespace LibGit2Sharp { @@ -20,7 +21,7 @@ public Identity(string name, string email) Ensure.ArgumentNotNullOrEmptyString(name, "name"); Ensure.ArgumentNotNullOrEmptyString(email, "email"); Ensure.ArgumentDoesNotContainZeroByte(name, "name"); - Ensure.ArgumentDoesNotContainZeroByte(name, "email"); + Ensure.ArgumentDoesNotContainZeroByte(email, "email"); _name = name; _email = email; @@ -41,5 +42,29 @@ public string Name { get { return _name; } } + + internal SignatureHandle BuildNowSignatureHandle() + { + return Proxy.git_signature_now(Name, Email); + } + } + + internal static class IdentityHelpers + { + /// + /// Build the handle for the Indentity with the current time, or return a handle + /// to an empty signature. + /// + /// + /// + public static unsafe SignatureHandle SafeBuildNowSignatureHandle(this Identity identity) + { + if (identity == null) + { + return new SignatureHandle(null, false); + } + + return identity.BuildNowSignatureHandle(); + } } } diff --git a/LibGit2Sharp/Index.cs b/LibGit2Sharp/Index.cs index 36dd60b7b..46d756e04 100644 --- a/LibGit2Sharp/Index.cs +++ b/LibGit2Sharp/Index.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Globalization; +using System.Runtime.InteropServices; using LibGit2Sharp.Core; using LibGit2Sharp.Core.Handles; @@ -15,7 +16,7 @@ namespace LibGit2Sharp [DebuggerDisplay("{DebuggerDisplay,nq}")] public class Index : IEnumerable { - private readonly IndexSafeHandle handle; + private readonly IndexHandle handle; private readonly Repository repo; private readonly ConflictCollection conflicts; @@ -25,13 +26,16 @@ public class Index : IEnumerable protected Index() { } - internal Index(Repository repo) + internal Index(IndexHandle handle, Repository repo) { this.repo = repo; - - handle = Proxy.git_repository_index(repo.Handle); + this.handle = handle; conflicts = new ConflictCollection(this); + } + internal Index(Repository repo) + : this(Proxy.git_repository_index(repo.Handle), repo) + { repo.RegisterForCleanup(handle); } @@ -46,7 +50,7 @@ internal Index(Repository repo, string indexPath) repo.RegisterForCleanup(handle); } - internal IndexSafeHandle Handle + internal IndexHandle Handle { get { return handle; } } @@ -70,22 +74,22 @@ public virtual bool IsFullyMerged /// /// Gets the with the specified relative path. /// - public virtual IndexEntry this[string path] + public virtual unsafe IndexEntry this[string path] { get { Ensure.ArgumentNotNullOrEmptyString(path, "path"); - IndexEntrySafeHandle entryHandle = Proxy.git_index_get_bypath(handle, path, 0); - return IndexEntry.BuildFromPtr(entryHandle); + git_index_entry* entry = Proxy.git_index_get_bypath(handle, path, 0); + return IndexEntry.BuildFromPtr(entry); } } - private IndexEntry this[int index] + private unsafe IndexEntry this[int index] { get { - IndexEntrySafeHandle entryHandle = Proxy.git_index_get_byindex(handle, (UIntPtr)index); + git_index_entry* entryHandle = Proxy.git_index_get_byindex(handle, (UIntPtr)index); return IndexEntry.BuildFromPtr(entryHandle); } } @@ -138,8 +142,6 @@ public virtual void Replace(Tree source) { Proxy.git_index_read_fromtree(this, obj.ObjectPtr); } - - UpdatePhysicalIndex(); } /// @@ -152,7 +154,6 @@ public virtual void Replace(Tree source) public virtual void Clear() { Proxy.git_index_clear(this); - UpdatePhysicalIndex(); } private void RemoveFromIndex(string relativePath) @@ -166,14 +167,8 @@ private void RemoveFromIndex(string relativePath) /// The path of the entry to be removed. public virtual void Remove(string indexEntryPath) { - if (indexEntryPath == null) - { - throw new ArgumentNullException("indexEntryPath"); - } - + Ensure.ArgumentNotNull(indexEntryPath, "indexEntryPath"); RemoveFromIndex(indexEntryPath); - - UpdatePhysicalIndex(); } /// @@ -186,14 +181,8 @@ public virtual void Remove(string indexEntryPath) /// The path, in the working directory, of the file to be added. public virtual void Add(string pathInTheWorkdir) { - if (pathInTheWorkdir == null) - { - throw new ArgumentNullException("pathInTheWorkdir"); - } - + Ensure.ArgumentNotNull(pathInTheWorkdir, "pathInTheWorkdir"); Proxy.git_index_add_bypath(handle, pathInTheWorkdir); - - UpdatePhysicalIndex(); } /// @@ -210,25 +199,9 @@ public virtual void Add(string pathInTheWorkdir) public virtual void Add(Blob blob, string indexEntryPath, Mode indexEntryMode) { Ensure.ArgumentConformsTo(indexEntryMode, m => m.HasAny(TreeEntryDefinition.BlobModes), "indexEntryMode"); - - if (blob == null) - { - throw new ArgumentNullException("blob"); - } - - if (indexEntryPath == null) - { - throw new ArgumentNullException("indexEntryPath"); - } - + Ensure.ArgumentNotNull(blob, "blob"); + Ensure.ArgumentNotNull(indexEntryPath, "indexEntryPath"); AddEntryToTheIndex(indexEntryPath, blob.Id, indexEntryMode); - - UpdatePhysicalIndex(); - } - - private void UpdatePhysicalIndex() - { - Proxy.git_index_write(handle); } internal void Replace(TreeChanges changes) @@ -245,21 +218,19 @@ internal void Replace(TreeChanges changes) continue; case ChangeKind.Deleted: - /* Fall through */ case ChangeKind.Modified: - AddEntryToTheIndex( - treeEntryChanges.OldPath, - treeEntryChanges.OldOid, - treeEntryChanges.OldMode); - + AddEntryToTheIndex(treeEntryChanges.OldPath, + treeEntryChanges.OldOid, + treeEntryChanges.OldMode); continue; default: - throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Entry '{0}' bears an unexpected ChangeKind '{1}'", treeEntryChanges.Path, treeEntryChanges.Status)); + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, + "Entry '{0}' bears an unexpected ChangeKind '{1}'", + treeEntryChanges.Path, + treeEntryChanges.Status)); } } - - UpdatePhysicalIndex(); } /// @@ -267,31 +238,28 @@ internal void Replace(TreeChanges changes) /// public virtual ConflictCollection Conflicts { - get - { - return conflicts; - } + get { return conflicts; } } - private void AddEntryToTheIndex(string path, ObjectId id, Mode mode) + private unsafe void AddEntryToTheIndex(string path, ObjectId id, Mode mode) { - var indexEntry = new GitIndexEntry + IntPtr pathPtr = StrictFilePathMarshaler.FromManaged(path); + var indexEntry = new git_index_entry { - Mode = (uint)mode, - Id = id.Oid, - Path = StrictFilePathMarshaler.FromManaged(path), + mode = (uint)mode, + path = (char*) pathPtr, }; + Marshal.Copy(id.RawId, 0, new IntPtr(indexEntry.id.Id), GitOid.Size); - Proxy.git_index_add(handle, indexEntry); - EncodingMarshaler.Cleanup(indexEntry.Path); + Proxy.git_index_add(handle, &indexEntry); + EncodingMarshaler.Cleanup(pathPtr); } private string DebuggerDisplay { get { - return string.Format(CultureInfo.InvariantCulture, - "Count = {0}", Count); + return string.Format(CultureInfo.InvariantCulture, "Count = {0}", Count); } } @@ -327,8 +295,29 @@ public virtual void Replace(Commit commit, IEnumerable paths, ExplicitPa { Ensure.ArgumentNotNull(commit, "commit"); - var changes = repo.Diff.Compare(commit.Tree, DiffTargets.Index, paths, explicitPathsOptions, new CompareOptions { Similarity = SimilarityOptions.None }); - Replace(changes); + using (var changes = repo.Diff.Compare(commit.Tree, DiffTargets.Index, paths, explicitPathsOptions, new CompareOptions { Similarity = SimilarityOptions.None })) + { + Replace(changes); + } + } + + /// + /// Write the contents of this to disk + /// + public virtual void Write() + { + Proxy.git_index_write(handle); + } + + /// + /// Write the contents of this to a tree + /// + /// + public virtual Tree WriteToTree() + { + 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 7dc6b0150..e07e284b7 100644 --- a/LibGit2Sharp/IndexEntry.cs +++ b/LibGit2Sharp/IndexEntry.cs @@ -40,25 +40,23 @@ public class IndexEntry : IEquatable /// public virtual ObjectId Id { get; private set; } - internal static IndexEntry BuildFromPtr(IndexEntrySafeHandle handle) + internal static unsafe IndexEntry BuildFromPtr(git_index_entry* entry) { - if (handle == null || handle.IsZero) + if (entry == null) { return null; } - GitIndexEntry entry = handle.MarshalAsGitIndexEntry(); - - FilePath path = LaxFilePathMarshaler.FromNative(entry.Path); + string path = LaxUtf8Marshaler.FromNative(entry->path); return new IndexEntry - { - Path = path.Native, - Id = entry.Id, - StageLevel = Proxy.git_index_entry_stage(handle), - Mode = (Mode)entry.Mode, - AssumeUnchanged = (GitIndexEntry.GIT_IDXENTRY_VALID & entry.Flags) == GitIndexEntry.GIT_IDXENTRY_VALID - }; + { + 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 + }; } /// @@ -117,7 +115,10 @@ private string DebuggerDisplay get { return string.Format(CultureInfo.InvariantCulture, - "{0} ({1}) => \"{2}\"", Path, StageLevel, Id.ToString(7)); + "{0} ({1}) => \"{2}\"", + Path, + StageLevel, + Id.ToString(7)); } } } diff --git a/LibGit2Sharp/IndexNameEntry.cs b/LibGit2Sharp/IndexNameEntry.cs index ef6fc50d2..79b3f6993 100644 --- a/LibGit2Sharp/IndexNameEntry.cs +++ b/LibGit2Sharp/IndexNameEntry.cs @@ -22,21 +22,22 @@ public class IndexNameEntry : IEquatable protected IndexNameEntry() { } - internal static IndexNameEntry BuildFromPtr(IndexNameEntrySafeHandle handle) + internal static unsafe IndexNameEntry BuildFromPtr(git_index_name_entry* entry) { - if (handle == null || handle.IsZero) + if (entry == null) { return null; } - GitIndexNameEntry entry = handle.MarshalAsGitIndexNameEntry(); - - string ancestor = entry.Ancestor != IntPtr.Zero ? - LaxFilePathMarshaler.FromNative(entry.Ancestor).Native : null; - string ours = entry.Ours != IntPtr.Zero ? - LaxFilePathMarshaler.FromNative(entry.Ours).Native : null; - string theirs = entry.Theirs != IntPtr.Zero ? - LaxFilePathMarshaler.FromNative(entry.Theirs).Native : null; + string ancestor = entry->ancestor != null + ? LaxFilePathMarshaler.FromNative(entry->ancestor).Native + : null; + string ours = entry->ours != null + ? LaxFilePathMarshaler.FromNative(entry->ours).Native + : null; + string theirs = entry->theirs != null + ? LaxFilePathMarshaler.FromNative(entry->theirs).Native + : null; return new IndexNameEntry { @@ -117,7 +118,10 @@ private string DebuggerDisplay get { return string.Format(CultureInfo.InvariantCulture, - "{0} {1} {2}", Ancestor, Ours, Theirs); + "{0} {1} {2}", + Ancestor, + Ours, + Theirs); } } } diff --git a/LibGit2Sharp/IndexNameEntryCollection.cs b/LibGit2Sharp/IndexNameEntryCollection.cs index 2896f9124..a75bedd71 100644 --- a/LibGit2Sharp/IndexNameEntryCollection.cs +++ b/LibGit2Sharp/IndexNameEntryCollection.cs @@ -26,11 +26,11 @@ internal IndexNameEntryCollection(Index index) this.index = index; } - private IndexNameEntry this[int idx] + private unsafe IndexNameEntry this[int idx] { get { - IndexNameEntrySafeHandle entryHandle = Proxy.git_index_name_get_byindex(index.Handle, (UIntPtr)idx); + git_index_name_entry* entryHandle = Proxy.git_index_name_get_byindex(index.Handle, (UIntPtr)idx); return IndexNameEntry.BuildFromPtr(entryHandle); } } diff --git a/LibGit2Sharp/IndexReucEntry.cs b/LibGit2Sharp/IndexReucEntry.cs index ffba71e07..583df95ba 100644 --- a/LibGit2Sharp/IndexReucEntry.cs +++ b/LibGit2Sharp/IndexReucEntry.cs @@ -15,9 +15,9 @@ public class IndexReucEntry : IEquatable { private static readonly LambdaEqualityHelper equalityHelper = new LambdaEqualityHelper(x => x.Path, - x => x.AncestorId, x => x.AncestorMode, - x => x.OurId, x => x.OurMode, - x => x.TheirId, x => x.TheirMode); + x => x.AncestorId, x => x.AncestorMode, + x => x.OurId, x => x.OurMode, + x => x.TheirId, x => x.TheirMode); /// /// Needed for mocking purposes. @@ -25,26 +25,24 @@ public class IndexReucEntry : IEquatable protected IndexReucEntry() { } - internal static IndexReucEntry BuildFromPtr(IndexReucEntrySafeHandle handle) + internal static unsafe IndexReucEntry BuildFromPtr(git_index_reuc_entry* entry) { - if (handle == null || handle.IsZero) + if (entry == null) { return null; } - GitIndexReucEntry entry = handle.MarshalAsGitIndexReucEntry(); - - FilePath path = LaxFilePathMarshaler.FromNative(entry.Path); + FilePath path = LaxUtf8Marshaler.FromNative(entry->Path); return new IndexReucEntry { Path = path.Native, - AncestorId = entry.AncestorId, - AncestorMode = (Mode)entry.AncestorMode, - OurId = entry.OurId, - OurMode = (Mode)entry.OurMode, - TheirId = entry.TheirId, - TheirMode = (Mode)entry.TheirMode, + AncestorId = ObjectId.BuildFromPtr(&entry->AncestorId), + AncestorMode = (Mode)entry->AncestorMode, + OurId = ObjectId.BuildFromPtr(&entry->OurId), + OurMode = (Mode)entry->OurMode, + TheirId = ObjectId.BuildFromPtr(&entry->TheirId), + TheirMode = (Mode)entry->TheirMode, }; } @@ -145,7 +143,11 @@ private string DebuggerDisplay get { return string.Format(CultureInfo.InvariantCulture, - "{0}: {1} {2} {3}", Path, AncestorId, OurId, TheirId); + "{0}: {1} {2} {3}", + Path, + AncestorId, + OurId, + TheirId); } } } diff --git a/LibGit2Sharp/IndexReucEntryCollection.cs b/LibGit2Sharp/IndexReucEntryCollection.cs index 8402a285a..61af48b18 100644 --- a/LibGit2Sharp/IndexReucEntryCollection.cs +++ b/LibGit2Sharp/IndexReucEntryCollection.cs @@ -29,22 +29,22 @@ internal IndexReucEntryCollection(Index index) /// /// Gets the with the specified relative path. /// - public virtual IndexReucEntry this[string path] + public virtual unsafe IndexReucEntry this[string path] { get { Ensure.ArgumentNotNullOrEmptyString(path, "path"); - IndexReucEntrySafeHandle entryHandle = Proxy.git_index_reuc_get_bypath(index.Handle, path); + git_index_reuc_entry* entryHandle = Proxy.git_index_reuc_get_bypath(index.Handle, path); return IndexReucEntry.BuildFromPtr(entryHandle); } } - private IndexReucEntry this[int idx] + private unsafe IndexReucEntry this[int idx] { get { - IndexReucEntrySafeHandle entryHandle = Proxy.git_index_reuc_get_byindex(index.Handle, (UIntPtr)idx); + git_index_reuc_entry* entryHandle = Proxy.git_index_reuc_get_byindex(index.Handle, (UIntPtr)idx); return IndexReucEntry.BuildFromPtr(entryHandle); } } diff --git a/LibGit2Sharp/InvalidSpecificationException.cs b/LibGit2Sharp/InvalidSpecificationException.cs index 7c82a4372..3d34571a4 100644 --- a/LibGit2Sharp/InvalidSpecificationException.cs +++ b/LibGit2Sharp/InvalidSpecificationException.cs @@ -11,14 +11,13 @@ namespace LibGit2Sharp /// create a branch from a blob, or peeling a blob to a commit). /// [Serializable] - public class InvalidSpecificationException : LibGit2SharpException + public class InvalidSpecificationException : NativeException { /// /// Initializes a new instance of the class. /// public InvalidSpecificationException() - { - } + { } /// /// Initializes a new instance of the class with a specified error message. @@ -26,8 +25,16 @@ public InvalidSpecificationException() /// A message that describes the error. public InvalidSpecificationException(string message) : base(message) - { - } + { } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// A composite format string for use in . + /// An object array that contains zero or more objects to format. + public InvalidSpecificationException(string format, params object[] args) + : base(format, args) + { } /// /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. @@ -36,8 +43,7 @@ public InvalidSpecificationException(string message) /// The exception that is the cause of the current exception. If the parameter is not a null reference, the current exception is raised in a catch block that handles the inner exception. public InvalidSpecificationException(string message, Exception innerException) : base(message, innerException) - { - } + { } /// /// Initializes a new instance of the class with a serialized data. @@ -46,12 +52,18 @@ public InvalidSpecificationException(string message, Exception innerException) /// The that contains contextual information about the source or destination. protected InvalidSpecificationException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } + { } + + 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 c20b0933b..3b5cefd25 100644 --- a/LibGit2Sharp/LibGit2Sharp.csproj +++ b/LibGit2Sharp/LibGit2Sharp.csproj @@ -1,391 +1,50 @@ - - - + + + - Debug - AnyCPU - 8.0.30703 - 2.0 - {EE6ED99F-CB12-4683-B055-D28FC7357A34} - Library - Properties - LibGit2Sharp - LibGit2Sharp - v4.0 - 512 - + netstandard2.0;netcoreapp2.1 + true + LibGit2Sharp brings all the might and speed of libgit2, a native Git implementation, to the managed world of .Net and Mono. + LibGit2Sharp contributors + Copyright © LibGit2Sharp contributors + libgit2 git + https://github.com/dotdevelop/libgit2sharp/ + LibGit2Sharp contributors + true + true + $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb + true + snupkg + true + ..\libgit2sharp.snk + square-logo.png + App_Readme/LICENSE.md - - true - full - false - bin\Debug\ - TRACE;DEBUG;NET40 - prompt - 4 - false - true - AllRules.ruleset - bin\Debug\LibGit2Sharp.xml - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - true - false - bin\Release\LibGit2Sharp.xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Code - - - - - - - - - - - - - - - - - - - - - - - - - - - - Code - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + - - - - - - - - - - - - - - + + + + + + + + + - This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + https://github.com/libgit2/libgit2sharp/blob/$(GitCommitIdShort)/CHANGES.md#libgit2sharp-changes - - - - \ No newline at end of file + + diff --git a/LibGit2Sharp/LibGit2SharpException.cs b/LibGit2Sharp/LibGit2SharpException.cs index b80dba6d4..5d1c33f25 100644 --- a/LibGit2Sharp/LibGit2SharpException.cs +++ b/LibGit2Sharp/LibGit2SharpException.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Runtime.Serialization; using LibGit2Sharp.Core; @@ -14,8 +15,7 @@ public class LibGit2SharpException : Exception /// Initializes a new instance of the class. /// public LibGit2SharpException() - { - } + { } /// /// Initializes a new instance of the class with a specified error message. @@ -23,8 +23,7 @@ public LibGit2SharpException() /// A message that describes the error. public LibGit2SharpException(string message) : base(message) - { - } + { } /// /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. @@ -33,6 +32,15 @@ public LibGit2SharpException(string message) /// The exception that is the cause of the current exception. If the parameter is not a null reference, the current exception is raised in a catch block that handles the inner exception. public LibGit2SharpException(string message, Exception innerException) : base(message, innerException) + { } + + /// + /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. + /// + /// A composite format string for use in . + /// An object array that contains zero or more objects to format. + public LibGit2SharpException(string format, params object[] args) + : base(String.Format(CultureInfo.InvariantCulture, format, args)) { } @@ -43,14 +51,6 @@ public LibGit2SharpException(string message, Exception innerException) /// The that contains contextual information about the source or destination. protected LibGit2SharpException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } - - internal LibGit2SharpException(string message, GitErrorCode code, GitErrorCategory category) : this(message) - { - Data.Add("libgit2.code", (int)code); - Data.Add("libgit2.category", (int)category); - - } + { } } } diff --git a/LibGit2Sharp/LockedFileException.cs b/LibGit2Sharp/LockedFileException.cs index a086a7b0b..44fd65b02 100644 --- a/LibGit2Sharp/LockedFileException.cs +++ b/LibGit2Sharp/LockedFileException.cs @@ -8,14 +8,13 @@ namespace LibGit2Sharp /// The exception that is thrown attempting to open a locked file. /// [Serializable] - public class LockedFileException : LibGit2SharpException + public class LockedFileException : NativeException { /// /// Initializes a new instance of the class. /// public LockedFileException() - { - } + { } /// /// Initializes a new instance of the class with a specified error message. @@ -23,8 +22,16 @@ public LockedFileException() /// A message that describes the error. public LockedFileException(string message) : base(message) - { - } + { } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// A composite format string for use in . + /// An object array that contains zero or more objects to format. + public LockedFileException(string format, params object[] args) + : base(format, args) + { } /// /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. @@ -33,8 +40,7 @@ public LockedFileException(string message) /// The exception that is the cause of the current exception. If the parameter is not a null reference, the current exception is raised in a catch block that handles the inner exception. public LockedFileException(string message, Exception innerException) : base(message, innerException) - { - } + { } /// /// Initializes a new instance of the class with a serialized data. @@ -43,12 +49,18 @@ public LockedFileException(string message, Exception innerException) /// The that contains contextual information about the source or destination. protected LockedFileException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } + { } + + internal LockedFileException(string message, GitErrorCategory category) + : base(message, category) + { } - internal LockedFileException(string message, GitErrorCode code, GitErrorCategory category) - : base(message, code, category) + internal override GitErrorCode ErrorCode { + get + { + return GitErrorCode.LockedFile; + } } } } diff --git a/LibGit2Sharp/LogConfiguration.cs b/LibGit2Sharp/LogConfiguration.cs index 67c974bdf..dd63bf308 100644 --- a/LibGit2Sharp/LogConfiguration.cs +++ b/LibGit2Sharp/LogConfiguration.cs @@ -33,8 +33,7 @@ public LogConfiguration(LogLevel level, LogHandler handler) } private LogConfiguration() - { - } + { } internal LogLevel Level { get; private set; } internal LogHandler Handler { get; private set; } diff --git a/LibGit2Sharp/MergeAndCheckoutOptionsBase.cs b/LibGit2Sharp/MergeAndCheckoutOptionsBase.cs index e9240e555..b0d7cfc1d 100644 --- a/LibGit2Sharp/MergeAndCheckoutOptionsBase.cs +++ b/LibGit2Sharp/MergeAndCheckoutOptionsBase.cs @@ -1,6 +1,5 @@ using LibGit2Sharp.Core; using LibGit2Sharp.Handlers; -using System; namespace LibGit2Sharp { diff --git a/LibGit2Sharp/MergeConflictException.cs b/LibGit2Sharp/MergeConflictException.cs deleted file mode 100644 index edcd5ebb7..000000000 --- a/LibGit2Sharp/MergeConflictException.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -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. - /// - [Serializable] - [Obsolete("This type will be removed in the next release. Please use CheckoutConflictException instead.")] - public class MergeConflictException : CheckoutConflictException - { - } -} diff --git a/LibGit2Sharp/MergeFetchHeadNotFoundException.cs b/LibGit2Sharp/MergeFetchHeadNotFoundException.cs index 26c3844a6..a86bf5caf 100644 --- a/LibGit2Sharp/MergeFetchHeadNotFoundException.cs +++ b/LibGit2Sharp/MergeFetchHeadNotFoundException.cs @@ -13,8 +13,7 @@ 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. @@ -22,8 +21,16 @@ public MergeFetchHeadNotFoundException() /// A message that describes the error. public MergeFetchHeadNotFoundException(string message) : base(message) - { - } + { } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// A composite format string for use in . + /// An object array that contains zero or more objects to format. + public MergeFetchHeadNotFoundException(string format, params object[] args) + : base(format, args) + { } /// /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. @@ -32,8 +39,7 @@ public MergeFetchHeadNotFoundException(string message) /// The exception that is the cause of the current exception. If the parameter is not a null reference, the current exception is raised in a catch block that handles the inner exception. public MergeFetchHeadNotFoundException(string message, Exception innerException) : base(message, innerException) - { - } + { } /// /// Initializes a new instance of the class with a serialized data. @@ -42,7 +48,6 @@ public MergeFetchHeadNotFoundException(string message, Exception innerException) /// The that contains contextual information about the source or destination. protected MergeFetchHeadNotFoundException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } + { } } } diff --git a/LibGit2Sharp/MergeHead.cs b/LibGit2Sharp/MergeHead.cs index 2ad508179..b548b254a 100644 --- a/LibGit2Sharp/MergeHead.cs +++ b/LibGit2Sharp/MergeHead.cs @@ -15,8 +15,7 @@ protected MergeHead() internal MergeHead(Repository repo, ObjectId targetId, int index) : base(repo, new DirectReference(string.Format(CultureInfo.InvariantCulture, "MERGE_HEAD[{0}]", index), repo, targetId), r => r.CanonicalName) - { - } + { } /// /// Gets the that this merge head points to. diff --git a/LibGit2Sharp/MergeOptions.cs b/LibGit2Sharp/MergeOptions.cs index ccdbff3e7..b57d955e4 100644 --- a/LibGit2Sharp/MergeOptions.cs +++ b/LibGit2Sharp/MergeOptions.cs @@ -1,6 +1,4 @@ using System; -using LibGit2Sharp.Core; -using LibGit2Sharp.Handlers; namespace LibGit2Sharp { @@ -19,8 +17,7 @@ public sealed class MergeOptions : MergeAndCheckoutOptionsBase /// /// public MergeOptions() - { - } + { } /// /// The type of merge to perform. @@ -40,12 +37,6 @@ public enum FastForwardStrategy /// Default = 0, - /// - /// Do not fast-forward. Always creates a merge commit. - /// - [Obsolete("This enum member will be removed in the next release. Please use NoFastForward instead.")] - NoFastFoward = 1, /* GIT_MERGE_NO_FASTFORWARD */ - /// /// Do not fast-forward. Always creates a merge commit. /// diff --git a/LibGit2Sharp/MergeOptionsBase.cs b/LibGit2Sharp/MergeOptionsBase.cs index 2dda65e6a..85e930ab1 100644 --- a/LibGit2Sharp/MergeOptionsBase.cs +++ b/LibGit2Sharp/MergeOptionsBase.cs @@ -22,6 +22,19 @@ protected MergeOptionsBase() /// 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. /// @@ -37,6 +50,11 @@ protected MergeOptionsBase() /// How to handle conflicts encountered during a merge. /// public MergeFileFavor MergeFileFavor { get; set; } + + /// + /// Ignore changes in amount of whitespace + /// + public bool IgnoreWhitespaceChange { get; set; } } /// diff --git a/LibGit2Sharp/MergeTreeOptions.cs b/LibGit2Sharp/MergeTreeOptions.cs index cf580d6f0..a9eea97eb 100644 --- a/LibGit2Sharp/MergeTreeOptions.cs +++ b/LibGit2Sharp/MergeTreeOptions.cs @@ -1,8 +1,4 @@ -using LibGit2Sharp.Core; -using LibGit2Sharp.Handlers; -using System; - -namespace LibGit2Sharp +namespace LibGit2Sharp { /// /// Options controlling the behavior of two trees being merged. @@ -17,7 +13,6 @@ public sealed class MergeTreeOptions : MergeOptionsBase /// /// public MergeTreeOptions() - { - } + { } } } diff --git a/LibGit2Sharp/MergeTreeResult.cs b/LibGit2Sharp/MergeTreeResult.cs index 50c305f39..c871c6278 100644 --- a/LibGit2Sharp/MergeTreeResult.cs +++ b/LibGit2Sharp/MergeTreeResult.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using LibGit2Sharp.Core; +using System.Collections.Generic; namespace LibGit2Sharp { @@ -31,31 +29,19 @@ internal MergeTreeResult(Tree tree) /// /// The status of the merge. /// - public virtual MergeTreeStatus Status - { - get; - private set; - } + 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; - } + 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; - } + public virtual IEnumerable Conflicts { get; private set; } } /// diff --git a/LibGit2Sharp/NameConflictException.cs b/LibGit2Sharp/NameConflictException.cs index 16d1bf091..0dcffc648 100644 --- a/LibGit2Sharp/NameConflictException.cs +++ b/LibGit2Sharp/NameConflictException.cs @@ -8,14 +8,13 @@ namespace LibGit2Sharp /// The exception that is thrown when a reference, a remote, a submodule... with the same name already exists in the repository /// [Serializable] - public class NameConflictException : LibGit2SharpException + public class NameConflictException : NativeException { /// /// Initializes a new instance of the class. /// public NameConflictException() - { - } + { } /// /// Initializes a new instance of the class with a specified error message. @@ -23,8 +22,16 @@ public NameConflictException() /// A message that describes the error. public NameConflictException(string message) : base(message) - { - } + { } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// A composite format string for use in . + /// An object array that contains zero or more objects to format. + public NameConflictException(string format, params object[] args) + : base(format, args) + { } /// /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. @@ -33,8 +40,7 @@ public NameConflictException(string message) /// The exception that is the cause of the current exception. If the parameter is not a null reference, the current exception is raised in a catch block that handles the inner exception. public NameConflictException(string message, Exception innerException) : base(message, innerException) - { - } + { } /// /// Initializes a new instance of the class with a serialized data. @@ -43,12 +49,18 @@ public NameConflictException(string message, Exception innerException) /// The that contains contextual information about the source or destination. protected NameConflictException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } + { } + + 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/NativeDllName.targets b/LibGit2Sharp/NativeDllName.targets deleted file mode 100644 index 33261d59e..000000000 --- a/LibGit2Sharp/NativeDllName.targets +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - . - $(MSBuildThisFileDirectory) - $(LibGit2SharpPath)\Core\NativeDllName.cs - $(CoreCompileDependsOn);GenerateNativeDllNameCs - $(CoreCleanDependsOn);CleanNativeDllNameCs - - - - - - - - - - diff --git a/LibGit2Sharp/NativeException.cs b/LibGit2Sharp/NativeException.cs new file mode 100644 index 000000000..2d418b85d --- /dev/null +++ b/LibGit2Sharp/NativeException.cs @@ -0,0 +1,46 @@ +using LibGit2Sharp.Core; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Runtime.Serialization; +using System.Text; + +namespace LibGit2Sharp +{ + /// + /// An exception thrown that corresponds to a libgit2 (native library) error. + /// + [Serializable] + 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) + { + } + + internal NativeException(SerializationInfo info, StreamingContext context) + : base(info, context) + { } + + 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 edfe2fd71..d5f032058 100644 --- a/LibGit2Sharp/Network.cs +++ b/LibGit2Sharp/Network.cs @@ -48,9 +48,11 @@ public virtual RemoteCollection Remotes /// /// The to list from. /// The references in the repository. - public virtual IEnumerable ListReferences(Remote remote) + public virtual IEnumerable ListReferences(Remote remote) { - return ListReferences(remote, null); + Ensure.ArgumentNotNull(remote, "remote"); + + return ListReferencesInternal(remote.Url, null); } /// @@ -65,23 +67,12 @@ 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) + public virtual IEnumerable ListReferences(Remote remote, CredentialsHandler credentialsProvider) { Ensure.ArgumentNotNull(remote, "remote"); + Ensure.ArgumentNotNull(credentialsProvider, "credentialsProvider"); - using (RemoteSafeHandle remoteHandle = Proxy.git_remote_lookup(repository.Handle, remote.Name, true)) - { - var gitCallbacks = new GitRemoteCallbacks {version = 1}; - - if (credentialsProvider != null) - { - var callbacks = new RemoteCallbacks(credentialsProvider); - gitCallbacks = callbacks.GenerateCallbacks(); - } - - Proxy.git_remote_connect(remoteHandle, GitDirection.Fetch, ref gitCallbacks); - return Proxy.git_remote_ls(repository, remoteHandle); - } + return ListReferencesInternal(remote.Url, credentialsProvider); } /// @@ -95,160 +86,60 @@ public virtual IEnumerable ListReferences(Remote remote, Creden /// /// The url to list from. /// The references in the remote repository. - public virtual IEnumerable ListReferences(string url) + public virtual IEnumerable ListReferences(string url) { Ensure.ArgumentNotNull(url, "url"); - using (RemoteSafeHandle remoteHandle = Proxy.git_remote_create_anonymous(repository.Handle, url)) - { - GitRemoteCallbacks gitCallbacks = new GitRemoteCallbacks { version = 1 }; - Proxy.git_remote_connect(remoteHandle, GitDirection.Fetch, ref gitCallbacks); - return Proxy.git_remote_ls(repository, remoteHandle); - } + return ListReferencesInternal(url, null); } - static RemoteSafeHandle BuildRemoteSafeHandle(RepositorySafeHandle repoHandle, Remote remote, string url) + /// + /// 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) { - Debug.Assert((remote == null) ^ (url == null)); - - RemoteSafeHandle remoteHandle; - - if (url != null) - { - remoteHandle = Proxy.git_remote_create_anonymous(repoHandle, url); - } - else - { - remoteHandle = Proxy.git_remote_lookup(repoHandle, remote.Name, true); - } + Ensure.ArgumentNotNull(url, "url"); + Ensure.ArgumentNotNull(credentialsProvider, "credentialsProvider"); - return remoteHandle; + return ListReferencesInternal(url, credentialsProvider); } - static void DoFetch(RepositorySafeHandle repoHandle, Remote remote, string url, - FetchOptions options, string logMessage, - IEnumerable refspecs) + private IEnumerable ListReferencesInternal(string url, CredentialsHandler credentialsProvider) { - if (options == null) + using (RemoteHandle remoteHandle = BuildRemoteHandle(repository.Handle, url)) { - options = new FetchOptions(); - } - - using (RemoteSafeHandle remoteHandle = BuildRemoteSafeHandle(repoHandle, remote, url)) - { - var callbacks = new RemoteCallbacks(options); - GitRemoteCallbacks gitCallbacks = callbacks.GenerateCallbacks(); + GitRemoteCallbacks gitCallbacks = new GitRemoteCallbacks { version = 1 }; + GitProxyOptions proxyOptions = new GitProxyOptions { Version = 1 }; - // 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 = new GitFetchOptions - { - RemoteCallbacks = gitCallbacks, - download_tags = Proxy.git_remote_autotag(remoteHandle), - }; - - if (options.TagFetchMode.HasValue) + if (credentialsProvider != null) { - fetchOptions.download_tags = options.TagFetchMode.Value; + var callbacks = new RemoteCallbacks(credentialsProvider); + gitCallbacks = callbacks.GenerateCallbacks(); } - Proxy.git_remote_fetch(remoteHandle, refspecs, fetchOptions, logMessage); + Proxy.git_remote_connect(remoteHandle, GitDirection.Fetch, ref gitCallbacks, ref proxyOptions); + return Proxy.git_remote_ls(repository, remoteHandle); } } - /// - /// Fetch from the . - /// - /// The remote to fetch - public virtual void Fetch(Remote remote) - { - Fetch(remote, (FetchOptions)null, null); - } - - /// - /// Fetch from the . - /// - /// The remote to fetch - /// controlling fetch behavior - public virtual void Fetch(Remote remote, FetchOptions options) - { - Fetch(remote, options, null); - } - - /// - /// Fetch from the . - /// - /// The remote to fetch - /// Message to use when updating the reflog. - public virtual void Fetch(Remote remote, string logMessage) - { - Fetch(remote, (FetchOptions)null, logMessage); - } - - /// - /// Fetch from the . - /// - /// The remote to fetch - /// controlling fetch behavior - /// Message to use when updating the reflog. - public virtual void Fetch(Remote remote, FetchOptions options, string logMessage) - { - Ensure.ArgumentNotNull(remote, "remote"); - - DoFetch(repository.Handle, remote, null, options, logMessage, new string[0]); - } - - /// - /// Fetch from the , using custom refspecs. - /// - /// The remote to fetch - /// Refspecs to use, replacing the remote's fetch refspecs - public virtual void Fetch(Remote remote, IEnumerable refspecs) + static RemoteHandle BuildRemoteHandle(RepositoryHandle repoHandle, string url) { - Fetch(remote, refspecs, null, null); - } + Debug.Assert(repoHandle != null && !repoHandle.IsNull); + Debug.Assert(url != null); - /// - /// Fetch from the , using custom refspecs. - /// - /// The remote to fetch - /// Refspecs to use, replacing the remote's fetch refspecs - /// controlling fetch behavior - public virtual void Fetch(Remote remote, IEnumerable refspecs, FetchOptions options) - { - Fetch(remote, refspecs, options, null); - } + RemoteHandle remoteHandle = Proxy.git_remote_create_anonymous(repoHandle, url); + Debug.Assert(remoteHandle != null && !(remoteHandle.IsNull)); - /// - /// Fetch from the , using custom refspecs. - /// - /// The remote to fetch - /// Refspecs to use, replacing the remote's fetch refspecs - /// Message to use when updating the reflog. - public virtual void Fetch(Remote remote, IEnumerable refspecs, string logMessage) - { - Fetch(remote, refspecs, null, logMessage); - } - - /// - /// Fetch from the , using custom refspecs. - /// - /// The remote to fetch - /// Refspecs to use, replacing the remote's fetch refspecs - /// controlling fetch behavior - /// Message to use when updating the reflog. - public virtual void Fetch(Remote remote, IEnumerable refspecs, FetchOptions options, string logMessage) - { - Ensure.ArgumentNotNull(remote, "remote"); - Ensure.ArgumentNotNull(refspecs, "refspecs"); - - DoFetch(repository.Handle, remote, null, options, logMessage, refspecs); + return remoteHandle; } /// @@ -256,9 +147,7 @@ public virtual void Fetch(Remote remote, IEnumerable refspecs, FetchOpti /// /// The url to fetch from /// The list of resfpecs to use - public virtual void Fetch( - string url, - IEnumerable refspecs) + public virtual void Fetch(string url, IEnumerable refspecs) { Fetch(url, refspecs, null, null); } @@ -269,10 +158,7 @@ public virtual void Fetch( /// The url to fetch from /// The list of resfpecs to use /// controlling fetch behavior - public virtual void Fetch( - string url, - IEnumerable refspecs, - FetchOptions options) + public virtual void Fetch(string url, IEnumerable refspecs, FetchOptions options) { Fetch(url, refspecs, options, null); } @@ -283,10 +169,7 @@ public virtual void Fetch( /// 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) + public virtual void Fetch(string url, IEnumerable refspecs, string logMessage) { Fetch(url, refspecs, null, logMessage); } @@ -307,7 +190,73 @@ public virtual void Fetch( Ensure.ArgumentNotNull(url, "url"); Ensure.ArgumentNotNull(refspecs, "refspecs"); - DoFetch(repository.Handle, null, url, options, logMessage, 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)) + { + throw new LibGit2SharpException("The branch '{0}' (\"{1}\") that you are trying to push does not track an upstream branch.", + branch.FriendlyName, branch.CanonicalName); + } + } + + 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); + } + } } /// @@ -324,8 +273,11 @@ public virtual void Push( Ensure.ArgumentNotNull(objectish, "objectish"); Ensure.ArgumentNotNullOrEmptyString(destinationSpec, "destinationSpec"); - Push(remote, string.Format(CultureInfo.InvariantCulture, - "{0}:{1}", objectish, destinationSpec)); + Push(remote, + string.Format(CultureInfo.InvariantCulture, + "{0}:{1}", + objectish, + destinationSpec)); } /// @@ -344,8 +296,12 @@ public virtual void Push( Ensure.ArgumentNotNull(objectish, "objectish"); Ensure.ArgumentNotNullOrEmptyString(destinationSpec, "destinationSpec"); - Push(remote, string.Format(CultureInfo.InvariantCulture, - "{0}:{1}", objectish, destinationSpec), pushOptions); + Push(remote, + string.Format(CultureInfo.InvariantCulture, + "{0}:{1}", + objectish, + destinationSpec), + pushOptions); } /// @@ -353,9 +309,7 @@ public virtual void Push( /// /// The to push to. /// The pushRefSpec to push. - public virtual void Push( - Remote remote, - string pushRefSpec) + public virtual void Push(Remote remote, string pushRefSpec) { Ensure.ArgumentNotNullOrEmptyString(pushRefSpec, "pushRefSpec"); @@ -382,9 +336,7 @@ public virtual void Push( /// /// The to push to. /// The pushRefSpecs to push. - public virtual void Push( - Remote remote, - IEnumerable pushRefSpecs) + public virtual void Push(Remote remote, IEnumerable pushRefSpecs) { Push(remote, pushRefSpecs, null); } @@ -395,10 +347,7 @@ public virtual void Push( /// The to push to. /// The pushRefSpecs to push. /// controlling push behavior - public virtual void Push( - Remote remote, - IEnumerable pushRefSpecs, - PushOptions pushOptions) + public virtual void Push(Remote remote, IEnumerable pushRefSpecs, PushOptions pushOptions) { Ensure.ArgumentNotNull(remote, "remote"); Ensure.ArgumentNotNull(pushRefSpecs, "pushRefSpecs"); @@ -415,44 +364,20 @@ public virtual void Push( } // Load the remote. - using (RemoteSafeHandle remoteHandle = Proxy.git_remote_lookup(repository.Handle, remote.Name, true)) + using (RemoteHandle remoteHandle = Proxy.git_remote_lookup(repository.Handle, remote.Name, true)) { var callbacks = new RemoteCallbacks(pushOptions); GitRemoteCallbacks gitCallbacks = callbacks.GenerateCallbacks(); - Proxy.git_remote_push(remoteHandle, pushRefSpecs, - new GitPushOptions() - { - PackbuilderDegreeOfParallelism = pushOptions.PackbuilderDegreeOfParallelism, - RemoteCallbacks = gitCallbacks, - }); - } - } - - /// - /// Pull changes from the configured upstream remote and branch into the branch pointed at by HEAD. - /// - /// If the merge is a non-fast forward merge that generates a merge commit, the of who made the merge. - /// Specifies optional parameters controlling merge behavior of pull; if null, the defaults are used. - public virtual MergeResult Pull(Signature merger, PullOptions options) - { - Ensure.ArgumentNotNull(merger, "merger"); - Ensure.ArgumentNotNull(options, "options"); - - Branch currentBranch = repository.Head; - - if (!currentBranch.IsTracking) - { - throw new LibGit2SharpException("There is no tracking information for the current branch."); - } - - if (currentBranch.Remote == null) - { - throw new LibGit2SharpException("No upstream remote for the current branch."); + Proxy.git_remote_push(remoteHandle, + pushRefSpecs, + new GitPushOptions() + { + PackbuilderDegreeOfParallelism = pushOptions.PackbuilderDegreeOfParallelism, + RemoteCallbacks = gitCallbacks, + ProxyOptions = new GitProxyOptions { Version = 1 }, + }); } - - Fetch(currentBranch.Remote, options.FetchOptions); - return repository.MergeFetchedRefs(merger, options.MergeOptions); } /// @@ -464,9 +389,10 @@ internal virtual IEnumerable FetchHeads { int i = 0; - return Proxy.git_repository_fetchhead_foreach( - repository.Handle, - (name, url, oid, isMerge) => new FetchHead(repository, name, url, oid, isMerge, i++)); + Func resultSelector = + (name, url, oid, isMerge) => new FetchHead(repository, name, url, oid, isMerge, i++); + + return Proxy.git_repository_fetchhead_foreach(repository.Handle, resultSelector); } } } diff --git a/LibGit2Sharp/NetworkExtensions.cs b/LibGit2Sharp/NetworkExtensions.cs deleted file mode 100644 index 4efc53f05..000000000 --- a/LibGit2Sharp/NetworkExtensions.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System.Collections.Generic; -using System.Globalization; -using System.Linq; - -namespace LibGit2Sharp -{ - /// - /// Provides helper overloads to a . - /// - public static class NetworkExtensions - { - /// - /// Push the specified branch to its tracked branch on the remote. - /// - /// The being worked with. - /// The branch to push. - /// Throws if either the Remote or the UpstreamBranchCanonicalName is not set. - public static void Push( - this Network network, - Branch branch) - { - network.Push(new[] { branch }); - } - /// - /// Push the specified branch to its tracked branch on the remote. - /// - /// The being worked with. - /// The branch to push. - /// controlling push behavior - /// Throws if either the Remote or the UpstreamBranchCanonicalName is not set. - public static void Push( - this Network network, - Branch branch, - PushOptions pushOptions) - { - network.Push(new[] { branch }, pushOptions); - } - - /// - /// Push the specified branches to their tracked branches on the remote. - /// - /// The being worked with. - /// The branches to push. - /// Throws if either the Remote or the UpstreamBranchCanonicalName is not set. - public static void Push( - this Network network, - IEnumerable branches) - { - network.Push(branches, null); - } - - /// - /// Push the specified branches to their tracked branches on the remote. - /// - /// The being worked with. - /// The branches to push. - /// controlling push behavior - /// Throws if either the Remote or the UpstreamBranchCanonicalName is not set. - public static void Push( - this Network network, - IEnumerable branches, - PushOptions pushOptions) - { - var enumeratedBranches = branches as IList ?? branches.ToList(); - - foreach (var branch in enumeratedBranches) - { - if (string.IsNullOrEmpty(branch.UpstreamBranchCanonicalName)) - { - throw new LibGit2SharpException( - string.Format( - CultureInfo.InvariantCulture, - "The branch '{0}' (\"{1}\") that you are trying to push does not track an upstream branch.", - branch.FriendlyName, branch.CanonicalName)); - } - } - - foreach (var branch in enumeratedBranches) - { - network.Push(branch.Remote, string.Format( - CultureInfo.InvariantCulture, - "{0}:{1}", branch.CanonicalName, branch.UpstreamBranchCanonicalName), pushOptions); - } - } - } -} diff --git a/LibGit2Sharp/NonFastForwardException.cs b/LibGit2Sharp/NonFastForwardException.cs index 2cf4ccd93..b5a858f47 100644 --- a/LibGit2Sharp/NonFastForwardException.cs +++ b/LibGit2Sharp/NonFastForwardException.cs @@ -9,14 +9,13 @@ namespace LibGit2Sharp /// against the remote without losing commits. /// [Serializable] - public class NonFastForwardException : LibGit2SharpException + public class NonFastForwardException : NativeException { /// /// Initializes a new instance of the class. /// public NonFastForwardException() - { - } + { } /// /// Initializes a new instance of the class with a specified error message. @@ -24,8 +23,16 @@ public NonFastForwardException() /// A message that describes the error. public NonFastForwardException(string message) : base(message) - { - } + { } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// A composite format string for use in . + /// An object array that contains zero or more objects to format. + public NonFastForwardException(string format, params object[] args) + : base(format, args) + { } /// /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. @@ -34,8 +41,7 @@ public NonFastForwardException(string message) /// The exception that is the cause of the current exception. If the parameter is not a null reference, the current exception is raised in a catch block that handles the inner exception. public NonFastForwardException(string message, Exception innerException) : base(message, innerException) - { - } + { } /// /// Initializes a new instance of the class with a serialized data. @@ -44,12 +50,18 @@ public NonFastForwardException(string message, Exception innerException) /// The that contains contextual information about the source or destination. protected NonFastForwardException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } + { } + + internal NonFastForwardException(string message, GitErrorCategory category) + : base(message, category) + { } - internal NonFastForwardException(string message, GitErrorCode code, GitErrorCategory category) - : base(message, code, category) + internal override GitErrorCode ErrorCode { + get + { + return GitErrorCode.NonFastForward; + } } } } diff --git a/LibGit2Sharp/NotFoundException.cs b/LibGit2Sharp/NotFoundException.cs index c2a32ed00..f8c49cc91 100644 --- a/LibGit2Sharp/NotFoundException.cs +++ b/LibGit2Sharp/NotFoundException.cs @@ -8,14 +8,13 @@ namespace LibGit2Sharp /// The exception that is thrown attempting to reference a resource that does not exist. /// [Serializable] - public class NotFoundException : LibGit2SharpException + public class NotFoundException : NativeException { /// /// Initializes a new instance of the class. /// public NotFoundException() - { - } + { } /// /// Initializes a new instance of the class with a specified error message. @@ -23,8 +22,16 @@ public NotFoundException() /// A message that describes the error. public NotFoundException(string message) : base(message) - { - } + { } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// A composite format string for use in . + /// An object array that contains zero or more objects to format. + public NotFoundException(string format, params object[] args) + : base(format, args) + { } /// /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. @@ -33,8 +40,7 @@ public NotFoundException(string message) /// The exception that is the cause of the current exception. If the parameter is not a null reference, the current exception is raised in a catch block that handles the inner exception. public NotFoundException(string message, Exception innerException) : base(message, innerException) - { - } + { } /// /// Initializes a new instance of the class with a serialized data. @@ -43,12 +49,18 @@ public NotFoundException(string message, Exception innerException) /// The that contains contextual information about the source or destination. protected NotFoundException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } + { } + + internal NotFoundException(string message, GitErrorCategory category) + : base(message, category) + { } - internal NotFoundException(string message, GitErrorCode code, GitErrorCategory category) - : base(message, code, category) + internal override GitErrorCode ErrorCode { + get + { + return GitErrorCode.NotFound; + } } } } diff --git a/LibGit2Sharp/Note.cs b/LibGit2Sharp/Note.cs index 89a4eb65b..1df0125e4 100644 --- a/LibGit2Sharp/Note.cs +++ b/LibGit2Sharp/Note.cs @@ -12,6 +12,9 @@ namespace LibGit2Sharp [DebuggerDisplay("{DebuggerDisplay,nq}")] public class Note : IEquatable { + private static readonly LambdaEqualityHelper equalityHelper = + new LambdaEqualityHelper(x => x.BlobId, x => x.TargetObjectId, x => x.Namespace); + /// /// Needed for mocking purposes. /// @@ -47,7 +50,7 @@ private Note(ObjectId blobId, string message, ObjectId targetObjectId, string @n /// public virtual ObjectId TargetObjectId { get; private set; } - internal static Note BuildFromPtr(NoteSafeHandle note, string @namespace, ObjectId targetObjectId) + internal static Note BuildFromPtr(NoteHandle note, string @namespace, ObjectId targetObjectId) { ObjectId oid = Proxy.git_note_id(note); string message = Proxy.git_note_message(note); @@ -55,9 +58,6 @@ internal static Note BuildFromPtr(NoteSafeHandle note, string @namespace, Object return new Note(oid, message, targetObjectId, @namespace); } - private static readonly LambdaEqualityHelper equalityHelper = - new LambdaEqualityHelper(x => x.BlobId, x => x.TargetObjectId, x => x.Namespace); - /// /// Determines whether the specified is equal to the current . /// @@ -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 6a2a947cb..30084881d 100644 --- a/LibGit2Sharp/NoteCollection.cs +++ b/LibGit2Sharp/NoteCollection.cs @@ -65,10 +65,7 @@ public virtual string DefaultNamespace /// public virtual IEnumerable Namespaces { - get - { - return NamespaceRefs.Select(UnCanonicalizeName); - } + get { return NamespaceRefs.Select(UnCanonicalizeName); } } internal IEnumerable NamespaceRefs @@ -108,8 +105,9 @@ public virtual IEnumerable this[string @namespace] string canonicalNamespace = NormalizeToCanonicalName(@namespace); - return Proxy.git_note_foreach(repo.Handle, canonicalNamespace, - (blobId,annotatedObjId) => this[canonicalNamespace, annotatedObjId]); + return Proxy.git_note_foreach(repo.Handle, + canonicalNamespace, + (blobId, annotatedObjId) => this[canonicalNamespace, annotatedObjId]); } } @@ -125,7 +123,7 @@ public virtual IEnumerable this[string @namespace] string canonicalNamespace = NormalizeToCanonicalName(@namespace); - using (NoteSafeHandle noteHandle = Proxy.git_note_read(repo.Handle, canonicalNamespace, id)) + using (NoteHandle noteHandle = Proxy.git_note_read(repo.Handle, canonicalNamespace, id)) { return noteHandle == null ? null @@ -214,8 +212,7 @@ private string DebuggerDisplay { get { - return string.Format(CultureInfo.InvariantCulture, - "Count = {0}", this.Count()); + return string.Format(CultureInfo.InvariantCulture, "Count = {0}", this.Count()); } } } diff --git a/LibGit2Sharp/NoteCollectionExtensions.cs b/LibGit2Sharp/NoteCollectionExtensions.cs deleted file mode 100644 index 3a2f78e22..000000000 --- a/LibGit2Sharp/NoteCollectionExtensions.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; - -namespace LibGit2Sharp -{ - /// - /// Provides helper overloads to a . - /// - public static class NoteCollectionExtensions - { - /// - /// Creates or updates a on the specified object, and for the given namespace. - /// Both the Author and Committer will be guessed from the Git configuration. An exception will be raised if no configuration is reachable. - /// - /// The - /// The target , for which the note will be created. - /// The note message. - /// The namespace on which the note will be created. It can be either a canonical namespace or an abbreviated namespace ('refs/notes/myNamespace' or just 'myNamespace'). - /// The note which was just saved. - public static Note Add(this NoteCollection collection, ObjectId targetId, string message, string @namespace) - { - Signature author = collection.repo.Config.BuildSignature(DateTimeOffset.Now, true); - - return collection.Add(targetId, message, author, author, @namespace); - } - - /// - /// Deletes the note on the specified object, and for the given namespace. - /// Both the Author and Committer will be guessed from the Git configuration. An exception will be raised if no configuration is reachable. - /// - /// The - /// The target , for which the note will be created. - /// The namespace on which the note will be removed. It can be either a canonical namespace or an abbreviated namespace ('refs/notes/myNamespace' or just 'myNamespace'). - public static void Remove(this NoteCollection collection, ObjectId targetId, string @namespace) - { - Signature author = collection.repo.Config.BuildSignature(DateTimeOffset.Now, true); - - collection.Remove(targetId, author, author, @namespace); - } - } -} diff --git a/LibGit2Sharp/ObjectDatabase.cs b/LibGit2Sharp/ObjectDatabase.cs index a79965613..3a4ebcdb6 100644 --- a/LibGit2Sharp/ObjectDatabase.cs +++ b/LibGit2Sharp/ObjectDatabase.cs @@ -17,7 +17,7 @@ namespace LibGit2Sharp public class ObjectDatabase : IEnumerable { private readonly Repository repo; - private readonly ObjectDatabaseSafeHandle handle; + private readonly ObjectDatabaseHandle handle; /// /// Needed for mocking purposes. @@ -41,11 +41,10 @@ internal ObjectDatabase(Repository repo) /// An object that can be used to iterate through the collection. public virtual IEnumerator GetEnumerator() { - ICollection oids = Proxy.git_odb_foreach(handle, - ptr => ptr.MarshalAs()); + ICollection oids = Proxy.git_odb_foreach(handle); return oids - .Select(gitOid => repo.Lookup(new ObjectId(gitOid))) + .Select(gitOid => repo.Lookup(gitOid)) .GetEnumerator(); } @@ -99,14 +98,14 @@ public virtual Blob CreateBlob(string path) if (repo.Info.IsBare && !Path.IsPathRooted(path)) { - throw new InvalidOperationException( - string.Format(CultureInfo.InvariantCulture, - "Cannot create a blob in a bare repository from a relative path ('{0}').", path)); + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, + "Cannot create a blob in a bare repository from a relative path ('{0}').", + path)); } ObjectId id = Path.IsPathRooted(path) - ? Proxy.git_blob_create_fromdisk(repo.Handle, path) - : Proxy.git_blob_create_fromfile(repo.Handle, path); + ? Proxy.git_blob_create_fromdisk(repo.Handle, path) + : Proxy.git_blob_create_fromfile(repo.Handle, path); return repo.Lookup(id); } @@ -152,7 +151,9 @@ public int Provider(IntPtr content, int max_length, IntPtr data) if (totalRemainingBytesToRead < max_length) { - bytesToRead = totalRemainingBytesToRead > int.MaxValue ? int.MaxValue : (int)totalRemainingBytesToRead; + bytesToRead = totalRemainingBytesToRead > int.MaxValue + ? int.MaxValue + : (int)totalRemainingBytesToRead; } } @@ -163,8 +164,7 @@ public int Provider(IntPtr content, int max_length, IntPtr data) int numberOfReadBytes = stream.Read(local, 0, bytesToRead); - if (numberOfBytesToConsume.HasValue - && numberOfReadBytes == 0) + if (numberOfBytesToConsume.HasValue && numberOfReadBytes == 0) { return (int)GitErrorCode.User; } @@ -177,6 +177,55 @@ public int Provider(IntPtr content, int max_length, IntPtr data) } } + /// + /// 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 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.", "stream"); + } + + using (var odbStream = Proxy.git_odb_open_wstream(handle, numberOfBytesToConsume, GitObjectType.Blob)) + { + 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. @@ -213,7 +262,7 @@ public virtual Blob CreateBlob(Stream stream, string hintpath, long numberOfByte return CreateBlob(stream, hintpath, (long?)numberOfBytesToConsume); } - private Blob CreateBlob(Stream stream, string hintpath, long? numberOfBytesToConsume) + private unsafe Blob CreateBlob(Stream stream, string hintpath, long? numberOfBytesToConsume) { Ensure.ArgumentNotNull(stream, "stream"); @@ -228,51 +277,64 @@ private Blob CreateBlob(Stream stream, string hintpath, long? numberOfBytesToCon throw new ArgumentException("The stream cannot be read from.", "stream"); } - var proc = new Processor(stream, numberOfBytesToConsume); - ObjectId id = Proxy.git_blob_create_fromchunks(repo.Handle, hintpath, proc.Provider); - - 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) - { - Ensure.ArgumentNotNull(stream, "stream"); - - if (!stream.CanRead) - { - throw new ArgumentException("The stream cannot be read from.", "stream"); - } + IntPtr writestream_ptr = Proxy.git_blob_create_fromstream(repo.Handle, hintpath); + GitWriteStream writestream = Marshal.PtrToStructure(writestream_ptr); - using (var odbStream = Proxy.git_odb_open_wstream(handle, numberOfBytesToConsume, GitObjectType.Blob)) + try { - var buffer = new byte[4*1024]; + var buffer = new byte[4 * 1024]; long totalRead = 0; + int read = 0; - while (totalRead < numberOfBytesToConsume) + while (true) { - long left = numberOfBytesToConsume - totalRead; - int toRead = left < buffer.Length ? (int)left : buffer.Length; - var read = stream.Read(buffer, 0, toRead); + int toRead = numberOfBytesToConsume.HasValue ? + (int)Math.Min(numberOfBytesToConsume.Value - totalRead, (long)buffer.Length) : + buffer.Length; + + if (toRead > 0) + { + read = (toRead > 0) ? stream.Read(buffer, 0, toRead) : 0; + } if (read == 0) { - throw new EndOfStreamException("The stream ended unexpectedly"); + break; + } + + fixed (byte* buffer_ptr = buffer) + { + writestream.write(writestream_ptr, (IntPtr)buffer_ptr, (UIntPtr)read); } - Proxy.git_odb_stream_write(odbStream, buffer, read); totalRead += read; } - var id = Proxy.git_odb_stream_finalize_write(odbStream); - - return repo.Lookup(id); + if (numberOfBytesToConsume.HasValue && totalRead < numberOfBytesToConsume.Value) + { + throw new EndOfStreamException("The stream ended unexpectedly"); + } + } + catch(Exception e) + { + writestream.free(writestream_ptr); + throw e; } + + 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); } /// @@ -365,7 +427,35 @@ public virtual Commit CreateCommit(Signature author, Signature committer, string ObjectId commitId = Proxy.git_commit_create(repo.Handle, null, author, committer, message, tree, parentIds); - return repo.Lookup(commitId); + Commit commit = repo.Lookup(commitId); + Ensure.GitObjectIsNotNull(commit, commitId.Sha); + return commit; + } + + /// + /// Inserts a into the object database after attaching the given signature. + /// + /// The raw unsigned commit + /// The signature data + /// The header field in the commit in which to store the signature + /// The created . + public virtual ObjectId CreateCommitWithSignature(string commitContent, string signature, string field) + { + return Proxy.git_commit_create_with_signature(repo.Handle, commitContent, signature, field); + } + + /// + /// Inserts a into the object database after attaching the given signature. + /// + /// This overload uses the default header field of "gpgsig" + /// + /// + /// The raw unsigned commit + /// The signature data + /// The created . + public virtual ObjectId CreateCommitWithSignature(string commitContent, string signature) + { + return Proxy.git_commit_create_with_signature(repo.Handle, commitContent, signature, null); } /// @@ -392,6 +482,34 @@ public virtual TagAnnotation CreateTagAnnotation(string name, GitObject target, return repo.Lookup(tagId); } + /// + /// Create a TAR archive of the given tree. + /// + /// The tree. + /// The archive path. + public virtual void Archive(Tree tree, string archivePath) + { + using (var output = new FileStream(archivePath, FileMode.Create)) + using (var archiver = new TarArchiver(output)) + { + Archive(tree, archiver); + } + } + + /// + /// Create a TAR archive of the given commit. + /// + /// commit. + /// The archive path. + public virtual void Archive(Commit commit, string archivePath) + { + using (var output = new FileStream(archivePath, FileMode.Create)) + using (var archiver = new TarArchiver(output)) + { + Archive(commit, archiver); + } + } + /// /// Archive the given commit. /// @@ -433,6 +551,69 @@ public virtual HistoryDivergence CalculateHistoryDivergence(Commit one, Commit a return new HistoryDivergence(repo, one, another); } + /// + /// Performs a cherry-pick of onto commit. + /// + /// The commit to cherry-pick. + /// The commit to cherry-pick onto. + /// Which commit to consider the parent for the diff when cherry-picking a merge commit. + /// The options for the merging in the cherry-pick operation. + /// A result containing a if the cherry-pick was successful and a list of s if it is not. + public virtual MergeTreeResult CherryPickCommit(Commit cherryPickCommit, Commit cherryPickOnto, int mainline, MergeTreeOptions options) + { + Ensure.ArgumentNotNull(cherryPickCommit, "cherryPickCommit"); + Ensure.ArgumentNotNull(cherryPickOnto, "cherryPickOnto"); + + var modifiedOptions = new MergeTreeOptions(); + + // We throw away the index after looking at the conflicts, so we'll never need the REUC + // entries to be there + modifiedOptions.SkipReuc = true; + + if (options != null) + { + modifiedOptions.FailOnConflict = options.FailOnConflict; + modifiedOptions.FindRenames = options.FindRenames; + modifiedOptions.MergeFileFavor = options.MergeFileFavor; + modifiedOptions.RenameThreshold = options.RenameThreshold; + modifiedOptions.TargetLimit = options.TargetLimit; + } + + bool earlyStop; + + using (var indexHandle = CherryPickCommit(cherryPickCommit, cherryPickOnto, mainline, modifiedOptions, out earlyStop)) + { + MergeTreeResult cherryPickResult; + + // Stopped due to FailOnConflict so there's no index or conflict list + if (earlyStop) + { + return new MergeTreeResult(new Conflict[] { }); + } + + if (Proxy.git_index_has_conflicts(indexHandle)) + { + List conflicts = new List(); + Conflict conflict; + using (ConflictIteratorHandle iterator = Proxy.git_index_conflict_iterator_new(indexHandle)) + { + while ((conflict = Proxy.git_index_conflict_next(iterator)) != null) + { + conflicts.Add(conflict); + } + } + cherryPickResult = new MergeTreeResult(conflicts); + } + else + { + var treeId = Proxy.git_index_write_tree_to(indexHandle, repo.Handle); + cherryPickResult = new MergeTreeResult(this.repo.Lookup(treeId)); + } + + return cherryPickResult; + } + } + /// /// Calculates the current shortest abbreviated /// string representation for a . @@ -454,14 +635,23 @@ public virtual string ShortenObjectId(GitObject gitObject) /// 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("minLength", minLength, - string.Format("Expected value should be greater than zero and less than or equal to {0}.", ObjectId.HexSize)); + throw new ArgumentOutOfRangeException("minLength", + minLength, + string.Format("Expected value should be greater than zero and less than or equal to {0}.", + ObjectId.HexSize)); } 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; @@ -482,7 +672,13 @@ public virtual bool CanMergeWithoutConflict(Commit one, Commit another) Ensure.ArgumentNotNull(one, "one"); Ensure.ArgumentNotNull(another, "another"); - var result = repo.ObjectDatabase.MergeCommits(one, another, null); + var opts = new MergeTreeOptions() + { + SkipReuc = true, + FailOnConflict = true, + }; + + var result = repo.ObjectDatabase.MergeCommits(one, another, opts); return (result.Status == MergeTreeStatus.Succeeded); } @@ -534,9 +730,11 @@ public virtual Commit FindMergeBase(IEnumerable commits, MergeBaseFindin case MergeBaseFindingStrategy.Standard: id = Proxy.git_merge_base_many(repo.Handle, ids.ToArray()); break; + case MergeBaseFindingStrategy.Octopus: id = Proxy.git_merge_base_octopus(repo.Handle, ids.ToArray()); break; + default: throw new ArgumentException("", "strategy"); } @@ -544,61 +742,350 @@ public virtual Commit FindMergeBase(IEnumerable commits, MergeBaseFindin 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(new Conflict[] { }); + } + + 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 returned - /// index must be disposed. + /// 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 - public virtual MergeTreeResult MergeCommits(Commit ours, Commit theirs, MergeTreeOptions options) + /// The containing the merged trees and any conflicts, or null if the merge stopped early due to conflicts. + /// The index must be disposed by the caller. + public virtual TransientIndex MergeCommitsIntoIndex(Commit ours, Commit theirs, MergeTreeOptions options) { Ensure.ArgumentNotNull(ours, "ours"); Ensure.ArgumentNotNull(theirs, "theirs"); options = options ?? new MergeTreeOptions(); + bool earlyStop; + var indexHandle = MergeCommits(ours, theirs, options, out earlyStop); + if (earlyStop) + { + if (indexHandle != null) + { + indexHandle.Dispose(); + } + return null; + } + var result = new TransientIndex(indexHandle, repo); + return result; + } + + /// + /// Performs a cherry-pick of onto commit. + /// + /// The commit to cherry-pick. + /// The commit to cherry-pick onto. + /// Which commit to consider the parent for the diff when cherry-picking a merge commit. + /// The options for the merging in the cherry-pick operation. + /// The containing the cherry-pick result tree and any conflicts, or null if the merge stopped early due to conflicts. + /// The index must be disposed by the caller. + public virtual TransientIndex CherryPickCommitIntoIndex(Commit cherryPickCommit, Commit cherryPickOnto, int mainline, MergeTreeOptions options) + { + Ensure.ArgumentNotNull(cherryPickCommit, "cherryPickCommit"); + Ensure.ArgumentNotNull(cherryPickOnto, "cherryPickOnto"); + + options = options ?? new MergeTreeOptions(); + + bool earlyStop; + var indexHandle = CherryPickCommit(cherryPickCommit, cherryPickOnto, mainline, options, out earlyStop); + if (earlyStop) + { + if (indexHandle != null) + { + indexHandle.Dispose(); + } + return null; + } + var result = new TransientIndex(indexHandle, repo); + return result; + } + + /// + /// Perform a three-way merge of two commits, looking up their + /// commit ancestor. The returned index will contain the results + /// of the merge and can be examined for conflicts. + /// + /// The first tree + /// The second tree + /// The controlling the merge + /// True if the merge stopped early due to conflicts + /// The containing the merged trees and any conflicts + private IndexHandle MergeCommits(Commit ours, Commit theirs, MergeTreeOptions options, out bool earlyStop) + { + GitMergeFlag mergeFlags = GitMergeFlag.GIT_MERGE_NORMAL; + if (options.SkipReuc) + { + mergeFlags |= GitMergeFlag.GIT_MERGE_SKIP_REUC; + } + if (options.FindRenames) + { + mergeFlags |= GitMergeFlag.GIT_MERGE_FIND_RENAMES; + } + if (options.FailOnConflict) + { + mergeFlags |= GitMergeFlag.GIT_MERGE_FAIL_ON_CONFLICT; + } + var mergeOptions = new GitMergeOpts { Version = 1, MergeFileFavorFlags = options.MergeFileFavor, - MergeTreeFlags = options.FindRenames ? GitMergeTreeFlags.GIT_MERGE_TREE_FIND_RENAMES : - GitMergeTreeFlags.GIT_MERGE_TREE_NORMAL, + 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)) - using (var indexHandle = Proxy.git_merge_commits(repo.Handle, oneHandle, twoHandle, mergeOptions)) { - MergeTreeResult mergeResult; + 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(new Conflict[] { }); + } if (Proxy.git_index_has_conflicts(indexHandle)) { List conflicts = new List(); Conflict conflict; - - using (ConflictIteratorSafeHandle iterator = Proxy.git_index_conflict_iterator_new(indexHandle)) + 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); + revertTreeResult = new MergeTreeResult(conflicts); } else { var treeId = Proxy.git_index_write_tree_to(indexHandle, repo.Handle); - mergeResult = new MergeTreeResult(this.repo.Lookup(treeId)); + revertTreeResult = new MergeTreeResult(this.repo.Lookup(treeId)); } - return mergeResult; + return revertTreeResult; } } } diff --git a/LibGit2Sharp/ObjectDatabaseExtensions.cs b/LibGit2Sharp/ObjectDatabaseExtensions.cs deleted file mode 100644 index 1ed70be6c..000000000 --- a/LibGit2Sharp/ObjectDatabaseExtensions.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System.IO; - -namespace LibGit2Sharp -{ - /// - /// Provides helper overloads to a . - /// - public static class ObjectDatabaseExtensions - { - /// - /// Create a TAR archive of the given tree. - /// - /// The object database. - /// The tree. - /// The archive path. - public static void Archive(this ObjectDatabase odb, Tree tree, string archivePath) - { - using (var output = new FileStream(archivePath, FileMode.Create)) - using (var archiver = new TarArchiver(output)) - { - odb.Archive(tree, archiver); - } - } - - /// - /// Create a TAR archive of the given commit. - /// - /// The object database. - /// commit. - /// The archive path. - public static void Archive(this ObjectDatabase odb, Commit commit, string archivePath) - { - using (var output = new FileStream(archivePath, FileMode.Create)) - using (var archiver = new TarArchiver(output)) - { - odb.Archive(commit, archiver); - } - } - } -} diff --git a/LibGit2Sharp/ObjectId.cs b/LibGit2Sharp/ObjectId.cs index 7b247e8a1..9d754781c 100644 --- a/LibGit2Sharp/ObjectId.cs +++ b/LibGit2Sharp/ObjectId.cs @@ -57,6 +57,32 @@ public ObjectId(byte[] rawId) Ensure.ArgumentConformsTo(rawId, b => b.Length == rawSize, "rawId"); } + internal static unsafe ObjectId BuildFromPtr(IntPtr ptr) + { + return BuildFromPtr((git_oid*) ptr.ToPointer()); + } + + internal static unsafe ObjectId BuildFromPtr(git_oid* id) + { + return id == null ? null : new ObjectId(id->Id); + } + + internal unsafe ObjectId(byte* rawId) + { + byte[] id = new byte[GitOid.Size]; + + fixed(byte* p = id) + { + for (int i = 0; i < rawSize; i++) + { + p[i] = rawId[i]; + } + } + + this.oid = new GitOid { Id = id }; + this.sha = ToString(oid.Id, oid.Id.Length * 2); + } + /// /// Initializes a new instance of the class. /// @@ -297,12 +323,14 @@ private static bool LooksValid(string objectId, bool throwIfInvalid) return false; } - throw new ArgumentException( - string.Format(CultureInfo.InvariantCulture, "'{0}' is not a valid object identifier. Its length should be {1}.", objectId, HexSize), - "objectId"); + throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, + "'{0}' is not a valid object identifier. Its length should be {1}.", + objectId, + HexSize), + "objectId"); } - return objectId.All(c => hexDigits.Contains(c.ToString(CultureInfo.InvariantCulture))); + return objectId.All(c => hexDigits.IndexOf(c) >= 0); } /// diff --git a/LibGit2Sharp/OdbBackend.cs b/LibGit2Sharp/OdbBackend.cs index 8ecd3f734..d96e15909 100644 --- a/LibGit2Sharp/OdbBackend.cs +++ b/LibGit2Sharp/OdbBackend.cs @@ -33,10 +33,7 @@ internal void Free() /// /// In your subclass, override this member to provide the list of actions your backend supports. /// - protected abstract OdbBackendOperations SupportedOperations - { - get; - } + protected abstract OdbBackendOperations SupportedOperations { get; } /// /// Call this method from your implementations of Read and ReadPrefix to allocate a buffer in @@ -64,8 +61,7 @@ protected UnmanagedMemoryStream AllocateAndBuildFrom(byte[] bytes) /// A Stream for you to write to and then return. Do not dispose this object before returning it. protected unsafe UnmanagedMemoryStream Allocate(long size) { - if (size < 0 || - (UIntPtr.Size == sizeof(int) && size > int.MaxValue)) + if (size < 0 || (UIntPtr.Size == sizeof(int) && size > int.MaxValue)) { throw new ArgumentOutOfRangeException("size"); } @@ -330,7 +326,7 @@ private unsafe static int ReadPrefix( try { - var shortSha = ObjectId.ToString(short_oid.Id, (int) len); + var shortSha = ObjectId.ToString(short_oid.Id, (int)len); ObjectId oid; ObjectType objectType; @@ -545,7 +541,7 @@ private static int ExistsPrefix( found_oid.Id = ObjectId.Zero.RawId; int result = odbBackend.ExistsPrefix(shortSha, out found); - if (result == (int) GitErrorCode.Ok) + if (result == (int)GitErrorCode.Ok) { found_oid.Id = found.RawId; } @@ -622,7 +618,7 @@ private unsafe int CallbackMethod(ObjectId id) { var oid = id.RawId; - fixed(void* ptr = &oid[0]) + fixed (void* ptr = &oid[0]) { return cb(new IntPtr(ptr), data); } @@ -639,11 +635,10 @@ internal static long ConverToLong(UIntPtr len) { if (len.ToUInt64() > long.MaxValue) { - throw new InvalidOperationException( - string.Format( - CultureInfo.InvariantCulture, - "Provided length ({0}) exceeds long.MaxValue ({1}).", - len.ToUInt64(), long.MaxValue)); + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, + "Provided length ({0}) exceeds long.MaxValue ({1}).", + len.ToUInt64(), + long.MaxValue)); } return (long)len.ToUInt64(); diff --git a/LibGit2Sharp/OdbBackendStream.cs b/LibGit2Sharp/OdbBackendStream.cs index 6397b7bb3..2889ac20b 100644 --- a/LibGit2Sharp/OdbBackendStream.cs +++ b/LibGit2Sharp/OdbBackendStream.cs @@ -45,48 +45,34 @@ protected virtual void Dispose() /// /// If true, then it is legal to call the Read method. /// - public abstract bool CanRead - { - get; - } + public abstract bool CanRead { get; } /// /// If true, then it is legal to call the Write and FinalizeWrite methods. /// - public abstract bool CanWrite - { - get; - } + public abstract bool CanWrite { get; } /// /// Requests that the stream write the next length bytes of the stream to the provided Stream object. /// - public abstract int Read( - Stream dataStream, - long length); + public abstract int Read(Stream dataStream, long length); /// /// Requests that the stream write the first length bytes of the provided Stream object to the stream. /// - public abstract int Write( - Stream dataStream, - long length); + public abstract int Write(Stream dataStream, long length); /// /// After all bytes have been written to the stream, the object ID is provided to FinalizeWrite. /// - public abstract int FinalizeWrite( - ObjectId id); + public abstract int FinalizeWrite(ObjectId id); /// /// The backend object this stream was created by. /// public virtual OdbBackend Backend { - get - { - return this.backend; - } + get { return this.backend; } } private readonly OdbBackend backend; @@ -138,10 +124,7 @@ private static class BackendStreamEntryPoints public static readonly GitOdbBackendStream.finalize_write_callback FinalizeWriteCallback = FinalizeWrite; public static readonly GitOdbBackendStream.free_callback FreeCallback = Free; - private unsafe static int Read( - IntPtr stream, - IntPtr buffer, - UIntPtr len) + private unsafe static int Read(IntPtr stream, IntPtr buffer, UIntPtr len) { OdbBackendStream odbBackendStream = GCHandle.FromIntPtr(Marshal.ReadIntPtr(stream, GitOdbBackendStream.GCHandleOffset)).Target as OdbBackendStream; @@ -165,10 +148,7 @@ private unsafe static int Read( return (int)GitErrorCode.Error; } - private static unsafe int Write( - IntPtr stream, - IntPtr buffer, - UIntPtr len) + private static unsafe int Write(IntPtr stream, IntPtr buffer, UIntPtr len) { OdbBackendStream odbBackendStream = GCHandle.FromIntPtr(Marshal.ReadIntPtr(stream, GitOdbBackendStream.GCHandleOffset)).Target as OdbBackendStream; @@ -192,9 +172,7 @@ private static unsafe int Write( return (int)GitErrorCode.Error; } - private static int FinalizeWrite( - IntPtr stream, - ref GitOid oid) + private static int FinalizeWrite(IntPtr stream, ref GitOid oid) { OdbBackendStream odbBackendStream = GCHandle.FromIntPtr(Marshal.ReadIntPtr(stream, GitOdbBackendStream.GCHandleOffset)).Target as OdbBackendStream; @@ -213,8 +191,7 @@ private static int FinalizeWrite( 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; diff --git a/LibGit2Sharp/PackBuilder.cs b/LibGit2Sharp/PackBuilder.cs new file mode 100644 index 000000000..dcaa2617a --- /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", "value"); + } + + nThreads = value; + } + get + { + return nThreads; + } + } + } +} diff --git a/LibGit2Sharp/Patch.cs b/LibGit2Sharp/Patch.cs index 4bbb884bd..2cd4d1605 100644 --- a/LibGit2Sharp/Patch.cs +++ b/LibGit2Sharp/Patch.cs @@ -16,7 +16,7 @@ namespace LibGit2Sharp /// deleted, modified, ..., then consider using a simpler . /// [DebuggerDisplay("{DebuggerDisplay,nq}")] - public class Patch : IEnumerable + public class Patch : IEnumerable, IDiffResult { private readonly StringBuilder fullPatchBuilder = new StringBuilder(); @@ -30,35 +30,39 @@ public class Patch : IEnumerable protected Patch() { } - internal Patch(DiffSafeHandle diff) + internal unsafe Patch(DiffHandle diff) { - int count = Proxy.git_diff_num_deltas(diff); - for (int i = 0; i < count; i++) + using (diff) { - using (var patch = Proxy.git_patch_from_diff(diff, i)) + int count = Proxy.git_diff_num_deltas(diff); + for (int i = 0; i < count; i++) { - var delta = Proxy.git_diff_get_delta(diff, i); - AddFileChange(delta); - Proxy.git_patch_print(patch, PrintCallBack); + using (var patch = Proxy.git_patch_from_diff(diff, i)) + { + var delta = Proxy.git_diff_get_delta(diff, i); + AddFileChange(delta); + Proxy.git_patch_print(patch, PrintCallBack); + } } - } } - private void AddFileChange(GitDiffDelta delta) + private unsafe void AddFileChange(git_diff_delta* delta) { var treeEntryChanges = new TreeEntryChanges(delta); - changes.Add(treeEntryChanges.Path, new PatchEntryChanges(delta.IsBinary(), treeEntryChanges)); + changes.Add(treeEntryChanges.Path, new PatchEntryChanges(delta->flags.HasFlag(GitDiffFlags.GIT_DIFF_FLAG_BINARY), treeEntryChanges)); } - private int PrintCallBack(GitDiffDelta delta, GitDiffHunk hunk, GitDiffLine line, IntPtr payload) + private unsafe int PrintCallBack(git_diff_delta* delta, GitDiffHunk hunk, GitDiffLine line, IntPtr payload) { string patchPart = LaxUtf8Marshaler.FromNative(line.content, (int)line.contentLen); // Deleted files mean no "new file" path - var pathPtr = delta.NewFile.Path != IntPtr.Zero ? delta.NewFile.Path : delta.OldFile.Path; + var pathPtr = delta->new_file.Path != null + ? delta->new_file.Path + : delta->old_file.Path; var filePath = LaxFilePathMarshaler.FromNative(pathPtr); PatchEntryChanges currentChange = this[filePath]; @@ -164,7 +168,7 @@ public virtual string Content /// /// . /// The patch content as string. - public static implicit operator string(Patch patch) + public static implicit operator string (Patch patch) { return patch.fullPatchBuilder.ToString(); } @@ -174,8 +178,29 @@ private string DebuggerDisplay get { return string.Format(CultureInfo.InvariantCulture, - "+{0} -{1}", linesAdded, linesDeleted); + "+{0} -{1}", + linesAdded, + linesDeleted); } } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases unmanaged and - optionally - managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + // This doesn't do anything yet because it loads everything + // eagerly and disposes of the diff handle in the constructor. + } } } diff --git a/LibGit2Sharp/PatchStats.cs b/LibGit2Sharp/PatchStats.cs index f22eecb1a..3d6bb46cd 100644 --- a/LibGit2Sharp/PatchStats.cs +++ b/LibGit2Sharp/PatchStats.cs @@ -13,7 +13,7 @@ namespace LibGit2Sharp /// The individual patches for each file can be accessed through the indexer of this class. /// [DebuggerDisplay("{DebuggerDisplay,nq}")] - public class PatchStats : IEnumerable + public class PatchStats : IEnumerable, IDiffResult { private readonly IDictionary changes = new Dictionary(); private readonly int totalLinesAdded; @@ -25,25 +25,27 @@ public class PatchStats : IEnumerable protected PatchStats() { } - internal PatchStats(DiffSafeHandle diff) + internal unsafe PatchStats(DiffHandle diff) { - int count = Proxy.git_diff_num_deltas(diff); - for (int i = 0; i < count; i++) + using (diff) { - using (var patch = Proxy.git_patch_from_diff(diff, i)) + int count = Proxy.git_diff_num_deltas(diff); + for (int i = 0; i < count; i++) { - var delta = Proxy.git_diff_get_delta(diff, i); - var pathPtr = delta.NewFile.Path != IntPtr.Zero ? delta.NewFile.Path : delta.OldFile.Path; - var newFilePath = LaxFilePathMarshaler.FromNative(pathPtr); + using (var patch = Proxy.git_patch_from_diff(diff, i)) + { + var delta = Proxy.git_diff_get_delta(diff, i); + var pathPtr = delta->new_file.Path != null ? delta->new_file.Path : delta->old_file.Path; + var newFilePath = LaxFilePathMarshaler.FromNative(pathPtr); - var stats = Proxy.git_patch_line_stats(patch); - int added = stats.Item1; - int deleted = stats.Item2; - changes.Add(newFilePath, new ContentChangeStats(added, deleted)); - totalLinesAdded += added; - totalLinesDeleted += deleted; + var stats = Proxy.git_patch_line_stats(patch); + int added = stats.Item1; + int deleted = stats.Item2; + changes.Add(newFilePath, new ContentChangeStats(added, deleted)); + totalLinesAdded += added; + totalLinesDeleted += deleted; + } } - } } @@ -75,7 +77,7 @@ IEnumerator IEnumerable.GetEnumerator() /// public virtual ContentChangeStats this[string path] { - get { return this[(FilePath) path]; } + get { return this[(FilePath)path]; } } private ContentChangeStats this[FilePath path] @@ -111,9 +113,30 @@ private string DebuggerDisplay { get { - return string.Format(CultureInfo.InvariantCulture, "+{0} -{1}", - TotalLinesAdded, TotalLinesDeleted); + return string.Format(CultureInfo.InvariantCulture, + "+{0} -{1}", + TotalLinesAdded, + TotalLinesDeleted); } } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases unmanaged and - optionally - managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + // This doesn't do anything yet because it loads everything + // eagerly and disposes of the diff handle in the constructor. + } } } diff --git a/LibGit2Sharp/PeelException.cs b/LibGit2Sharp/PeelException.cs index e7fbfe796..d7758d7c9 100644 --- a/LibGit2Sharp/PeelException.cs +++ b/LibGit2Sharp/PeelException.cs @@ -9,14 +9,13 @@ namespace LibGit2Sharp /// target type due to the object model. /// [Serializable] - public class PeelException : LibGit2SharpException + 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. @@ -24,8 +23,16 @@ public PeelException() /// 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. @@ -34,8 +41,7 @@ public PeelException(string message) /// The exception that is the cause of the current exception. If the parameter is not a null reference, the current exception is raised in a catch block that handles the inner exception. public PeelException(string message, Exception innerException) : base(message, innerException) - { - } + { } /// /// Initializes a new instance of the class with a serialized data. @@ -44,12 +50,18 @@ public PeelException(string message, Exception innerException) /// The that contains contextual information about the source or destination. protected PeelException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } + { } + + internal PeelException(string message, GitErrorCategory category) + : base(message, category) + { } - internal PeelException(string message, GitErrorCode code, GitErrorCategory category) - : base(message, code, category) + internal override GitErrorCode ErrorCode { + get + { + return GitErrorCode.Peel; + } } } } diff --git a/LibGit2Sharp/Properties/AssemblyInfo.cs b/LibGit2Sharp/Properties/AssemblyInfo.cs index b848dc65a..ffa977d1d 100644 --- a/LibGit2Sharp/Properties/AssemblyInfo.cs +++ b/LibGit2Sharp/Properties/AssemblyInfo.cs @@ -1,24 +1,10 @@ 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 © LibGit2Sharp contributors")] - [assembly: CLSCompliant(true)] // Setting ComVisible to false makes the types in this assembly not visible @@ -30,18 +16,3 @@ // 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.22.0")] -[assembly: AssemblyFileVersion("0.22.0")] -[assembly: AssemblyInformationalVersion("0.22.0-dev00000000000000")] diff --git a/LibGit2Sharp/PushOptions.cs b/LibGit2Sharp/PushOptions.cs index 15e6af691..99c65dd8b 100644 --- a/LibGit2Sharp/PushOptions.cs +++ b/LibGit2Sharp/PushOptions.cs @@ -12,6 +12,12 @@ public sealed class PushOptions /// public CredentialsHandler CredentialsProvider { get; set; } + /// + /// This handler will be called to let the user make a decision on whether to allow + /// the connection to preoceed based on the certificate presented by the server. + /// + public CertificateCheckHandler CertificateCheck { get; set; } + /// /// If the transport being used to push to the remote requires the creation /// of a pack file, this controls the number of worker threads used by @@ -39,5 +45,11 @@ public sealed class PushOptions /// 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; } } } diff --git a/LibGit2Sharp/PushResult.cs b/LibGit2Sharp/PushResult.cs index bf85e3ab8..713f13a55 100644 --- a/LibGit2Sharp/PushResult.cs +++ b/LibGit2Sharp/PushResult.cs @@ -18,10 +18,7 @@ protected PushResult() /// public virtual IEnumerable FailedPushUpdates { - get - { - return failedPushUpdates; - } + get { return failedPushUpdates; } } /// @@ -30,10 +27,7 @@ public virtual IEnumerable FailedPushUpdates /// public virtual bool HasErrors { - get - { - return failedPushUpdates.Count > 0; - } + get { return failedPushUpdates.Count > 0; } } internal PushResult(List failedPushUpdates) diff --git a/LibGit2Sharp/PushUpdate.cs b/LibGit2Sharp/PushUpdate.cs new file mode 100644 index 000000000..bbabb6817 --- /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..00dc3f267 --- /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 index e643df648..cf4479701 100644 --- a/LibGit2Sharp/RecurseSubmodulesException.cs +++ b/LibGit2Sharp/RecurseSubmodulesException.cs @@ -15,8 +15,7 @@ public class RecurseSubmodulesException : LibGit2SharpException /// Initializes a new instance of the class. /// public RecurseSubmodulesException() - { - } + { } /// /// The path to the initial repository the operation was run on. @@ -34,5 +33,14 @@ public RecurseSubmodulesException(string message, Exception innerException, stri { InitialRepositoryPath = initialRepositoryPath; } + + /// + /// 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) + { } } } diff --git a/LibGit2Sharp/RefSpec.cs b/LibGit2Sharp/RefSpec.cs index d0ac8b3b4..5819820eb 100644 --- a/LibGit2Sharp/RefSpec.cs +++ b/LibGit2Sharp/RefSpec.cs @@ -1,4 +1,5 @@ -using System.Diagnostics; +using System; +using System.Diagnostics; using System.Globalization; using LibGit2Sharp.Core; using LibGit2Sharp.Core.Handles; @@ -11,17 +12,16 @@ namespace LibGit2Sharp [DebuggerDisplay("{DebuggerDisplay,nq}")] public class RefSpec { - private RefSpec(string refSpec, RefSpecDirection direction, string source, string destination, bool forceUpdate) - { - Ensure.ArgumentNotNullOrEmptyString(refSpec, "refSpec"); - Ensure.ArgumentNotNull(source, "source"); - Ensure.ArgumentNotNull(destination, "destination"); + // This is here to keep the pointer alive + #pragma warning disable 0414 + readonly Remote remote; + #pragma warning restore 0414 + readonly IntPtr handle; - Specification = refSpec; - Direction = direction; - Source = source; - Destination = destination; - ForceUpdate = forceUpdate; + internal unsafe RefSpec(Remote remote, git_refspec* handle) + { + this.remote = remote; + this.handle = new IntPtr(handle); } /// @@ -30,45 +30,106 @@ private RefSpec(string refSpec, RefSpecDirection direction, string source, strin protected RefSpec() { } - internal static RefSpec BuildFromPtr(GitRefSpecHandle handle) - { - Ensure.ArgumentNotNull(handle, "handle"); - - return new RefSpec(Proxy.git_refspec_string(handle), Proxy.git_refspec_direction(handle), - Proxy.git_refspec_src(handle), Proxy.git_refspec_dst(handle), Proxy.git_refspec_force(handle)); - } - /// /// Gets the pattern describing the mapping between remote and local references /// - public virtual string Specification { get; private set; } + public virtual string Specification + { + get + { + return Proxy.git_refspec_string(this.handle); + } + } /// /// Indicates whether this is intended to be used during a Push or Fetch operation /// - public virtual RefSpecDirection Direction { get; private set; } + public virtual RefSpecDirection Direction + { + get + { + return Proxy.git_refspec_direction(this.handle); + } + } /// /// The source reference specifier /// - public virtual string Source { get; private set; } + public virtual string Source + { + get + { + return Proxy.git_refspec_src(this.handle); + } + } /// /// The target reference specifier /// - public virtual string Destination { get; private set; } + public virtual string Destination + { + get + { + return Proxy.git_refspec_dst(this.handle); + } + } /// /// Indicates whether the destination will be force-updated if fast-forwarding is not possible /// - public virtual bool ForceUpdate { get; private set; } + public virtual bool ForceUpdate + { + get + { + return Proxy.git_refspec_force(this.handle); + } + } + + /// + /// Check whether the given reference matches the source (lhs) part of + /// this refspec. + /// + /// The reference name to check + public virtual bool SourceMatches(string reference) + { + return Proxy.git_refspec_src_matches(handle, reference); + } + + /// + /// Check whether the given reference matches the target (rhs) part of + /// this refspec. + /// + /// The reference name to check + public virtual bool DestinationMatches(string reference) + { + return Proxy.git_refspec_dst_matches(handle, reference); + } + + /// + /// Perform the transformation described by this refspec on the given + /// reference name (from source to destination). + /// + /// The reference name to transform + public virtual string Transform(string reference) + { + return Proxy.git_refspec_transform(handle, reference); + } + + /// + /// Perform the reverse of the transformation described by this refspec + /// on the given reference name (from destination to source). + /// + /// The reference name to transform + public virtual string ReverseTransform(string reference) + { + return Proxy.git_refspec_rtransform(handle, reference); + } private string DebuggerDisplay { get { - return string.Format(CultureInfo.InvariantCulture, - "{0}", Specification); + return string.Format(CultureInfo.InvariantCulture, "{0}", Specification); } } } diff --git a/LibGit2Sharp/RefSpecCollection.cs b/LibGit2Sharp/RefSpecCollection.cs index 1b7b5a137..6ba813e47 100644 --- a/LibGit2Sharp/RefSpecCollection.cs +++ b/LibGit2Sharp/RefSpecCollection.cs @@ -1,4 +1,5 @@ -using System.Collections; +using System; +using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; @@ -14,7 +15,12 @@ namespace LibGit2Sharp [DebuggerDisplay("{DebuggerDisplay,nq}")] public class RefSpecCollection : IEnumerable { - readonly IList refspecs; + // These are here to keep the pointer alive + #pragma warning disable 0414 + readonly Remote remote; + readonly RemoteHandle handle; + #pragma warning restore 0414 + readonly Lazy> refspecs; /// /// Needed for mocking purposes. @@ -22,28 +28,27 @@ public class RefSpecCollection : IEnumerable protected RefSpecCollection() { } - internal RefSpecCollection(RemoteSafeHandle handle) + internal RefSpecCollection(Remote remote, RemoteHandle handle) { Ensure.ArgumentNotNull(handle, "handle"); - refspecs = RetrieveRefSpecs(handle); + this.remote = remote; + this.handle = handle; + + refspecs = new Lazy>(() => RetrieveRefSpecs(remote, handle)); } - static IList RetrieveRefSpecs(RemoteSafeHandle remoteHandle) + static unsafe IList RetrieveRefSpecs(Remote remote, RemoteHandle remoteHandle) { int count = Proxy.git_remote_refspec_count(remoteHandle); List refSpecs = new List(); for (int i = 0; i < count; i++) { - using (GitRefSpecHandle handle = Proxy.git_remote_get_refspec(remoteHandle, i)) - { - refSpecs.Add(RefSpec.BuildFromPtr(handle)); - } + refSpecs.Add(new RefSpec(remote, Proxy.git_remote_get_refspec(remoteHandle, i))); } return refSpecs; - } /// @@ -52,7 +57,7 @@ static IList RetrieveRefSpecs(RemoteSafeHandle remoteHandle) /// An object that can be used to iterate through the collection. public virtual IEnumerator GetEnumerator() { - return refspecs.GetEnumerator(); + return refspecs.Value.GetEnumerator(); } /// @@ -68,8 +73,7 @@ private string DebuggerDisplay { get { - return string.Format(CultureInfo.InvariantCulture, - "Count = {0}", this.Count()); + return string.Format(CultureInfo.InvariantCulture, "Count = {0}", this.Count()); } } } diff --git a/LibGit2Sharp/Reference.cs b/LibGit2Sharp/Reference.cs index 5b0787ede..40a85f79f 100644 --- a/LibGit2Sharp/Reference.cs +++ b/LibGit2Sharp/Reference.cs @@ -36,7 +36,13 @@ internal Reference(IRepository repo, string canonicalName, string targetIdentifi 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.Handle, 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); @@ -59,7 +65,7 @@ internal static T BuildFromPtr(ReferenceSafeHandle handle, Repository repo) w break; default: - throw new LibGit2SharpException(String.Format(CultureInfo.InvariantCulture, "Unable to build a new reference from a type '{0}'.", type)); + throw new LibGit2SharpException("Unable to build a new reference from a type '{0}'.", type); } return reference as T; @@ -83,6 +89,42 @@ public static bool IsValidName(string canonicalName) return Proxy.git_reference_is_valid_name(canonicalName); } + /// + /// Determine if the current is a local branch. + /// + /// true if the current is a local branch, false otherwise. + public virtual bool IsLocalBranch + { + get { return CanonicalName.LooksLikeLocalBranch(); } + } + + /// + /// Determine if the current is a remote tracking branch. + /// + /// true if the current is a remote tracking branch, false otherwise. + public virtual bool IsRemoteTrackingBranch + { + get { return CanonicalName.LooksLikeRemoteTrackingBranch(); } + } + + /// + /// Determine if the current is a tag. + /// + /// true if the current is a tag, false otherwise. + public virtual bool IsTag + { + get { return CanonicalName.LooksLikeTag(); } + } + + /// + /// Determine if the current is a note. + /// + /// true if the current is a note, false otherwise. + public virtual bool IsNote + { + get { return CanonicalName.LooksLikeNote(); } + } + /// /// Gets the full name of this reference. /// @@ -195,10 +237,23 @@ private string DebuggerDisplay get { return string.Format(CultureInfo.InvariantCulture, - "{0} => \"{1}\"", CanonicalName, TargetIdentifier); + "{0} => \"{1}\"", + CanonicalName, + TargetIdentifier); } } - IRepository IBelongToARepository.Repository { get { return repo; } } + IRepository IBelongToARepository.Repository + { + get + { + if (repo == null) + { + throw new InvalidOperationException("Repository requires a local repository"); + } + + return repo; + } + } } } diff --git a/LibGit2Sharp/ReferenceCollection.cs b/LibGit2Sharp/ReferenceCollection.cs index ce0ed9577..602a20f17 100644 --- a/LibGit2Sharp/ReferenceCollection.cs +++ b/LibGit2Sharp/ReferenceCollection.cs @@ -66,6 +66,106 @@ IEnumerator IEnumerable.GetEnumerator() #endregion + /// + /// Creates a direct or symbolic reference with the specified name and target + /// + /// The name of the reference to create. + /// The target which can be either the canonical name of a reference or a revparse spec. + /// The optional message to log in the when adding the + /// A new . + public virtual Reference Add(string name, string canonicalRefNameOrObjectish, + string logMessage) + { + return Add(name, canonicalRefNameOrObjectish, logMessage, false); + } + + private enum RefState + { + Exists, + DoesNotExistButLooksValid, + DoesNotLookValid, + } + + private static RefState TryResolveReference(out Reference reference, ReferenceCollection refsColl, string canonicalName) + { + if (!Reference.IsValidName(canonicalName)) + { + reference = null; + return RefState.DoesNotLookValid; + } + + reference = refsColl[canonicalName]; + + return reference != null ? RefState.Exists : RefState.DoesNotExistButLooksValid; + } + + /// + /// Creates a direct or symbolic reference with the specified name and target + /// + /// The name of the reference to create. + /// The target which can be either the canonical name of a reference or a revparse spec. + /// The optional message to log in the when adding the + /// True to allow silent overwriting a potentially existing reference, false otherwise. + /// A new . + public virtual Reference Add(string name, string canonicalRefNameOrObjectish, string logMessage, bool allowOverwrite) + { + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + Ensure.ArgumentNotNullOrEmptyString(canonicalRefNameOrObjectish, "canonicalRefNameOrObjectish"); + + Reference reference; + RefState refState = TryResolveReference(out reference, this, canonicalRefNameOrObjectish); + + var gitObject = repo.Lookup(canonicalRefNameOrObjectish, GitObjectType.Any, LookUpOptions.None); + + if (refState == RefState.Exists) + { + return Add(name, reference, logMessage, allowOverwrite); + } + + if (refState == RefState.DoesNotExistButLooksValid && gitObject == null) + { + using (ReferenceHandle handle = Proxy.git_reference_symbolic_create(repo.Handle, name, canonicalRefNameOrObjectish, allowOverwrite, + logMessage)) + { + return Reference.BuildFromPtr(handle, repo); + } + } + + Ensure.GitObjectIsNotNull(gitObject, canonicalRefNameOrObjectish); + + if (logMessage == null) + { + logMessage = string.Format(CultureInfo.InvariantCulture, "{0}: Created from {1}", + name.LooksLikeLocalBranch() ? "branch" : "reference", canonicalRefNameOrObjectish); + } + + EnsureHasLog(name); + return Add(name, gitObject.Id, logMessage, allowOverwrite); + } + + + /// + /// Creates a direct or symbolic reference with the specified name and target + /// + /// The name of the reference to create. + /// The target which can be either the canonical name of a reference or a revparse spec. + /// A new . + public virtual Reference Add(string name, string canonicalRefNameOrObjectish) + { + return Add(name, canonicalRefNameOrObjectish, null, false); + } + + /// + /// Creates a direct or symbolic reference with the specified name and target + /// + /// The name of the reference to create. + /// The target which can be either the canonical name of a reference or a revparse spec. + /// True to allow silent overwriting a potentially existing reference, false otherwise. + /// A new . + public virtual Reference Add(string name, string canonicalRefNameOrObjectish, bool allowOverwrite) + { + return Add(name, canonicalRefNameOrObjectish, null, allowOverwrite); + } /// /// Creates a direct reference with the specified name and target /// @@ -91,7 +191,7 @@ public virtual DirectReference Add(string name, ObjectId targetId, string logMes Ensure.ArgumentNotNullOrEmptyString(name, "name"); Ensure.ArgumentNotNull(targetId, "targetId"); - using (ReferenceSafeHandle handle = Proxy.git_reference_create(repo.Handle, name, targetId, allowOverwrite, logMessage)) + using (ReferenceHandle handle = Proxy.git_reference_create(repo.Handle, name, targetId, allowOverwrite, logMessage)) { return (DirectReference)Reference.BuildFromPtr(handle, repo); } @@ -145,8 +245,11 @@ public virtual SymbolicReference Add(string name, Reference targetRef, string lo Ensure.ArgumentNotNullOrEmptyString(name, "name"); Ensure.ArgumentNotNull(targetRef, "targetRef"); - using (ReferenceSafeHandle handle = Proxy.git_reference_symbolic_create(repo.Handle, name, targetRef.CanonicalName, - allowOverwrite, logMessage)) + using (ReferenceHandle handle = Proxy.git_reference_symbolic_create(repo.Handle, + name, + targetRef.CanonicalName, + allowOverwrite, + logMessage)) { return (SymbolicReference)Reference.BuildFromPtr(handle, repo); } @@ -175,6 +278,24 @@ public virtual SymbolicReference Add(string name, Reference targetRef, bool allo return Add(name, targetRef, null, allowOverwrite); } + /// + /// Remove a reference with the specified name + /// + /// The canonical name of the reference to delete. + public virtual void Remove(string name) + { + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + + Reference reference = this[name]; + + if (reference == null) + { + return; + } + + Remove(reference); + } + /// /// Remove a reference from the repository /// @@ -213,17 +334,83 @@ public virtual Reference Rename(Reference reference, string newName, string logM if (logMessage == null) { - logMessage = string.Format(CultureInfo.InvariantCulture, "{0}: renamed {1} to {2}", - reference.IsLocalBranch() ? "branch" : "reference", reference.CanonicalName, newName); + logMessage = string.Format(CultureInfo.InvariantCulture, + "{0}: renamed {1} to {2}", + reference.IsLocalBranch + ? "branch" + : "reference", + reference.CanonicalName, + newName); } - using (ReferenceSafeHandle referencePtr = RetrieveReferencePtr(reference.CanonicalName)) - using (ReferenceSafeHandle handle = Proxy.git_reference_rename(referencePtr, newName, allowOverwrite, logMessage)) + 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 /// @@ -251,9 +438,11 @@ 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); } } @@ -279,13 +468,99 @@ public virtual Reference UpdateTarget(Reference directRef, ObjectId targetId, st private Reference UpdateDirectReferenceTarget(Reference directRef, ObjectId targetId, string logMessage) { - using (ReferenceSafeHandle referencePtr = RetrieveReferencePtr(directRef.CanonicalName)) - using (ReferenceSafeHandle handle = Proxy.git_reference_set_target(referencePtr, targetId, logMessage)) + using (ReferenceHandle referencePtr = RetrieveReferencePtr(directRef.CanonicalName)) + using (ReferenceHandle handle = Proxy.git_reference_set_target(referencePtr, targetId, logMessage)) { return Reference.BuildFromPtr(handle, repo); } } + /// + /// Updates the target of a direct reference. + /// + /// The direct reference which target should be updated. + /// The revparse spec of the target. + /// The optional message to log in the + /// A new . + public virtual Reference UpdateTarget(Reference directRef, string objectish, string logMessage) + { + Ensure.ArgumentNotNull(directRef, "directRef"); + Ensure.ArgumentNotNull(objectish, "objectish"); + + GitObject target = repo.Lookup(objectish); + + Ensure.GitObjectIsNotNull(target, objectish); + + return UpdateTarget(directRef, target.Id, logMessage); + } + + /// + /// Updates the target of a direct reference + /// + /// The direct reference which target should be updated. + /// The revparse spec of the target. + /// A new . + public virtual Reference UpdateTarget(Reference directRef, string objectish) + { + return UpdateTarget(directRef, objectish, null); + } + + /// + /// Updates the target of a reference + /// + /// The canonical name of the reference. + /// The target which can be either the canonical name of a reference or a revparse spec. + /// The optional message to log in the of the reference. + /// A new . + public virtual Reference UpdateTarget(string name, string canonicalRefNameOrObjectish, string logMessage) + { + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + Ensure.ArgumentNotNullOrEmptyString(canonicalRefNameOrObjectish, "canonicalRefNameOrObjectish"); + + if (name == "HEAD") + { + return UpdateHeadTarget(canonicalRefNameOrObjectish, logMessage); + } + + Reference reference = this[name]; + + var directReference = reference as DirectReference; + if (directReference != null) + { + return UpdateTarget(directReference, canonicalRefNameOrObjectish, logMessage); + } + + var symbolicReference = reference as SymbolicReference; + if (symbolicReference != null) + { + Reference targetRef; + + RefState refState = TryResolveReference(out targetRef, this, canonicalRefNameOrObjectish); + + if (refState == RefState.DoesNotLookValid) + { + throw new ArgumentException(String.Format(CultureInfo.InvariantCulture, "The reference specified by {0} is a Symbolic reference, you must provide a reference canonical name as the target.", name), "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 /// @@ -319,8 +594,8 @@ public virtual Reference UpdateTarget(Reference symbolicRef, Reference targetRef private Reference UpdateSymbolicRefenceTarget(Reference symbolicRef, Reference targetRef, string logMessage) { - using (ReferenceSafeHandle referencePtr = RetrieveReferencePtr(symbolicRef.CanonicalName)) - using (ReferenceSafeHandle handle = Proxy.git_reference_symbolic_set_target(referencePtr, targetRef.CanonicalName, 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); } @@ -366,7 +641,8 @@ internal Reference MoveHeadTarget(T target) else { throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, - "'{0}' is not a valid target type.", typeof(T))); + "'{0}' is not a valid target type.", + typeof(T))); } return repo.Refs.Head; @@ -395,9 +671,9 @@ internal Reference UpdateHeadTarget(string target, string logMessage) 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; } @@ -427,12 +703,83 @@ public virtual Reference Head get { return this["HEAD"]; } } + + /// + /// Find the s among + /// that can reach at least one in the specified . + /// + /// The set of s to examine. + /// The set of s that are interesting. + /// A subset of that can reach at least one within . + public virtual IEnumerable ReachableFrom( + IEnumerable refSubset, + IEnumerable targets) + { + Ensure.ArgumentNotNull(refSubset, "refSubset"); + Ensure.ArgumentNotNull(targets, "targets"); + + var refs = new List(refSubset); + if (refs.Count == 0) + { + return Enumerable.Empty(); + } + + List targetsSet = targets.Select(c => c.Id).Distinct().ToList(); + if (targetsSet.Count == 0) + { + return Enumerable.Empty(); + } + + var result = new List(); + + foreach (var reference in refs) + { + var peeledTargetCommit = reference + .ResolveToDirectReference() + .Target.Peel(false); + + if (peeledTargetCommit == null) + { + continue; + } + + var commitId = peeledTargetCommit.Id; + + foreach (var potentialAncestorId in targetsSet) + { + if (potentialAncestorId == commitId) + { + result.Add(reference); + break; + } + + if (Proxy.git_graph_descendant_of(repo.Handle, commitId, potentialAncestorId)) + { + result.Add(reference); + break; + } + } + } + + return result; + } + + /// + /// Find the s + /// that can reach at least one in the specified . + /// + /// The set of s that are interesting. + /// The list of that can reach at least one within . + public virtual IEnumerable ReachableFrom(IEnumerable targets) + { + return ReachableFrom(this, targets); + } + private string DebuggerDisplay { get { - return string.Format(CultureInfo.InvariantCulture, - "Count = {0}", this.Count()); + return string.Format(CultureInfo.InvariantCulture, "Count = {0}", this.Count()); } } diff --git a/LibGit2Sharp/ReferenceCollectionExtensions.cs b/LibGit2Sharp/ReferenceCollectionExtensions.cs deleted file mode 100644 index 5fb2f6dde..000000000 --- a/LibGit2Sharp/ReferenceCollectionExtensions.cs +++ /dev/null @@ -1,369 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using LibGit2Sharp.Core; -using LibGit2Sharp.Core.Handles; - -namespace LibGit2Sharp -{ - /// - /// Provides helper overloads to a . - /// - public static class ReferenceCollectionExtensions - { - private enum RefState - { - Exists, - DoesNotExistButLooksValid, - DoesNotLookValid, - } - - private static RefState TryResolveReference(out Reference reference, ReferenceCollection refsColl, string canonicalName) - { - if (!Reference.IsValidName(canonicalName)) - { - reference = null; - return RefState.DoesNotLookValid; - } - - reference = refsColl[canonicalName]; - - return reference != null ? RefState.Exists : RefState.DoesNotExistButLooksValid; - } - - /// - /// Creates a direct or symbolic reference with the specified name and target - /// - /// The being worked with. - /// The name of the reference to create. - /// The target which can be either the canonical name of a reference or a revparse spec. - /// The optional message to log in the when adding the - /// A new . - public static Reference Add(this ReferenceCollection refsColl, string name, string canonicalRefNameOrObjectish, - string logMessage) - { - return refsColl.Add(name, canonicalRefNameOrObjectish, logMessage, false); - } - - /// - /// Creates a direct or symbolic reference with the specified name and target - /// - /// The being worked with. - /// The name of the reference to create. - /// The target which can be either the canonical name of a reference or a revparse spec. - /// The optional message to log in the when adding the - /// True to allow silent overwriting a potentially existing reference, false otherwise. - /// A new . - public static Reference Add(this ReferenceCollection refsColl, string name, string canonicalRefNameOrObjectish, string logMessage, bool allowOverwrite) - { - Ensure.ArgumentNotNullOrEmptyString(name, "name"); - Ensure.ArgumentNotNullOrEmptyString(canonicalRefNameOrObjectish, "canonicalRefNameOrObjectish"); - - Reference reference; - RefState refState = TryResolveReference(out reference, refsColl, canonicalRefNameOrObjectish); - - var gitObject = refsColl.repo.Lookup(canonicalRefNameOrObjectish, GitObjectType.Any, LookUpOptions.None); - - if (refState == RefState.Exists) - { - return refsColl.Add(name, reference, logMessage, allowOverwrite); - } - - if (refState == RefState.DoesNotExistButLooksValid && gitObject == null) - { - using (ReferenceSafeHandle handle = Proxy.git_reference_symbolic_create(refsColl.repo.Handle, name, canonicalRefNameOrObjectish, allowOverwrite, - logMessage)) - { - return Reference.BuildFromPtr(handle, refsColl.repo); - } - } - - Ensure.GitObjectIsNotNull(gitObject, canonicalRefNameOrObjectish); - - if (logMessage == null) - { - logMessage = string.Format(CultureInfo.InvariantCulture, "{0}: Created from {1}", - name.LooksLikeLocalBranch() ? "branch" : "reference", canonicalRefNameOrObjectish); - } - - refsColl.EnsureHasLog(name); - return refsColl.Add(name, gitObject.Id, logMessage, allowOverwrite); - } - - - /// - /// Creates a direct or symbolic reference with the specified name and target - /// - /// The being worked with. - /// The name of the reference to create. - /// The target which can be either the canonical name of a reference or a revparse spec. - /// A new . - public static Reference Add(this ReferenceCollection refsColl, string name, string canonicalRefNameOrObjectish) - { - return Add(refsColl, name, canonicalRefNameOrObjectish, null, false); - } - - /// - /// Creates a direct or symbolic reference with the specified name and target - /// - /// The being worked with. - /// The name of the reference to create. - /// The target which can be either the canonical name of a reference or a revparse spec. - /// True to allow silent overwriting a potentially existing reference, false otherwise. - /// A new . - public static Reference Add(this ReferenceCollection refsColl, string name, string canonicalRefNameOrObjectish, bool allowOverwrite) - { - return Add(refsColl, name, canonicalRefNameOrObjectish, null, allowOverwrite); - } - - /// - /// Updates the target of a direct reference. - /// - /// The being worked with. - /// The direct reference which target should be updated. - /// The revparse spec of the target. - /// The optional message to log in the - /// A new . - public static Reference UpdateTarget(this ReferenceCollection refsColl, Reference directRef, string objectish, string logMessage) - { - Ensure.ArgumentNotNull(directRef, "directRef"); - Ensure.ArgumentNotNull(objectish, "objectish"); - - GitObject target = refsColl.repo.Lookup(objectish); - - Ensure.GitObjectIsNotNull(target, objectish); - - return refsColl.UpdateTarget(directRef, target.Id, logMessage); - } - - /// - /// Updates the target of a direct reference - /// - /// The being worked with. - /// The direct reference which target should be updated. - /// The revparse spec of the target. - /// A new . - public static Reference UpdateTarget(this ReferenceCollection refsColl, Reference directRef, string objectish) - { - return UpdateTarget(refsColl, directRef, objectish, null); - } - - /// - /// Rename an existing reference with a new name - /// - /// The canonical name of the reference to rename. - /// The new canonical name. - /// The being worked with. - /// A new . - public static Reference Rename(this ReferenceCollection refsColl, string currentName, string newName) - { - return refsColl.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. - /// The being worked with. - /// A new . - public static Reference Rename(this ReferenceCollection refsColl, string currentName, string newName, - bool allowOverwrite) - { - return refsColl.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 - /// The being worked with. - /// A new . - public static Reference Rename(this ReferenceCollection refsColl, string currentName, string newName, - string logMessage) - { - return refsColl.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. - /// The being worked with. - /// A new . - public static Reference Rename(this ReferenceCollection refsColl, string currentName, string newName, - string logMessage, bool allowOverwrite) - { - Ensure.ArgumentNotNullOrEmptyString(currentName, "currentName"); - - Reference reference = refsColl[currentName]; - - if (reference == null) - { - throw new LibGit2SharpException( - string.Format(CultureInfo.InvariantCulture, - "Reference '{0}' doesn't exist. One cannot move a non existing reference.", currentName)); - } - - return refsColl.Rename(reference, newName, logMessage, allowOverwrite); - } - - /// - /// Updates the target of a reference - /// - /// The being worked with. - /// The canonical name of the reference. - /// The target which can be either the canonical name of a reference or a revparse spec. - /// The optional message to log in the of the reference. - /// A new . - public static Reference UpdateTarget(this ReferenceCollection refsColl, string name, string canonicalRefNameOrObjectish, string logMessage) - { - Ensure.ArgumentNotNullOrEmptyString(name, "name"); - Ensure.ArgumentNotNullOrEmptyString(canonicalRefNameOrObjectish, "canonicalRefNameOrObjectish"); - - if (name == "HEAD") - { - return refsColl.UpdateHeadTarget(canonicalRefNameOrObjectish, logMessage); - } - - Reference reference = refsColl[name]; - - var directReference = reference as DirectReference; - if (directReference != null) - { - return refsColl.UpdateTarget(directReference, canonicalRefNameOrObjectish, logMessage); - } - - var symbolicReference = reference as SymbolicReference; - if (symbolicReference != null) - { - Reference targetRef; - - RefState refState = TryResolveReference(out targetRef, refsColl, canonicalRefNameOrObjectish); - - if (refState == RefState.DoesNotLookValid) - { - throw new ArgumentException(String.Format(CultureInfo.InvariantCulture, "The reference specified by {0} is a Symbolic reference, you must provide a reference canonical name as the target.", name), "canonicalRefNameOrObjectish"); - } - - return refsColl.UpdateTarget(symbolicReference, targetRef, logMessage); - } - - throw new LibGit2SharpException(string.Format(CultureInfo.InvariantCulture, "Reference '{0}' has an unexpected type ('{1}').", name, reference.GetType())); - } - - /// - /// Updates the target of a reference - /// - /// The being worked with. - /// The canonical name of the reference. - /// The target which can be either the canonical name of a reference or a revparse spec. - /// A new . - public static Reference UpdateTarget(this ReferenceCollection refsColl, string name, string canonicalRefNameOrObjectish) - { - return UpdateTarget(refsColl, name, canonicalRefNameOrObjectish, null); - } - - /// - /// Delete a reference with the specified name - /// - /// The being worked with. - /// The canonical name of the reference to delete. - public static void Remove(this ReferenceCollection refsColl, string name) - { - Ensure.ArgumentNotNullOrEmptyString(name, "name"); - - Reference reference = refsColl[name]; - - if (reference == null) - { - return; - } - - refsColl.Remove(reference); - } - - /// - /// Find the s among - /// that can reach at least one in the specified . - /// - /// The being worked with. - /// The set of s to examine. - /// The set of s that are interesting. - /// A subset of that can reach at least one within . - public static IEnumerable ReachableFrom( - this ReferenceCollection refsColl, - IEnumerable refSubset, - IEnumerable targets) - { - Ensure.ArgumentNotNull(refSubset, "refSubset"); - Ensure.ArgumentNotNull(targets, "targets"); - - var refs = new List(refSubset); - if (refs.Count == 0) - { - return Enumerable.Empty(); - } - - List targetsSet = targets.Select(c => c.Id).Distinct().ToList(); - if (targetsSet.Count == 0) - { - return Enumerable.Empty(); - } - - var result = new List(); - - foreach (var reference in refs) - { - var peeledTargetCommit = reference - .ResolveToDirectReference() - .Target.DereferenceToCommit(false); - - if (peeledTargetCommit == null) - { - continue; - } - - var commitId = peeledTargetCommit.Id; - - foreach (var potentialAncestorId in targetsSet) - { - if (potentialAncestorId == commitId) - { - result.Add(reference); - break; - } - - if (Proxy.git_graph_descendant_of(refsColl.repo.Handle, commitId, potentialAncestorId)) - { - result.Add(reference); - break; - } - } - } - - return result; - } - - /// - /// Find the s - /// that can reach at least one in the specified . - /// - /// The being worked with. - /// The set of s that are interesting. - /// The list of that can reach at least one within . - public static IEnumerable ReachableFrom( - this ReferenceCollection refsColl, - IEnumerable targets) - { - return ReachableFrom(refsColl, refsColl, targets); - } - } -} diff --git a/LibGit2Sharp/ReferenceExtensions.cs b/LibGit2Sharp/ReferenceExtensions.cs index 8b33e1618..c29f6f076 100644 --- a/LibGit2Sharp/ReferenceExtensions.cs +++ b/LibGit2Sharp/ReferenceExtensions.cs @@ -5,7 +5,7 @@ namespace LibGit2Sharp /// /// Provides helpers to a . /// - public static class ReferenceExtensions + internal static class ReferenceExtensions { internal static bool LooksLikeLocalBranch(this string canonicalName) { @@ -31,45 +31,5 @@ private static bool IsPrefixedBy(this string input, string prefix) { return input.StartsWith(prefix, StringComparison.Ordinal); } - - /// - /// Determine if the current is a local branch. - /// - /// The to test. - /// true if the current is a local branch, false otherwise. - public static bool IsLocalBranch(this Reference reference) - { - return reference.CanonicalName.LooksLikeLocalBranch(); - } - - /// - /// Determine if the current is a remote tracking branch. - /// - /// The to test. - /// true if the current is a remote tracking branch, false otherwise. - public static bool IsRemoteTrackingBranch(this Reference reference) - { - return reference.CanonicalName.LooksLikeRemoteTrackingBranch(); - } - - /// - /// Determine if the current is a tag. - /// - /// The to test. - /// true if the current is a tag, false otherwise. - public static bool IsTag(this Reference reference) - { - return reference.CanonicalName.LooksLikeTag(); - } - - /// - /// Determine if the current is a note. - /// - /// The to test. - /// true if the current is a note, false otherwise. - public static bool IsNote(this Reference reference) - { - return reference.CanonicalName.LooksLikeNote(); - } } } diff --git a/LibGit2Sharp/ReferenceWrapper.cs b/LibGit2Sharp/ReferenceWrapper.cs index e6fa07797..3e4243a7e 100644 --- a/LibGit2Sharp/ReferenceWrapper.cs +++ b/LibGit2Sharp/ReferenceWrapper.cs @@ -16,10 +16,11 @@ public abstract class ReferenceWrapper : IEquatable protected readonly Repository repo; + private readonly Reference reference; private readonly Lazy objectBuilder; private static readonly LambdaEqualityHelper> equalityHelper = - new LambdaEqualityHelper>(x => x.CanonicalName, x => x.TargetObject); + new LambdaEqualityHelper>(x => x.CanonicalName, x => x.reference.TargetIdentifier); private readonly string canonicalName; @@ -40,6 +41,7 @@ protected internal ReferenceWrapper(Repository repo, Reference reference, Func(() => RetrieveTargetObject(reference)); } @@ -60,12 +62,14 @@ public virtual string FriendlyName } /// - /// Gets the name of this reference. + /// The underlying /// - [Obsolete("This property will be removed in the next release. Please use FriendlyName instead.")] - public virtual string Name + public virtual Reference Reference { - get { return FriendlyName; } + get + { + return reference; + } } /// @@ -165,8 +169,10 @@ private string DebuggerDisplay get { return string.Format(CultureInfo.InvariantCulture, - "{0} => \"{1}\"", CanonicalName, - (TargetObject != null) ? TargetObject.Id.ToString(7) : "?"); + "{0} => \"{1}\"", CanonicalName, + (TargetObject != null) + ? TargetObject.Id.ToString(7) + : "?"); } } diff --git a/LibGit2Sharp/ReflogCollection.cs b/LibGit2Sharp/ReflogCollection.cs index d2bbb8919..20b1a8b73 100644 --- a/LibGit2Sharp/ReflogCollection.cs +++ b/LibGit2Sharp/ReflogCollection.cs @@ -37,8 +37,7 @@ internal ReflogCollection(Repository repo, string canonicalName) if (!Reference.IsValidName(canonicalName)) { - throw new InvalidSpecificationException( - string.Format(CultureInfo.InvariantCulture, "The given reference name '{0}' is not valid", canonicalName)); + throw new InvalidSpecificationException("The given reference name '{0}' is not valid", canonicalName); } this.repo = repo; @@ -54,17 +53,17 @@ internal ReflogCollection(Repository repo, string canonicalName) /// /// /// An object that can be used to iterate through the collection. - public virtual IEnumerator GetEnumerator() + public virtual unsafe IEnumerator GetEnumerator() { var entries = new List(); - using (ReflogSafeHandle reflog = Proxy.git_reflog_read(repo.Handle, canonicalName)) + using (ReflogHandle reflog = Proxy.git_reflog_read(repo.Handle, canonicalName)) { var entriesCount = Proxy.git_reflog_entrycount(reflog); for (int i = 0; i < entriesCount; i++) { - ReflogEntrySafeHandle handle = Proxy.git_reflog_entry_byindex(reflog, i); + git_reflog_entry* handle = Proxy.git_reflog_entry_byindex(reflog, i); entries.Add(new ReflogEntry(handle)); } } @@ -87,8 +86,7 @@ private string DebuggerDisplay { get { - return string.Format(CultureInfo.InvariantCulture, - "Count = {0}", this.Count()); + return string.Format(CultureInfo.InvariantCulture, "Count = {0}", this.Count()); } } } diff --git a/LibGit2Sharp/ReflogEntry.cs b/LibGit2Sharp/ReflogEntry.cs index f783b11cf..d5f064c5a 100644 --- a/LibGit2Sharp/ReflogEntry.cs +++ b/LibGit2Sharp/ReflogEntry.cs @@ -25,7 +25,7 @@ protected ReflogEntry() /// Initializes a new instance of the class. /// /// a to the reflog entry - public ReflogEntry(SafeHandle entryHandle) + internal unsafe ReflogEntry(git_reflog_entry* entryHandle) { _from = Proxy.git_reflog_entry_id_old(entryHandle); _to = Proxy.git_reflog_entry_id_new(entryHandle); @@ -57,15 +57,6 @@ public virtual Signature Committer get { return _committer; } } - /// - /// of the committer of this reference update - /// - [Obsolete("This property will be removed in the next release. Please use Committer instead.")] - public virtual Signature Commiter - { - get { return Committer; } - } - /// /// the message assiocated to this reference update /// diff --git a/LibGit2Sharp/Remote.cs b/LibGit2Sharp/Remote.cs index efa716a85..3bf957866 100644 --- a/LibGit2Sharp/Remote.cs +++ b/LibGit2Sharp/Remote.cs @@ -12,15 +12,13 @@ namespace LibGit2Sharp /// A remote repository whose branches are tracked. /// [DebuggerDisplay("{DebuggerDisplay,nq}")] - public class Remote : IEquatable, IBelongToARepository + public class Remote : IBelongToARepository, IDisposable { - private static readonly LambdaEqualityHelper equalityHelper = - new LambdaEqualityHelper(x => x.Name, x => x.Url, x => x.PushUrl); - internal readonly Repository repository; private readonly RefSpecCollection refSpecs; - private string pushUrl; + + readonly RemoteHandle handle; /// /// Needed for mocking purposes. @@ -28,52 +26,81 @@ public class Remote : IEquatable, IBelongToARepository protected Remote() { } - private Remote(RemoteSafeHandle handle, Repository repository) + internal Remote(RemoteHandle handle, Repository repository) { this.repository = repository; - Name = Proxy.git_remote_name(handle); - Url = Proxy.git_remote_url(handle); - PushUrl = Proxy.git_remote_pushurl(handle); - TagFetchMode = Proxy.git_remote_autotag(handle); - refSpecs = new RefSpecCollection(handle); + this.handle = handle; + refSpecs = new RefSpecCollection(this, handle); + repository.RegisterForCleanup(this); + } + + /// + /// The finalizer for the class. + /// + ~Remote() + { + Dispose(false); + } + + #region IDisposable + + bool disposedValue = false; // To detect redundant calls + + /// + /// Release the unmanaged remote object + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); } - internal static Remote BuildFromPtr(RemoteSafeHandle handle, Repository repo) + void Dispose(bool disposing) { - var remote = new Remote(handle, repo); + if (!disposedValue) + { + if (handle != null) + { + handle.Dispose(); + } - return remote; + disposedValue = true; + } } + #endregion + /// /// Gets the alias of this remote repository. /// - public virtual string Name { get; private set; } + public virtual string Name + { + get { return Proxy.git_remote_name(handle); } + } /// /// Gets the url to use to communicate with this remote repository. /// - public virtual string Url { get; private set; } + public virtual string Url + { + get { return Proxy.git_remote_url(handle); } } /// /// Gets the distinct push url for this remote repository, if set. /// Defaults to the fetch url () if not set. /// - public virtual string PushUrl { - get - { - return pushUrl ?? Url; - } - private set - { - pushUrl = value; - } + public virtual string PushUrl + { + get { return Proxy.git_remote_pushurl(handle) ?? Url; } } /// /// Gets the Tag Fetch Mode of the remote - indicating how tags are fetched. /// - public virtual TagFetchMode TagFetchMode { get; private set; } + public virtual TagFetchMode TagFetchMode + { + get { return Proxy.git_remote_autotag(handle); } + } /// /// Gets the list of s defined for this @@ -103,12 +130,12 @@ public virtual IEnumerable PushRefSpecs /// /// The reference to transform. /// The transformed reference. - internal string FetchSpecTransformToSource(string reference) + internal unsafe string FetchSpecTransformToSource(string reference) { - using (RemoteSafeHandle remoteHandle = Proxy.git_remote_lookup(repository.Handle, Name, true)) + using (RemoteHandle remoteHandle = Proxy.git_remote_lookup(repository.Handle, Name, true)) { - GitRefSpecHandle fetchSpecPtr = Proxy.git_remote_get_refspec(remoteHandle, 0); - return Proxy.git_refspec_rtransform(fetchSpecPtr, reference); + git_refspec* fetchSpecPtr = Proxy.git_remote_get_refspec(remoteHandle, 0); + return Proxy.git_refspec_rtransform(new IntPtr(fetchSpecPtr), reference); } } @@ -147,63 +174,11 @@ public virtual bool AutomaticallyPruneOnFetch } } - /// - /// 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 Remote); - } - - /// - /// Determines whether the specified is equal to the current . - /// - /// The to compare with the current . - /// True if the specified is equal to the current ; otherwise, false. - public bool Equals(Remote other) - { - return equalityHelper.Equals(this, other); - } - - /// - /// 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 ==(Remote left, Remote 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 !=(Remote left, Remote right) - { - return !Equals(left, right); - } - private string DebuggerDisplay { get { - return string.Format(CultureInfo.InvariantCulture, - "{0} => {1}", Name, Url); + return string.Format(CultureInfo.InvariantCulture, "{0} => {1}", Name, Url); } } diff --git a/LibGit2Sharp/RemoteCallbacks.cs b/LibGit2Sharp/RemoteCallbacks.cs index 55be945d2..8d5d07232 100644 --- a/LibGit2Sharp/RemoteCallbacks.cs +++ b/LibGit2Sharp/RemoteCallbacks.cs @@ -27,7 +27,9 @@ internal RemoteCallbacks(PushOptions pushOptions) PushTransferProgress = pushOptions.OnPushTransferProgress; PackBuilderProgress = pushOptions.OnPackBuilderProgress; CredentialsProvider = pushOptions.CredentialsProvider; + CertificateCheck = pushOptions.CertificateCheck; PushStatusError = pushOptions.OnPushStatusError; + PrePushCallback = pushOptions.OnNegotiationCompletedBeforePush; } internal RemoteCallbacks(FetchOptionsBase fetchOptions) @@ -41,6 +43,7 @@ internal RemoteCallbacks(FetchOptionsBase fetchOptions) DownloadTransferProgress = fetchOptions.OnTransferProgress; UpdateTips = fetchOptions.OnUpdateTips; CredentialsProvider = fetchOptions.CredentialsProvider; + CertificateCheck = fetchOptions.CertificateCheck; } #region Delegates @@ -56,7 +59,7 @@ internal RemoteCallbacks(FetchOptionsBase fetchOptions) private readonly UpdateTipsHandler UpdateTips; /// - /// PushStatusError callback. It will be called when the libgit2 push_update_reference returns a non null status message, + /// 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; @@ -77,6 +80,11 @@ internal RemoteCallbacks(FetchOptionsBase fetchOptions) /// private readonly PackBuilderProgressHandler PackBuilderProgress; + /// + /// Called during remote push operation after negotiation, before upload + /// + private readonly PrePushHandler PrePushCallback; + #endregion /// @@ -84,9 +92,14 @@ internal RemoteCallbacks(FetchOptionsBase fetchOptions) /// private readonly CredentialsHandler CredentialsProvider; - internal GitRemoteCallbacks GenerateCallbacks() + /// + /// Callback to perform validation on the certificate + /// + private readonly CertificateCheckHandler CertificateCheck; + + internal unsafe GitRemoteCallbacks GenerateCallbacks() { - var callbacks = new GitRemoteCallbacks {version = 1}; + var callbacks = new GitRemoteCallbacks { version = 1 }; if (Progress != null) { @@ -108,6 +121,11 @@ internal GitRemoteCallbacks GenerateCallbacks() callbacks.acquire_credentials = GitCredentialHandler; } + if (CertificateCheck != null) + { + callbacks.certificate_check = GitCertificateCheck; + } + if (DownloadTransferProgress != null) { callbacks.download_progress = GitDownloadTransferProgressHandler; @@ -123,6 +141,11 @@ internal GitRemoteCallbacks GenerateCallbacks() callbacks.pack_progress = GitPackbuilderProgressHandler; } + if (PrePushCallback != null) + { + callbacks.push_negotiation = GitPushNegotiationHandler; + } + return callbacks; } @@ -185,7 +208,7 @@ private int GitUpdateTipsHandler(IntPtr str, ref GitOid oldId, ref GitOid newId, /// 0 on success; a negative value to abort the process. private int GitPushUpdateReference(IntPtr str, IntPtr status, IntPtr data) { - PushStatusErrorHandler onPushError = PushStatusError; + PushStatusErrorHandler onPushError = PushStatusError; if (onPushError != null) { @@ -242,7 +265,12 @@ private int GitPackbuilderProgressHandler(int stage, uint current, uint total, I return Proxy.ConvertResultToCancelFlag(shouldContinue); } - private int GitCredentialHandler(out IntPtr ptr, IntPtr cUrl, IntPtr usernameFromUrl, GitCredentialType credTypes, IntPtr payload) + private int GitCredentialHandler( + out IntPtr ptr, + IntPtr cUrl, + IntPtr usernameFromUrl, + GitCredentialType credTypes, + IntPtr payload) { string url = LaxUtf8Marshaler.FromNative(cUrl); string username = LaxUtf8Marshaler.FromNative(usernameFromUrl); @@ -256,10 +284,100 @@ private int GitCredentialHandler(out IntPtr ptr, IntPtr cUrl, IntPtr usernameFro { types |= SupportedCredentialTypes.Default; } + if (credTypes.HasFlag(GitCredentialType.SshKey)) + { + types |= SupportedCredentialTypes.Ssh; + } + if (credTypes.HasFlag(GitCredentialType.Username)) + { + types |= SupportedCredentialTypes.UsernameQuery; + } + + 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.giterr_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; - var cred = CredentialsProvider(url, username, types); + 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.giterr_set_str(GitErrorCategory.Callback, exception); + } + + return Proxy.ConvertResultToCancelFlag(result); + } + + private int GitPushNegotiationHandler(IntPtr updates, UIntPtr len, IntPtr payload) + { + if (updates == IntPtr.Zero) + { + return (int)GitErrorCode.Error; + } + + bool result = false; + try + { + + int length = len.ConvertToInt(); + PushUpdate[] pushUpdates = new PushUpdate[length]; + + unsafe + { + IntPtr* ptr = (IntPtr*)updates.ToPointer(); + + for (int i = 0; i < length; i++) + { + if (ptr[i] == IntPtr.Zero) + { + throw new NullReferenceException("Unexpected null git_push_update pointer was encountered"); + } + + PushUpdate pushUpdate = new PushUpdate((git_push_update*) ptr[i].ToPointer()); + pushUpdates[i] = pushUpdate; + } + + result = PrePushCallback(pushUpdates); + } + } + catch (Exception exception) + { + Log.Write(LogLevel.Error, exception.ToString()); + Proxy.giterr_set_str(GitErrorCategory.Callback, exception); + result = false; + } - return cred.GitCredentialHandler(out ptr); + return Proxy.ConvertResultToCancelFlag(result); } #endregion diff --git a/LibGit2Sharp/RemoteCollection.cs b/LibGit2Sharp/RemoteCollection.cs index a85cb6ad6..634f6e770 100644 --- a/LibGit2Sharp/RemoteCollection.cs +++ b/LibGit2Sharp/RemoteCollection.cs @@ -43,28 +43,27 @@ internal Remote RemoteForName(string name, bool shouldThrowIfNotFound = true) { Ensure.ArgumentNotNull(name, "name"); - using (RemoteSafeHandle handle = Proxy.git_remote_lookup(repository.Handle, name, shouldThrowIfNotFound)) - { - return handle == null ? null : Remote.BuildFromPtr(handle, this.repository); - } + RemoteHandle handle = Proxy.git_remote_lookup(repository.Handle, name, shouldThrowIfNotFound); + return handle == null ? null : new Remote(handle, this.repository); } /// /// Update properties of a remote. + /// + /// These updates will be performed as a bulk update at the end of the method. /// - /// The remote to update. + /// The name of the remote to update. /// Delegate to perform updates on the remote. - /// The updated remote. - public virtual Remote Update(Remote remote, params Action[] actions) + public virtual void Update(string remote, params Action[] actions) { var updater = new RemoteUpdater(repository, remote); - foreach (Action action in actions) - { - action(updater); - } - - return this[remote.Name]; + repository.Config.WithinTransaction(() => { + foreach (Action action in actions) + { + action(updater); + } + }); } /// @@ -102,10 +101,8 @@ public virtual Remote Add(string name, string url) Ensure.ArgumentNotNull(name, "name"); Ensure.ArgumentNotNull(url, "url"); - using (RemoteSafeHandle handle = Proxy.git_remote_create(repository.Handle, name, url)) - { - return Remote.BuildFromPtr(handle, this.repository); - } + RemoteHandle handle = Proxy.git_remote_create(repository.Handle, name, url); + return new Remote(handle, this.repository); } /// @@ -121,10 +118,8 @@ public virtual Remote Add(string name, string url, string fetchRefSpec) Ensure.ArgumentNotNull(url, "url"); Ensure.ArgumentNotNull(fetchRefSpec, "fetchRefSpec"); - using (RemoteSafeHandle handle = Proxy.git_remote_create_with_fetchspec(repository.Handle, name, url, fetchRefSpec)) - { - return Remote.BuildFromPtr(handle, this.repository); - } + RemoteHandle handle = Proxy.git_remote_create_with_fetchspec(repository.Handle, name, url, fetchRefSpec); + return new Remote(handle, this.repository); } /// @@ -170,8 +165,7 @@ private string DebuggerDisplay { get { - return string.Format(CultureInfo.InvariantCulture, - "Count = {0}", this.Count()); + return string.Format(CultureInfo.InvariantCulture, "Count = {0}", this.Count()); } } } diff --git a/LibGit2Sharp/RemoteUpdater.cs b/LibGit2Sharp/RemoteUpdater.cs index 07c823bcc..53fd33a4b 100644 --- a/LibGit2Sharp/RemoteUpdater.cs +++ b/LibGit2Sharp/RemoteUpdater.cs @@ -1,7 +1,6 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Linq; using LibGit2Sharp.Core; using LibGit2Sharp.Core.Handles; @@ -15,7 +14,7 @@ public class RemoteUpdater private readonly UpdatingCollection fetchRefSpecs; private readonly UpdatingCollection pushRefSpecs; private readonly Repository repo; - private readonly Remote remote; + private readonly string remoteName; /// /// Needed for mocking purposes. @@ -29,7 +28,19 @@ internal RemoteUpdater(Repository repo, Remote remote) Ensure.ArgumentNotNull(remote, "remote"); this.repo = repo; - this.remote = remote; + 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); @@ -37,7 +48,7 @@ internal RemoteUpdater(Repository repo, Remote remote) private IEnumerable GetFetchRefSpecs() { - using (RemoteSafeHandle remoteHandle = Proxy.git_remote_lookup(repo.Handle, remote.Name, true)) + using (RemoteHandle remoteHandle = Proxy.git_remote_lookup(repo.Handle, remoteName, true)) { return Proxy.git_remote_get_fetch_refspecs(remoteHandle); } @@ -45,17 +56,17 @@ private IEnumerable GetFetchRefSpecs() private void SetFetchRefSpecs(IEnumerable value) { - repo.Config.UnsetMultivar(string.Format("remote.{0}.fetch", remote.Name), ConfigurationLevel.Local); + repo.Config.UnsetAll(string.Format("remote.{0}.fetch", remoteName), ConfigurationLevel.Local); foreach (var url in value) { - Proxy.git_remote_add_fetch(repo.Handle, remote.Name, url); + Proxy.git_remote_add_fetch(repo.Handle, remoteName, url); } } private IEnumerable GetPushRefSpecs() { - using (RemoteSafeHandle remoteHandle = Proxy.git_remote_lookup(repo.Handle, remote.Name, true)) + using (RemoteHandle remoteHandle = Proxy.git_remote_lookup(repo.Handle, remoteName, true)) { return Proxy.git_remote_get_push_refspecs(remoteHandle); } @@ -63,11 +74,11 @@ private IEnumerable GetPushRefSpecs() private void SetPushRefSpecs(IEnumerable value) { - repo.Config.UnsetMultivar(string.Format("remote.{0}.push", remote.Name), ConfigurationLevel.Local); + repo.Config.UnsetAll(string.Format("remote.{0}.push", remoteName), ConfigurationLevel.Local); foreach (var url in value) { - Proxy.git_remote_add_push(repo.Handle, remote.Name, url); + Proxy.git_remote_add_push(repo.Handle, remoteName, url); } } @@ -76,10 +87,7 @@ private void SetPushRefSpecs(IEnumerable value) /// public virtual TagFetchMode TagFetchMode { - set - { - Proxy.git_remote_set_autotag(repo.Handle, remote.Name, value); - } + set { Proxy.git_remote_set_autotag(repo.Handle, remoteName, value); } } /// @@ -87,10 +95,7 @@ public virtual TagFetchMode TagFetchMode /// public virtual string Url { - set - { - Proxy.git_remote_set_url(repo.Handle, remote.Name, value); - } + set { Proxy.git_remote_set_url(repo.Handle, remoteName, value); } } /// @@ -98,10 +103,7 @@ public virtual string Url /// public virtual string PushUrl { - set - { - Proxy.git_remote_set_pushurl(repo.Handle, remote.Name, value); - } + set { Proxy.git_remote_set_pushurl(repo.Handle, remoteName, value); } } /// diff --git a/LibGit2Sharp/RemoveFromIndexException.cs b/LibGit2Sharp/RemoveFromIndexException.cs index 57bfbafae..6d9718c18 100644 --- a/LibGit2Sharp/RemoveFromIndexException.cs +++ b/LibGit2Sharp/RemoveFromIndexException.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Runtime.Serialization; namespace LibGit2Sharp @@ -13,8 +14,7 @@ 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. @@ -22,6 +22,15 @@ public RemoveFromIndexException() /// A message that describes the error. public RemoveFromIndexException(string message) : base(message) + { } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// A composite format string for use in . + /// An object array that contains zero or more objects to format. + public RemoveFromIndexException(string format, params object[] args) + : base(format, args) { } @@ -32,8 +41,7 @@ public RemoveFromIndexException(string message) /// The exception that is the cause of the current exception. If the parameter is not a null reference, the current exception is raised in a catch block that handles the inner exception. public RemoveFromIndexException(string message, Exception innerException) : base(message, innerException) - { - } + { } /// /// Initializes a new instance of the class with a serialized data. @@ -42,7 +50,6 @@ public RemoveFromIndexException(string message, Exception innerException) /// The that contains contextual information about the source or destination. protected RemoveFromIndexException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } + { } } } diff --git a/LibGit2Sharp/RenameDetails.cs b/LibGit2Sharp/RenameDetails.cs index 199b7269f..b866aac60 100644 --- a/LibGit2Sharp/RenameDetails.cs +++ b/LibGit2Sharp/RenameDetails.cs @@ -110,9 +110,11 @@ private string DebuggerDisplay { get { - return string.Format( - CultureInfo.InvariantCulture, - "{0} -> {1} [{2}%]", OldFilePath, NewFilePath, Similarity); + return string.Format(CultureInfo.InvariantCulture, + "{0} -> {1} [{2}%]", + OldFilePath, + NewFilePath, + Similarity); } } } diff --git a/LibGit2Sharp/Repository.cs b/LibGit2Sharp/Repository.cs index 79c9077bc..721133cc6 100644 --- a/LibGit2Sharp/Repository.cs +++ b/LibGit2Sharp/Repository.cs @@ -21,7 +21,7 @@ public sealed class Repository : IRepository private readonly BranchCollection branches; private readonly CommitLog commits; private readonly Lazy config; - private readonly RepositorySafeHandle handle; + private readonly RepositoryHandle handle; private readonly Lazy index; private readonly ReferenceCollection refs; private readonly TagCollection tags; @@ -31,11 +31,33 @@ public sealed class Repository : IRepository private readonly NoteCollection notes; private readonly Lazy odb; private readonly Lazy network; + private readonly Lazy rebaseOperation; private readonly Stack toCleanup = new Stack(); private readonly Ignore ignore; private readonly SubmoduleCollection submodules; + private readonly WorktreeCollection worktrees; private readonly Lazy pathCase; + [Flags] + private enum RepositoryRequiredParameter + { + None = 0, + Path = 1, + Options = 2, + } + + /// + /// Initializes a new instance of the class + /// that does not point to an on-disk Git repository. This is + /// suitable only for custom, in-memory Git repositories that are + /// configured with custom object database, reference database and/or + /// configuration backends. + /// + public Repository() + : this(null, null, RepositoryRequiredParameter.None) + { + } + /// /// Initializes a new instance of the class. /// For a standard repository, should either point to the ".git" folder or to the working directory. For a bare repository, should directly point to the repository folder. @@ -44,31 +66,109 @@ public sealed class Repository : IRepository /// 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) + public Repository(string path) + : this(path, null, RepositoryRequiredParameter.Path) { } /// - /// Initializes a new instance of the class, providing optional 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, + /// 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. + /// The path to the git repository to open, can be either the + /// path to the git directory (for non-bare repositories this + /// would be the ".git" folder inside the working directory) + /// or the path to the working directory. /// /// /// Overrides to the way a repository is opened. /// - public Repository(string path, RepositoryOptions options) + 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; @@ -82,8 +182,7 @@ public Repository(string path, RepositoryOptions options) 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."); } if (!isWorkDirNull) @@ -101,10 +200,6 @@ public Repository(string path, RepositoryOptions options) 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); @@ -122,18 +217,20 @@ public Repository(string path, RepositoryOptions options) 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); EagerlyLoadComponentsWithSpecifiedPaths(options); } @@ -154,7 +251,12 @@ public Repository(string path, RepositoryOptions options) /// True if a repository can be resolved through this path; false otherwise static public bool IsValid(string path) { - Ensure.ArgumentNotNullOrEmptyString(path, "path"); + Ensure.ArgumentNotNull(path, "path"); + + if (string.IsNullOrWhiteSpace(path)) + { + return false; + } try { @@ -175,19 +277,6 @@ private void EagerlyLoadComponentsWithSpecifiedPaths(RepositoryOptions options) return; } - if (options.GlobalConfigurationLocation != null || - options.XdgConfigurationLocation != null || - options.SystemConfigurationLocation != null) - { - // Dirty hack to force the eager load of the configuration - // without Resharper pestering about useless code - - if (!Config.HasConfig(ConfigurationLevel.Local)) - { - throw new InvalidOperationException("Unexpected state."); - } - } - if (!string.IsNullOrEmpty(options.IndexPath)) { // Another dirty hack to avoid warnings @@ -198,7 +287,7 @@ private void EagerlyLoadComponentsWithSpecifiedPaths(RepositoryOptions options) } } - internal RepositorySafeHandle Handle + internal RepositoryHandle Handle { get { return handle; } } @@ -246,7 +335,7 @@ public Index Index throw new BareRepositoryException("Index is not available in a bare repository."); } - return index.Value; + return index != null ? index.Value : null; } } @@ -255,20 +344,25 @@ public Index Index /// public Ignore Ignore { - get - { - return ignore; - } + get { return ignore; } } /// /// Provides access to network functionality for a repository. /// public Network Network + { + get { return network.Value; } + } + + /// + /// Provides access to rebase functionality for a repository. + /// + public Rebase Rebase { get { - return network.Value; + return rebaseOperation.Value; } } @@ -277,10 +371,7 @@ public Network Network /// public ObjectDatabase ObjectDatabase { - get - { - return odb.Value; - } + get { return odb.Value; } } /// @@ -356,6 +447,14 @@ public SubmoduleCollection Submodules get { return submodules; } } + /// + /// Worktrees in the repository. + /// + public WorktreeCollection Worktrees + { + get { return worktrees; } + } + #region IDisposable Members /// @@ -396,7 +495,7 @@ public static string Init(string path, bool isBare) { Ensure.ArgumentNotNullOrEmptyString(path, "path"); - using (RepositorySafeHandle repo = Proxy.git_repository_init_ext(null, path, isBare)) + using (RepositoryHandle repo = Proxy.git_repository_init_ext(null, path, isBare)) { FilePath repoPath = Proxy.git_repository_path(repo); return repoPath.Native; @@ -421,7 +520,7 @@ public static string Init(string workingDirectoryPath, string gitDirectoryPath) // TODO: Shouldn't we ensure that the working folder isn't under the gitDir? - using (RepositorySafeHandle repo = Proxy.git_repository_init_ext(wd, gitDirectoryPath, false)) + using (RepositoryHandle repo = Proxy.git_repository_init_ext(wd, gitDirectoryPath, false)) { FilePath repoPath = Proxy.git_repository_path(repo); return repoPath.Native; @@ -470,13 +569,13 @@ public GitObject Lookup(string objectish, ObjectType type) return Lookup(objectish, type.ToGitObjectType(), LookUpOptions.None); } - internal GitObject LookupInternal(ObjectId id, GitObjectType type, FilePath knownPath) + internal GitObject LookupInternal(ObjectId id, GitObjectType type, string knownPath) { Ensure.ArgumentNotNull(id, "id"); - using (GitObjectSafeHandle obj = Proxy.git_object_lookup(handle, id, type)) + using (ObjectHandle obj = Proxy.git_object_lookup(handle, id, type)) { - if (obj == null) + if (obj == null || obj.IsNull) { return null; } @@ -506,7 +605,7 @@ internal GitObject Lookup(string objectish, GitObjectType type, LookUpOptions lo Ensure.ArgumentNotNullOrEmptyString(objectish, "objectish"); GitObject obj; - using (GitObjectSafeHandle sh = Proxy.git_revparse_single(handle, objectish)) + using (ObjectHandle sh = Proxy.git_revparse_single(handle, objectish)) { if (sh == null) { @@ -530,8 +629,7 @@ internal GitObject Lookup(string objectish, GitObjectType type, LookUpOptions lo if (lookUpOptions.HasFlag(LookUpOptions.DereferenceResultToCommit)) { - return obj.DereferenceToCommit( - lookUpOptions.HasFlag(LookUpOptions.ThrowWhenCanNotBeDereferencedToACommit)); + return obj.Peel(lookUpOptions.HasFlag(LookUpOptions.ThrowWhenCanNotBeDereferencedToACommit)); } return obj; @@ -539,10 +637,58 @@ internal GitObject Lookup(string objectish, GitObjectType type, LookUpOptions lo internal Commit LookupCommit(string committish) { - return (Commit)Lookup(committish, GitObjectType.Any, - LookUpOptions.ThrowWhenNoGitObjectHasBeenFound | - LookUpOptions.DereferenceResultToCommit | - LookUpOptions.ThrowWhenCanNotBeDereferencedToACommit); + return (Commit)Lookup(committish, + GitObjectType.Any, + LookUpOptions.ThrowWhenNoGitObjectHasBeenFound | + LookUpOptions.DereferenceResultToCommit | + LookUpOptions.ThrowWhenCanNotBeDereferencedToACommit); + } + + /// + /// Lists the Remote Repository References. + /// + /// + /// Does not require a local Repository. The retrieved + /// + /// throws in this case. + /// + /// The url to list from. + /// The references in the remote repository. + public static IEnumerable ListRemoteReferences(string url) + { + return ListRemoteReferences(url, null); + } + + /// + /// Lists the Remote Repository References. + /// + /// + /// Does not require a local Repository. The retrieved + /// + /// throws in this case. + /// + /// The url to list from. + /// The used to connect to remote repository. + /// The references in the remote repository. + public static IEnumerable ListRemoteReferences(string url, CredentialsHandler credentialsProvider) + { + Ensure.ArgumentNotNull(url, "url"); + + using (RepositoryHandle repositoryHandle = Proxy.git_repository_new()) + using (RemoteHandle remoteHandle = Proxy.git_remote_create_anonymous(repositoryHandle, url)) + { + var gitCallbacks = new GitRemoteCallbacks { version = 1 }; + var proxyOptions = new GitProxyOptions { Version = 1 }; + + if (credentialsProvider != null) + { + var callbacks = new RemoteCallbacks(credentialsProvider); + gitCallbacks = callbacks.GenerateCallbacks(); + } + + Proxy.git_remote_connect(remoteHandle, GitDirection.Fetch, ref gitCallbacks, ref proxyOptions); + return Proxy.git_remote_ls(null, remoteHandle); + } } /// @@ -550,7 +696,7 @@ internal Commit LookupCommit(string committish) /// The lookup start from and walk upward parent directories if nothing has been found. /// /// The base path where the lookup starts. - /// The path to the git repository. + /// The path to the git repository, or null if no repository was found. public static string Discover(string startingPath) { FilePath discoveredPath = Proxy.git_repository_discover(startingPath); @@ -611,24 +757,31 @@ public static string Clone(string sourceUrl, string workdirPath, bool continueOperation = OnRepositoryOperationStarting(options.RepositoryOperationStarting, context); - if(!continueOperation) + if (!continueOperation) { throw new UserCancelledException("Clone cancelled by the user."); } - using (GitCheckoutOptsWrapper checkoutOptionsWrapper = new GitCheckoutOptsWrapper(options)) + using (var checkoutOptionsWrapper = new GitCheckoutOptsWrapper(options)) + using (var fetchOptionsWrapper = new GitFetchOptionsWrapper()) { var gitCheckoutOptions = checkoutOptionsWrapper.Options; - var remoteCallbacks = new RemoteCallbacks(options); - var gitRemoteCallbacks = remoteCallbacks.GenerateCallbacks(); + var gitFetchOptions = fetchOptionsWrapper.Options; + gitFetchOptions.ProxyOptions = new GitProxyOptions { Version = 1 }; + gitFetchOptions.RemoteCallbacks = new RemoteCallbacks(options).GenerateCallbacks(); + if (options.FetchOptions != null && options.FetchOptions.CustomHeaders != null) + { + gitFetchOptions.CustomHeaders = + GitStrArrayManaged.BuildFrom(options.FetchOptions.CustomHeaders); + } var cloneOpts = new GitCloneOptions { Version = 1, Bare = options.IsBare ? 1 : 0, CheckoutOpts = gitCheckoutOptions, - FetchOpts = new GitFetchOptions { RemoteCallbacks = gitRemoteCallbacks }, + FetchOpts = gitFetchOptions, }; string clonedRepoPath; @@ -637,7 +790,7 @@ public static string Clone(string sourceUrl, string workdirPath, { cloneOpts.CheckoutBranch = StrictUtf8Marshaler.FromManaged(options.BranchName); - using (RepositorySafeHandle repo = Proxy.git_clone(sourceUrl, workdirPath, ref cloneOpts)) + using (RepositoryHandle repo = Proxy.git_clone(sourceUrl, workdirPath, ref cloneOpts)) { clonedRepoPath = Proxy.git_repository_path(repo).Native; } @@ -658,10 +811,9 @@ public static string Clone(string sourceUrl, string workdirPath, } catch (Exception ex) { - throw new RecurseSubmodulesException( - "The top level repository was cloned, but there was an error cloning its submodules.", - ex, - clonedRepoPath); + throw new RecurseSubmodulesException("The top level repository was cloned, but there was an error cloning its submodules.", + ex, + clonedRepoPath); } return clonedRepoPath; @@ -744,8 +896,9 @@ private static void RecursivelyCloneSubmodules(CloneOptions options, string repo /// 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) + private static bool OnRepositoryOperationStarting( + RepositoryOperationStarting repositoryChangedCallback, + RepositoryOperationContext context) { bool continueOperation = true; if (repositoryChangedCallback != null) @@ -756,8 +909,9 @@ private static bool OnRepositoryOperationStarting(RepositoryOperationStarting re return continueOperation; } - private static void OnRepositoryOperationCompleted(RepositoryOperationCompleted repositoryChangedCallback, - RepositoryOperationContext context) + private static void OnRepositoryOperationCompleted( + RepositoryOperationCompleted repositoryChangedCallback, + RepositoryOperationContext context) { if (repositoryChangedCallback != null) { @@ -777,125 +931,14 @@ public BlameHunkCollection Blame(string path, BlameOptions options) } /// - /// 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. - /// - /// - /// A revparse spec for the commit or branch to checkout. - /// controlling checkout behavior. - /// The that was checked out. - public Branch Checkout(string committishOrBranchSpec, CheckoutOptions options) - { - Ensure.ArgumentNotNullOrEmptyString(committishOrBranchSpec, "committishOrBranchSpec"); - Ensure.ArgumentNotNull(options, "options"); - - var handles = Proxy.git_revparse_ext(Handle, committishOrBranchSpec); - if (handles == null) - { - Ensure.GitObjectIsNotNull(null, committishOrBranchSpec); - } - - var objH = handles.Item1; - var refH = handles.Item2; - GitObject obj; - try - { - if (!refH.IsInvalid) - { - var reference = Reference.BuildFromPtr(refH, this); - if (reference.IsLocalBranch()) - { - Branch branch = Branches[reference.CanonicalName]; - return Checkout(branch, options); - } - } - - obj = GitObject.BuildFrom(this, Proxy.git_object_id(objH), Proxy.git_object_type(objH), - PathFromRevparseSpec(committishOrBranchSpec)); - } - finally - { - objH.Dispose(); - refH.Dispose(); - } - - Commit commit = obj.DereferenceToCommit(true); - Checkout(commit.Tree, options, committishOrBranchSpec); - - return Head; - } - - /// - /// Checkout the tip commit of the specified object. If this commit is the - /// current tip of the branch, will checkout the named branch. Otherwise, will checkout the tip commit - /// as a detached HEAD. - /// - /// The to check out. - /// controlling checkout behavior. - /// The that was checked out. - public Branch Checkout(Branch branch, CheckoutOptions options) - { - Ensure.ArgumentNotNull(branch, "branch"); - Ensure.ArgumentNotNull(options, "options"); - - // Make sure this is not an unborn branch. - if (branch.Tip == null) - { - throw new UnbornBranchException( - string.Format(CultureInfo.InvariantCulture, - "The tip of branch '{0}' is null. There's nothing to checkout.", branch.FriendlyName)); - } - - if (!branch.IsRemote && !(branch is DetachedHead) && - string.Equals(Refs[branch.CanonicalName].TargetIdentifier, branch.Tip.Id.Sha, - StringComparison.OrdinalIgnoreCase)) - { - Checkout(branch.Tip.Tree, options, branch.CanonicalName); - } - else - { - Checkout(branch.Tip.Tree, options, branch.Tip.Id.Sha); - } - - return Head; - } - - /// - /// Checkout the specified . - /// - /// Will detach the HEAD and make it point to this commit sha. - /// - /// - /// The to check out. - /// controlling checkout behavior. - /// The that was checked out. - public Branch Checkout(Commit commit, CheckoutOptions options) - { - Ensure.ArgumentNotNull(commit, "commit"); - Ensure.ArgumentNotNull(options, "options"); - - Checkout(commit.Tree, options, commit.Id.Sha); - - return 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. + /// Checkout the specified tree. /// /// The to checkout. - /// controlling checkout behavior. - /// The spec which will be written as target in the reflog. - private void Checkout( - Tree tree, - CheckoutOptions checkoutOptions, - string refLogHeadSpec) + /// The paths to checkout. + /// Collection of parameters controlling checkout behavior. + public void Checkout(Tree tree, IEnumerable paths, CheckoutOptions options) { - CheckoutTree(tree, null, checkoutOptions); - - Refs.MoveHeadTarget(refLogHeadSpec); + CheckoutTree(tree, paths != null ? paths.ToList() : null, options); } /// @@ -904,13 +947,10 @@ private void Checkout( /// The to checkout. /// The paths to checkout. /// Collection of parameters controlling checkout behavior. - private void CheckoutTree( - Tree tree, - IList paths, - IConvertableToGitCheckoutOpts opts) + private void CheckoutTree(Tree tree, IList paths, IConvertableToGitCheckoutOpts opts) { - using(GitCheckoutOptsWrapper checkoutOptionsWrapper = new GitCheckoutOptsWrapper(opts, ToFilePaths(paths))) + using (GitCheckoutOptsWrapper checkoutOptionsWrapper = new GitCheckoutOptsWrapper(opts, ToFilePaths(paths))) { var options = checkoutOptionsWrapper.Options; Proxy.git_checkout_tree(Handle, tree.Id, ref options); @@ -929,15 +969,16 @@ public void Reset(ResetMode resetMode, Commit commit) } /// - /// Sets the current to the specified commit and optionally resets the and + /// Sets to the specified commit and optionally resets the and /// the content of the working tree to match. /// /// Flavor of reset operation to perform. /// The target commit object. /// Collection of parameters controlling checkout behavior. - private void Reset(ResetMode resetMode, Commit commit, IConvertableToGitCheckoutOpts opts) + public void Reset(ResetMode resetMode, Commit commit, CheckoutOptions opts) { Ensure.ArgumentNotNull(commit, "commit"); + Ensure.ArgumentNotNull(opts, "opts"); using (GitCheckoutOptsWrapper checkoutOptionsWrapper = new GitCheckoutOptsWrapper(opts)) { @@ -960,29 +1001,17 @@ public void CheckoutPaths(string committishOrBranchSpec, IEnumerable pat Ensure.ArgumentNotNullOrEmptyString(committishOrBranchSpec, "committishOrBranchSpec"); Ensure.ArgumentNotNull(paths, "paths"); + var listOfPaths = paths.ToList(); + // If there are no paths, then there is nothing to do. - if (!paths.Any()) + if (listOfPaths.Count == 0) { return; } Commit commit = LookupCommit(committishOrBranchSpec); - CheckoutTree(commit.Tree, paths.ToList(), checkoutOptions ?? new CheckoutOptions()); - } - /// - /// Replaces entries in the with entries from the specified commit. - /// - /// The target commit object. - /// The list of paths (either files or directories) that should be considered. - /// - /// If set, the passed will be treated as explicit paths. - /// Use these options to determine how unmatched explicit paths should be handled. - /// - [Obsolete("This method will be removed in the next release. Please use Index.Replace() instead.")] - public void Reset(Commit commit, IEnumerable paths, ExplicitPathsOptions explicitPathsOptions) - { - Index.Replace(commit, paths, explicitPathsOptions); + CheckoutTree(commit.Tree, listOfPaths, checkoutOptions ?? new CheckoutOptions()); } /// @@ -1021,11 +1050,9 @@ public Commit Commit(string message, Signature author, Signature committer, Comm if (treesame && !amendMergeCommit) { - throw new EmptyCommitException( - options.AmendPreviousCommit ? - String.Format(CultureInfo.InvariantCulture, - "Amending this commit would produce a commit that is identical to its parent (id = {0})", parents[0].Id) : - "No changes; nothing to commit."); + throw (options.AmendPreviousCommit ? + new EmptyCommitException("Amending this commit would produce a commit that is identical to its parent (id = {0})", parents[0].Id) : + new EmptyCommitException("No changes; nothing to commit.")); } } @@ -1070,7 +1097,7 @@ private void UpdateHeadAndTerminalReference(Commit commit, string reflogMessage) return; } - var symRef = (SymbolicReference) reference; + var symRef = (SymbolicReference)reference; reference = symRef.Target; @@ -1107,7 +1134,7 @@ private IEnumerable RetrieveParentsOfTheCommitBeingCreated(bool amendPre /// /// Clean the working tree by removing files that are not under version control. /// - public void RemoveUntrackedFiles() + public unsafe void RemoveUntrackedFiles() { var options = new GitCheckoutOpts { @@ -1116,7 +1143,7 @@ public void RemoveUntrackedFiles() | CheckoutStrategy.GIT_CHECKOUT_ALLOW_CONFLICTS, }; - Proxy.git_checkout_index(Handle, new NullGitObjectSafeHandle(), ref options); + Proxy.git_checkout_index(Handle, new ObjectHandle(null, false), ref options); } private void CleanupDisposableDependencies() @@ -1147,7 +1174,7 @@ public MergeResult Merge(Commit commit, Signature merger, MergeOptions options) options = options ?? new MergeOptions(); - using (GitAnnotatedCommitHandle annotatedCommitHandle = Proxy.git_annotated_commit_lookup(Handle, commit.Id.Oid)) + using (AnnotatedCommitHandle annotatedCommitHandle = Proxy.git_annotated_commit_lookup(Handle, commit.Id.Oid)) { return Merge(new[] { annotatedCommitHandle }, merger, options); } @@ -1167,8 +1194,8 @@ public MergeResult Merge(Branch branch, Signature merger, MergeOptions options) options = options ?? new MergeOptions(); - using (ReferenceSafeHandle referencePtr = Refs.RetrieveReferencePtr(branch.CanonicalName)) - using (GitAnnotatedCommitHandle annotatedCommitHandle = Proxy.git_annotated_commit_from_ref(Handle, referencePtr)) + using (ReferenceHandle referencePtr = Refs.RetrieveReferencePtr(branch.CanonicalName)) + using (AnnotatedCommitHandle annotatedCommitHandle = Proxy.git_annotated_commit_from_ref(Handle, referencePtr)) { return Merge(new[] { annotatedCommitHandle }, merger, options); } @@ -1214,11 +1241,11 @@ public MergeResult MergeFetchedRefs(Signature merger, MergeOptions options) if (fetchHeads.Length == 0) { var expectedRef = this.Head.UpstreamBranchCanonicalName; - throw new MergeFetchHeadNotFoundException(string.Format(CultureInfo.InvariantCulture, - "The current branch is configured to merge with the reference '{0}' from the remote, but this reference was not fetched.", expectedRef)); + throw new MergeFetchHeadNotFoundException("The current branch is configured to merge with the reference '{0}' from the remote, but this reference was not fetched.", + expectedRef); } - GitAnnotatedCommitHandle[] annotatedCommitHandles = fetchHeads.Select(fetchHead => + AnnotatedCommitHandle[] annotatedCommitHandles = fetchHeads.Select(fetchHead => Proxy.git_annotated_commit_from_fetchhead(Handle, fetchHead.RemoteCanonicalName, fetchHead.Url, fetchHead.Target.Id.Oid)).ToArray(); try @@ -1229,7 +1256,7 @@ public MergeResult MergeFetchedRefs(Signature merger, MergeOptions options) finally { // Cleanup. - foreach (GitAnnotatedCommitHandle annotatedCommitHandle in annotatedCommitHandles) + foreach (AnnotatedCommitHandle annotatedCommitHandle in annotatedCommitHandles) { annotatedCommitHandle.Dispose(); } @@ -1271,8 +1298,8 @@ public RevertResult Revert(Commit commit, Signature reverter, RevertOptions opti { Version = 1, MergeFileFavorFlags = options.MergeFileFavor, - MergeTreeFlags = options.FindRenames ? GitMergeTreeFlags.GIT_MERGE_TREE_FIND_RENAMES : - GitMergeTreeFlags.GIT_MERGE_TREE_NORMAL, + MergeTreeFlags = options.FindRenames ? GitMergeFlag.GIT_MERGE_FIND_RENAMES : + GitMergeFlag.GIT_MERGE_NORMAL, RenameThreshold = (uint)options.RenameThreshold, TargetLimit = (uint)options.TargetLimit, }; @@ -1355,8 +1382,8 @@ public CherryPickResult CherryPick(Commit commit, Signature committer, CherryPic { Version = 1, MergeFileFavorFlags = options.MergeFileFavor, - MergeTreeFlags = options.FindRenames ? GitMergeTreeFlags.GIT_MERGE_TREE_FIND_RENAMES : - GitMergeTreeFlags.GIT_MERGE_TREE_NORMAL, + MergeTreeFlags = options.FindRenames ? GitMergeFlag.GIT_MERGE_FIND_RENAMES : + GitMergeFlag.GIT_MERGE_NORMAL, RenameThreshold = (uint)options.RenameThreshold, TargetLimit = (uint)options.TargetLimit, }; @@ -1412,7 +1439,7 @@ private FastForwardStrategy FastForwardStrategyFromMergePreference(GitMergePrefe /// 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(GitAnnotatedCommitHandle[] annotatedCommits, Signature merger, MergeOptions options) + private MergeResult Merge(AnnotatedCommitHandle[] annotatedCommits, Signature merger, MergeOptions options) { GitMergeAnalysis mergeAnalysis; GitMergePreference mergePreference; @@ -1429,7 +1456,7 @@ private MergeResult Merge(GitAnnotatedCommitHandle[] annotatedCommits, Signature FastForwardStrategy fastForwardStrategy = (options.FastForwardStrategy != FastForwardStrategy.Default) ? options.FastForwardStrategy : FastForwardStrategyFromMergePreference(mergePreference); - switch(fastForwardStrategy) + switch (fastForwardStrategy) { case FastForwardStrategy.Default: if (mergeAnalysis.HasFlag(GitMergeAnalysis.GIT_MERGE_ANALYSIS_FASTFORWARD)) @@ -1492,25 +1519,47 @@ private MergeResult Merge(GitAnnotatedCommitHandle[] annotatedCommits, Signature /// 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(GitAnnotatedCommitHandle[] annotatedCommits, Signature merger, MergeOptions options) + private MergeResult NormalMerge(AnnotatedCommitHandle[] annotatedCommits, Signature merger, MergeOptions options) { MergeResult mergeResult; + GitMergeFlag treeFlags = options.FindRenames ? GitMergeFlag.GIT_MERGE_FIND_RENAMES + : GitMergeFlag.GIT_MERGE_NORMAL; + + if (options.FailOnConflict) + { + treeFlags |= GitMergeFlag.GIT_MERGE_FAIL_ON_CONFLICT; + } + + if (options.SkipReuc) + { + treeFlags |= GitMergeFlag.GIT_MERGE_SKIP_REUC; + } + + var fileFlags = options.IgnoreWhitespaceChange + ? GitMergeFileFlag.GIT_MERGE_FILE_IGNORE_WHITESPACE_CHANGE + : GitMergeFileFlag.GIT_MERGE_FILE_DEFAULT; var mergeOptions = new GitMergeOpts - { - Version = 1, - MergeFileFavorFlags = options.MergeFileFavor, - MergeTreeFlags = options.FindRenames ? GitMergeTreeFlags.GIT_MERGE_TREE_FIND_RENAMES : - GitMergeTreeFlags.GIT_MERGE_TREE_NORMAL, - RenameThreshold = (uint) options.RenameThreshold, - TargetLimit = (uint) options.TargetLimit, - }; + { + Version = 1, + MergeFileFavorFlags = options.MergeFileFavor, + MergeTreeFlags = treeFlags, + RenameThreshold = (uint)options.RenameThreshold, + TargetLimit = (uint)options.TargetLimit, + FileFlags = fileFlags + }; + bool earlyStop; using (GitCheckoutOptsWrapper checkoutOptionsWrapper = new GitCheckoutOptsWrapper(options)) { var checkoutOpts = checkoutOptionsWrapper.Options; - Proxy.git_merge(Handle, annotatedCommits, mergeOptions, checkoutOpts); + Proxy.git_merge(Handle, annotatedCommits, mergeOptions, checkoutOpts, out earlyStop); + } + + if (earlyStop) + { + return new MergeResult(MergeStatus.Conflicts); } if (Index.IsFullyMerged) @@ -1538,10 +1587,10 @@ private MergeResult NormalMerge(GitAnnotatedCommitHandle[] annotatedCommits, Sig /// The merge head handle to fast-forward merge. /// Options controlling merge behavior. /// The of the merge. - private MergeResult FastForwardMerge(GitAnnotatedCommitHandle annotatedCommit, MergeOptions options) + private MergeResult FastForwardMerge(AnnotatedCommitHandle annotatedCommit, MergeOptions options) { ObjectId id = Proxy.git_annotated_commit_id(annotatedCommit); - Commit fastForwardCommit = (Commit) Lookup(id, ObjectType.Commit); + Commit fastForwardCommit = (Commit)Lookup(id, ObjectType.Commit); Ensure.GitObjectIsNotNull(fastForwardCommit, id.Sha); CheckoutTree(fastForwardCommit.Tree, null, new FastForwardCheckoutOptionsAdapter(options)); @@ -1584,11 +1633,6 @@ internal StringComparer PathComparer get { return pathCase.Value.Comparer; } } - internal bool PathStartsWith(string path, string value) - { - return pathCase.Value.StartsWith(path, value); - } - internal FilePath[] ToFilePaths(IEnumerable paths) { if (paths == null) @@ -1616,277 +1660,6 @@ internal FilePath[] ToFilePaths(IEnumerable paths) return filePaths.ToArray(); } - /// - /// Promotes to the staging area the latest modifications of a file in the working directory (addition, updation or removal). - /// - /// If this path is ignored by configuration then it will not be staged unless is unset. - /// - /// The path of the file within the working directory. - /// Determines how paths will be staged. - public void Stage(string path, StageOptions stageOptions) - { - Ensure.ArgumentNotNull(path, "path"); - - Stage(new[] { path }, stageOptions); - } - - /// - /// Promotes to the staging area the latest modifications of a collection of files in the working directory (addition, updation or removal). - /// - /// Any paths (even those listed explicitly) that are ignored by configuration will not be staged unless is unset. - /// - /// The collection of paths of the files within the working directory. - /// Determines how paths will be staged. - public void Stage(IEnumerable paths, StageOptions stageOptions) - { - Ensure.ArgumentNotNull(paths, "paths"); - - DiffModifiers diffModifiers = DiffModifiers.IncludeUntracked; - ExplicitPathsOptions explicitPathsOptions = stageOptions != null ? stageOptions.ExplicitPathsOptions : null; - - if (stageOptions != null && stageOptions.IncludeIgnored) - { - diffModifiers |= DiffModifiers.IncludeIgnored; - } - - var changes = 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.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)); - } - - foreach (TreeEntryChanges treeEntryChanges in changes - .Where(tec => tec.Status == ChangeKind.Deleted)) - { - RemoveFromIndex(treeEntryChanges.Path); - } - - foreach (TreeEntryChanges treeEntryChanges in changes) - { - switch (treeEntryChanges.Status) - { - case ChangeKind.Added: - case ChangeKind.Modified: - AddToIndex(treeEntryChanges.Path); - break; - - default: - continue; - } - } - - UpdatePhysicalIndex(); - } - - /// - /// Removes from the staging area all the modifications of a file since the latest commit (addition, updation or removal). - /// - /// The path of the file within the working directory. - /// - /// The passed will be treated as explicit paths. - /// Use these options to determine how unmatched explicit paths should be handled. - /// - public void Unstage(string path, ExplicitPathsOptions explicitPathsOptions) - { - Ensure.ArgumentNotNull(path, "path"); - - Unstage(new[] { path }, explicitPathsOptions); - } - - /// - /// Removes from the staging area all the modifications of a collection of file since the latest commit (addition, updation or removal). - /// - /// 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 void Unstage(IEnumerable paths, ExplicitPathsOptions explicitPathsOptions) - { - Ensure.ArgumentNotNull(paths, "paths"); - - if (Info.IsHeadUnborn) - { - var changes = Diff.Compare(null, DiffTargets.Index, paths, explicitPathsOptions, new CompareOptions { Similarity = SimilarityOptions.None }); - - Index.Replace(changes); - } - else - { - Index.Replace(Head.Tip, paths, explicitPathsOptions); - } - } - - /// - /// Moves and/or renames a file in the working directory and promotes the change to the staging area. - /// - /// The path of the file within the working directory which has to be moved/renamed. - /// The target path of the file within the working directory. - public void Move(string sourcePath, string destinationPath) - { - Move(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 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 void Move(IEnumerable sourcePaths, IEnumerable destinationPaths) - { - Ensure.ArgumentNotNull(sourcePaths, "sourcePaths"); - Ensure.ArgumentNotNull(destinationPaths, "destinationPaths"); - - //TODO: Move() should support following use cases: - // - Moving a file under a directory ('file' and 'dir' -> 'dir/file') - // - Moving a directory (and its content) under another directory ('dir1' and 'dir2' -> 'dir2/dir1/*') - - //TODO: Move() should throw when: - // - Moving a directory under a file - - IDictionary, Tuple> batch = PrepareBatch(sourcePaths, destinationPaths); - - if (batch.Count == 0) - { - throw new ArgumentNullException("sourcePaths"); - } - - foreach (KeyValuePair, Tuple> keyValuePair in batch) - { - string sourcePath = keyValuePair.Key.Item1; - string destPath = keyValuePair.Value.Item1; - - if (Directory.Exists(sourcePath) || Directory.Exists(destPath)) - { - throw new NotImplementedException(); - } - - FileStatus sourceStatus = keyValuePair.Key.Item2; - if (sourceStatus.HasAny(new Enum[] { FileStatus.Nonexistent, FileStatus.DeletedFromIndex, FileStatus.NewInWorkdir, FileStatus.DeletedFromWorkdir })) - { - throw new LibGit2SharpException(string.Format(CultureInfo.InvariantCulture, "Unable to move file '{0}'. Its current status is '{1}'.", sourcePath, sourceStatus)); - } - - FileStatus desStatus = keyValuePair.Value.Item2; - if (desStatus.HasAny(new Enum[] { FileStatus.Nonexistent, FileStatus.DeletedFromWorkdir })) - { - continue; - } - - throw new LibGit2SharpException(string.Format(CultureInfo.InvariantCulture, "Unable to overwrite file '{0}'. Its current status is '{1}'.", destPath, desStatus)); - } - - string wd = 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(); - } - - /// - /// 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 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 void Remove(string path, bool removeFromWorkingDirectory, ExplicitPathsOptions explicitPathsOptions) - { - Ensure.ArgumentNotNull(path, "path"); - - Remove(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. - /// - /// - /// 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 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 void Remove(IEnumerable paths, bool removeFromWorkingDirectory, ExplicitPathsOptions explicitPathsOptions) - { - Ensure.ArgumentNotNullOrEmptyEnumerable(paths, "paths"); - - var pathsToDelete = paths.Where(p => Directory.Exists(Path.Combine(Info.WorkingDirectory, p))).ToList(); - var notConflictedPaths = new List(); - - foreach (var path in paths) - { - Ensure.ArgumentNotNullOrEmptyString(path, "path"); - - var conflict = Index.Conflicts[path]; - - if (conflict != null) - { - pathsToDelete.Add(RemoveFromIndex(path)); - } - else - { - notConflictedPaths.Add(path); - } - } - - if (notConflictedPaths.Count > 0) - { - pathsToDelete.AddRange(RemoveStagedItems(notConflictedPaths, removeFromWorkingDirectory, explicitPathsOptions)); - } - - if (removeFromWorkingDirectory) - { - RemoveFilesAndFolders(pathsToDelete); - } - - UpdatePhysicalIndex(); - } - /// /// Retrieves the state of a file in the working directory, comparing it against the staging area and the latest commit. /// @@ -1918,7 +1691,7 @@ internal void ReloadFromDisk() Proxy.git_index_read(Index.Handle); } - private void AddToIndex(string relativePath) + internal void AddToIndex(string relativePath) { if (!Submodules.TryStage(relativePath, true)) { @@ -1926,127 +1699,18 @@ private void AddToIndex(string relativePath) } } - private string RemoveFromIndex(string relativePath) + internal string RemoveFromIndex(string relativePath) { Proxy.git_index_remove_bypath(Index.Handle, relativePath); return relativePath; } - private void UpdatePhysicalIndex() + internal void UpdatePhysicalIndex() { Proxy.git_index_write(Index.Handle); } - private Tuple BuildFrom(string path) - { - string relativePath = this.BuildRelativePathFrom(path); - return new Tuple(relativePath, RetrieveStatus(relativePath)); - } - - 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 IDictionary, Tuple> PrepareBatch(IEnumerable leftPaths, IEnumerable rightPaths) - { - IDictionary, Tuple> dic = new Dictionary, Tuple>(); - - IEnumerator leftEnum = leftPaths.GetEnumerator(); - IEnumerator rightEnum = rightPaths.GetEnumerator(); - - while (Enumerate(leftEnum, rightEnum)) - { - Tuple from = BuildFrom(leftEnum.Current); - Tuple to = BuildFrom(rightEnum.Current); - dic.Add(from, to); - } - - return dic; - } - - private void RemoveFilesAndFolders(IEnumerable pathsList) - { - string wd = 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 IEnumerable RemoveStagedItems(IEnumerable paths, bool removeFromWorkingDirectory = true, ExplicitPathsOptions explicitPathsOptions = null) - { - var removed = new List(); - var changes = Diff.Compare(DiffModifiers.IncludeUnmodified | DiffModifiers.IncludeUntracked, paths, explicitPathsOptions); - - foreach (var treeEntryChanges in changes) - { - var status = RetrieveStatus(treeEntryChanges.Path); - - switch (treeEntryChanges.Status) - { - case ChangeKind.Added: - case ChangeKind.Deleted: - removed.Add(RemoveFromIndex(treeEntryChanges.Path)); - break; - - case ChangeKind.Unmodified: - if (removeFromWorkingDirectory && ( - status.HasFlag(FileStatus.ModifiedInIndex) || - status.HasFlag(FileStatus.NewInIndex) )) - { - throw new RemoveFromIndexException(string.Format(CultureInfo.InvariantCulture, "Unable to remove file '{0}', as it has changes staged in the index. You can call the Remove() method with removeFromWorkingDirectory=false if you want to remove it from the index only.", - treeEntryChanges.Path)); - } - removed.Add(RemoveFromIndex(treeEntryChanges.Path)); - continue; - - case ChangeKind.Modified: - if (status.HasFlag(FileStatus.ModifiedInWorkdir) && status.HasFlag(FileStatus.ModifiedInIndex)) - { - throw new RemoveFromIndexException(string.Format(CultureInfo.InvariantCulture, "Unable to remove file '{0}', as it has staged content different from both the working directory and the HEAD.", - treeEntryChanges.Path)); - } - if (removeFromWorkingDirectory) - { - throw new RemoveFromIndexException(string.Format(CultureInfo.InvariantCulture, "Unable to remove file '{0}', as it has local modifications. You can call the Remove() method with removeFromWorkingDirectory=false if you want to remove it from the index only.", - treeEntryChanges.Path)); - } - removed.Add(RemoveFromIndex(treeEntryChanges.Path)); - continue; - - default: - throw new RemoveFromIndexException(string.Format(CultureInfo.InvariantCulture, "Unable to remove file '{0}'. Its current status is '{1}'.", - treeEntryChanges.Path, treeEntryChanges.Status)); - } - } - - return removed; - } - /// /// Finds the most recent annotated tag that is reachable from a commit. /// @@ -2056,7 +1720,7 @@ private IEnumerable RemoveStagedItems(IEnumerable paths, bool re /// /// /// Optionally, the parameter allow to tweak the - /// search strategy (considering lightweith tags, or even branches as reference points) + /// search strategy (considering lightweight tags, or even branches as reference points) /// and the formatting of the returned identifier. /// /// @@ -2071,14 +1735,37 @@ public string Describe(Commit commit, DescribeOptions 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.IsNull ? 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 922ff82cd..edeb6b93f 100644 --- a/LibGit2Sharp/RepositoryExtensions.cs +++ b/LibGit2Sharp/RepositoryExtensions.cs @@ -98,7 +98,7 @@ private static Commit RetrieveHeadCommit(IRepository repository) { Commit commit = repository.Head.Tip; - Ensure.GitObjectIsNotNull(commit, "HEAD", m => new UnbornBranchException(m)); + Ensure.GitObjectIsNotNull(commit, "HEAD"); return commit; } @@ -178,172 +178,29 @@ public static void Reset(this IRepository repository, ResetMode resetMode, strin repository.Reset(resetMode, commit); } - /// - /// Replaces entries in the with entries from the specified commit. - /// - /// The being worked with. - /// A revparse spec for the target commit object. - /// The list of paths (either files or directories) that should be considered. - /// - /// If set, the passed will be treated as explicit paths. - /// Use these options to determine how unmatched explicit paths should be handled. - /// - [Obsolete("This method will be removed in the next release. Please use Index.Replace() instead.")] - public static void Reset(this IRepository repository, string committish = "HEAD", IEnumerable paths = null, ExplicitPathsOptions explicitPathsOptions = null) - { - if (repository.Info.IsBare) - { - throw new BareRepositoryException("Reset is not allowed in a bare repository"); - } - - Ensure.ArgumentNotNullOrEmptyString(committish, "committish"); - - Commit commit = LookUpCommit(repository, committish); - - repository.Index.Replace(commit, paths, explicitPathsOptions); - } - private static Commit LookUpCommit(IRepository repository, string committish) { GitObject obj = repository.Lookup(committish); Ensure.GitObjectIsNotNull(obj, committish); - return obj.DereferenceToCommit(true); - } - - /// - /// Stores the content of the as a new into the repository. - /// The tip of the will be used as the parent of this new Commit. - /// Once the commit is created, the will move forward to point at it. - /// Both the Author and Committer will be guessed from the Git configuration. An exception will be raised if no configuration is reachable. - /// - /// The being worked with. - /// The description of why a change was made to the repository. - /// The generated . - public static Commit Commit(this IRepository repository, string message) - { - return repository.Commit(message, (CommitOptions)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. - /// - /// The being worked with. - /// The description of why a change was made to the repository. - /// The that specify the commit behavior. - /// The generated . - public static Commit Commit(this IRepository repository, string message, CommitOptions options) - { - Signature author = repository.Config.BuildSignature(DateTimeOffset.Now, true); - - return repository.Commit(message, author, options); + return obj.Peel(true); } /// /// Stores the content of the as a new into the repository. /// The tip of the will be used as the parent of this new Commit. /// Once the commit is created, the will move forward to point at it. - /// The Committer will be guessed from the Git configuration. An exception will be raised if no configuration is reachable. /// - /// The being worked with. - /// The of who made the change. + /// The being worked with. /// The description of why a change was made to the repository. - /// The generated . - public static Commit Commit(this IRepository repository, string message, Signature author) - { - return repository.Commit(message, author, (CommitOptions)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. - /// - /// The being worked with. /// The of who made the change. - /// The description of why a change was made to the repository. - /// The that specify the commit behavior. + /// The of who added the change to the repository. /// The generated . - public static Commit Commit(this IRepository repository, string message, Signature author, CommitOptions options) - { - Signature committer = repository.Config.BuildSignature(DateTimeOffset.Now, true); - - return repository.Commit(message, author, committer, options); - } - - /// - /// Fetch from the specified remote. - /// - /// The being worked with. - /// The name of the to fetch from. - public static void Fetch(this IRepository repository, string remoteName) - { - repository.Fetch(remoteName, null); - } - - /// - /// Fetch from the specified remote. - /// - /// The being worked with. - /// The name of the to fetch from. - /// controlling fetch behavior - public static void Fetch(this IRepository repository, string remoteName, FetchOptions options) - { - Ensure.ArgumentNotNull(repository, "repository"); - Ensure.ArgumentNotNullOrEmptyString(remoteName, "remoteName"); - - Remote remote = repository.Network.Remotes.RemoteForName(remoteName, true); - repository.Network.Fetch(remote, options); - } - - /// - /// Checkout the specified , reference or SHA. - /// - /// The being worked with. - /// A revparse spec for the commit or branch to checkout. - /// The that was checked out. - public static Branch Checkout(this IRepository repository, string commitOrBranchSpec) - { - CheckoutOptions options = new CheckoutOptions(); - return repository.Checkout(commitOrBranchSpec, options); - } - - /// - /// Checkout the commit pointed at by the tip of the specified . - /// - /// If this commit is the current tip of the branch as it exists in the repository, the HEAD - /// will point to this branch. Otherwise, the HEAD will be detached, pointing at the commit sha. - /// - /// - /// The being worked with. - /// The to check out. - /// The that was checked out. - public static Branch Checkout(this IRepository repository, Branch branch) - { - CheckoutOptions options = new CheckoutOptions(); - return repository.Checkout(branch, options); - } - - /// - /// Checkout the specified . - /// - /// Will detach the HEAD and make it point to this commit sha. - /// - /// - /// The being worked with. - /// The to check out. - /// The that was checked out. - public static Branch Checkout(this IRepository repository, Commit commit) + public static Commit Commit(this IRepository repository, string message, Signature author, Signature committer) { - CheckoutOptions options = new CheckoutOptions(); - return repository.Checkout(commit, options); + return repository.Commit(message, author, committer, default(CommitOptions)); } - internal static string BuildRelativePathFrom(this Repository repo, string path) + internal static string BuildRelativePathFrom(this IRepository repo, string path) { //TODO: To be removed when libgit2 natively implements this if (!Path.IsPathRooted(path)) @@ -353,16 +210,23 @@ internal static string BuildRelativePathFrom(this Repository repo, string path) string normalizedPath = Path.GetFullPath(path); - if (!repo.PathStartsWith(normalizedPath, repo.Info.WorkingDirectory)) + if (!PathStartsWith(repo, normalizedPath, repo.Info.WorkingDirectory)) { throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "Unable to process file '{0}'. This file is not located under the working directory of the repository ('{1}').", - normalizedPath, repo.Info.WorkingDirectory)); + normalizedPath, + repo.Info.WorkingDirectory)); } return normalizedPath.Substring(repo.Info.WorkingDirectory.Length); } + internal static bool PathStartsWith(IRepository repository, string path, string value) + { + var pathCase = new PathCase(repository); + return pathCase.StartsWith(path, value); + } + private static ObjectId DereferenceToCommit(Repository repo, string identifier) { var options = LookUpOptions.DereferenceResultToCommit; @@ -466,7 +330,7 @@ internal static IEnumerable Committishes(this Repository repo, object if (throwIfNotFound) { - throw new LibGit2SharpException(string.Format(CultureInfo.InvariantCulture, "Unexpected kind of identifier '{0}'.", identifier)); + throw new LibGit2SharpException("Unexpected kind of identifier '{0}'.", identifier); } yield return null; @@ -507,51 +371,6 @@ public static MergeResult Merge(this IRepository repository, string committish, return repository.Merge(committish, merger, null); } - /// - /// Checkout the tip commit of the specified object. If this commit is the - /// current tip of the branch, will checkout the named branch. Otherwise, will checkout the tip commit - /// as a detached HEAD. - /// - /// The being worked with. - /// The to check out. - /// controlling checkout behavior. - /// The that was checked out. - public static Branch Checkout(this IRepository repository, Branch branch, CheckoutOptions options) - { - return repository.Checkout(branch, options); - } - - /// - /// Checkout the specified . - /// - /// Will detach the HEAD and make it point to this commit sha. - /// - /// - /// The being worked with. - /// The to check out. - /// controlling checkout behavior. - /// The that was checked out. - public static Branch Checkout(this IRepository repository, Commit commit, CheckoutOptions options) - { - return repository.Checkout(commit, options); - } - - /// - /// Checkout the specified , reference or SHA. - /// - /// If the committishOrBranchSpec parameter resolves to a branch name, then the checked out HEAD will - /// will point to the branch. Otherwise, the HEAD will be detached, pointing at the commit sha. - /// - /// - /// The being worked with. - /// A revparse spec for the commit or branch to checkout. - /// controlling checkout behavior. - /// The that was checked out. - public static Branch Checkout(this IRepository repository, string committishOrBranchSpec, CheckoutOptions options) - { - return repository.Checkout(committishOrBranchSpec, options); - } - /// /// Updates specifed paths in the index and working directory with the versions from the specified branch, reference, or SHA. /// @@ -578,44 +397,6 @@ public static void Reset(this IRepository repository, ResetMode resetMode, Commi repository.Reset(resetMode, commit); } - /// - /// Replaces entries in the with entries from the specified commit. - /// - /// The being worked with. - /// The target commit object. - /// The list of paths (either files or directories) that should be considered. - [Obsolete("This method will be removed in the next release. Please use Index.Replace() instead.")] - public static void Reset(this IRepository repository, Commit commit, IEnumerable paths) - { - repository.Index.Replace(commit, paths, null); - } - - /// - /// Replaces entries in the with entries from the specified commit. - /// - /// The being worked with. - /// The target commit object. - [Obsolete("This method will be removed in the next release. Please use Index.Replace() instead.")] - public static void Reset(this IRepository repository, Commit commit) - { - repository.Index.Replace(commit, null, null); - } - - /// - /// Stores the content of the as a new into the repository. - /// The tip of the will be used as the parent of this new Commit. - /// Once the commit is created, the will move forward to point at it. - /// - /// The being worked with. - /// The description of why a change was made to the repository. - /// The of who made the change. - /// The of who added the change to the repository. - /// The generated . - public static Commit Commit(this IRepository repository, string message, Signature author, Signature committer) - { - return repository.Commit(message, author, committer, null); - } - /// /// Find where each line of a file originated. /// @@ -663,116 +444,6 @@ public static RevertResult Revert(this IRepository repository, Commit commit, Si return repository.Revert(commit, reverter, null); } - /// - /// Promotes to the staging area the latest modifications of a file in the working directory (addition, updation or removal). - /// - /// The being worked with. - /// The path of the file within the working directory. - public static void Stage(this IRepository repository, string path) - { - repository.Stage(path, null); - } - - /// - /// Promotes to the staging area the latest modifications of a collection of files in the working directory (addition, updation or removal). - /// - /// The being worked with. - /// The collection of paths of the files within the working directory. - public static void Stage(this IRepository repository, IEnumerable paths) - { - repository.Stage(paths, null); - } - - /// - /// Removes from the staging area all the modifications of a file since the latest commit (addition, updation or removal). - /// - /// The being worked with. - /// The path of the file within the working directory. - public static void Unstage(this IRepository repository, string path) - { - repository.Unstage(path, null); - } - - /// - /// Removes from the staging area all the modifications of a collection of file since the latest commit (addition, updation or removal). - /// - /// The being worked with. - /// The collection of paths of the files within the working directory. - public static void Unstage(this IRepository repository, IEnumerable paths) - { - repository.Unstage(paths, 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. - public static void Remove(this IRepository repository, string path) - { - repository.Remove(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(this IRepository repository, string path, bool removeFromWorkingDirectory) - { - repository.Remove(path, removeFromWorkingDirectory, 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. - /// - /// - /// The being worked with. - /// The collection of paths of the files within the working directory. - public static void Remove(this IRepository repository, IEnumerable paths) - { - repository.Remove(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. - /// - /// - /// The being worked with. - /// The collection of paths of the files within the working directory. - /// True to remove the files from the working directory, False otherwise. - public static void Remove(this IRepository repository, IEnumerable paths, bool removeFromWorkingDirectory) - { - repository.Remove(paths, removeFromWorkingDirectory, null); - } - /// /// Retrieves the state of all files in the working directory, comparing them against the staging area and the latest commit. /// diff --git a/LibGit2Sharp/RepositoryInformation.cs b/LibGit2Sharp/RepositoryInformation.cs index 9f9596617..436b3198e 100644 --- a/LibGit2Sharp/RepositoryInformation.cs +++ b/LibGit2Sharp/RepositoryInformation.cs @@ -23,7 +23,7 @@ internal RepositoryInformation(Repository repo, bool isBare) FilePath path = Proxy.git_repository_path(repo.Handle); FilePath workingDirectoryPath = Proxy.git_repository_workdir(repo.Handle); - Path = path.Native; + Path = path == null ? null : path.Native; WorkingDirectory = workingDirectoryPath == null ? null : workingDirectoryPath.Native; IsShallow = Proxy.git_repository_is_shallow(repo.Handle); } diff --git a/LibGit2Sharp/RepositoryNotFoundException.cs b/LibGit2Sharp/RepositoryNotFoundException.cs index b07ee913c..2255c0891 100644 --- a/LibGit2Sharp/RepositoryNotFoundException.cs +++ b/LibGit2Sharp/RepositoryNotFoundException.cs @@ -14,8 +14,7 @@ public class RepositoryNotFoundException : LibGit2SharpException /// Initializes a new instance of the class. /// public RepositoryNotFoundException() - { - } + { } /// /// Initializes a new instance of the class with a specified error message. @@ -23,8 +22,16 @@ public RepositoryNotFoundException() /// A message that describes the error. public RepositoryNotFoundException(string message) : base(message) - { - } + { } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// A composite format string for use in . + /// An object array that contains zero or more objects to format. + public RepositoryNotFoundException(string format, params object[] args) + : base(format, args) + { } /// /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. @@ -33,8 +40,7 @@ public RepositoryNotFoundException(string message) /// The exception that is the cause of the current exception. If the parameter is not a null reference, the current exception is raised in a catch block that handles the inner exception. public RepositoryNotFoundException(string message, Exception innerException) : base(message, innerException) - { - } + { } /// /// Initializes a new instance of the class with a serialized data. @@ -43,7 +49,6 @@ public RepositoryNotFoundException(string message, Exception innerException) /// The that contains contextual information about the source or destination. protected RepositoryNotFoundException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } + { } } } diff --git a/LibGit2Sharp/RepositoryOperationContext.cs b/LibGit2Sharp/RepositoryOperationContext.cs index 466deb4cb..5b67d7269 100644 --- a/LibGit2Sharp/RepositoryOperationContext.cs +++ b/LibGit2Sharp/RepositoryOperationContext.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace LibGit2Sharp +namespace LibGit2Sharp { /// /// Class to convey information about the repository that is being operated on @@ -25,8 +20,7 @@ protected RepositoryOperationContext() /// 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. @@ -36,10 +30,11 @@ internal RepositoryOperationContext(string repositoryPath, string remoteUrl) /// 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) + internal RepositoryOperationContext( + string repositoryPath, + string remoteUrl, + string parentRepositoryPath, + string submoduleName, int recursionDepth) { RepositoryPath = repositoryPath; RemoteUrl = remoteUrl; diff --git a/LibGit2Sharp/RepositoryOptions.cs b/LibGit2Sharp/RepositoryOptions.cs index a8cf0c7f8..55692a663 100644 --- a/LibGit2Sharp/RepositoryOptions.cs +++ b/LibGit2Sharp/RepositoryOptions.cs @@ -1,4 +1,6 @@ -namespace LibGit2Sharp +using System; + +namespace LibGit2Sharp { /// /// Provides optional additional information to the Repository to be opened. @@ -26,33 +28,6 @@ public sealed class RepositoryOptions /// public string IndexPath { get; set; } - /// - /// Overrides the probed location of the Global configuration file of a repository. - /// - /// The path has either to lead to an existing valid configuration file, - /// or to a non existent configuration file which will be eventually created. - /// - /// - public string GlobalConfigurationLocation { get; set; } - - /// - /// Overrides the probed location of the XDG configuration file of a repository. - /// - /// The path has either to lead to an existing valid configuration file, - /// or to a non existent configuration file which will be eventually created. - /// - /// - public string XdgConfigurationLocation { get; set; } - - /// - /// Overrides the probed location of the System configuration file of a repository. - /// - /// The path has to lead to an existing valid configuration file, - /// or to a non existent configuration file which will be eventually created. - /// - /// - public string SystemConfigurationLocation { get; set; } - /// /// Overrides the default identity to be used when creating reflog entries. /// diff --git a/LibGit2Sharp/RepositoryStatus.cs b/LibGit2Sharp/RepositoryStatus.cs index 56a1c8dda..490dc6798 100644 --- a/LibGit2Sharp/RepositoryStatus.cs +++ b/LibGit2Sharp/RepositoryStatus.cs @@ -34,17 +34,17 @@ public class RepositoryStatus : IEnumerable private static IDictionary> Build() { 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) }, - }; + { + { 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) }, + }; } /// @@ -53,33 +53,19 @@ private static IDictionary> Bu protected RepositoryStatus() { } - internal RepositoryStatus(Repository repo, StatusOptions options) + internal unsafe RepositoryStatus(Repository repo, StatusOptions options) { statusEntries = new List(); using (GitStatusOptions coreOptions = CreateStatusOptions(options ?? new StatusOptions())) - using (StatusListSafeHandle list = Proxy.git_status_list_new(repo.Handle, coreOptions)) + using (StatusListHandle list = Proxy.git_status_list_new(repo.Handle, coreOptions)) { int count = Proxy.git_status_list_entrycount(list); for (int i = 0; i < count; i++) { - StatusEntrySafeHandle e = Proxy.git_status_byindex(list, i); - GitStatusEntry entry = e.MarshalAsGitStatusEntry(); - - GitDiffDelta deltaHeadToIndex = null; - GitDiffDelta deltaIndexToWorkDir = null; - - if (entry.HeadToIndexPtr != IntPtr.Zero) - { - deltaHeadToIndex = entry.HeadToIndexPtr.MarshalAs(); - } - if (entry.IndexToWorkDirPtr != IntPtr.Zero) - { - deltaIndexToWorkDir = entry.IndexToWorkDirPtr.MarshalAs(); - } - - AddStatusEntryForDelta(entry.Status, deltaHeadToIndex, deltaIndexToWorkDir); + git_status_entry *entry = Proxy.git_status_byindex(list, i); + AddStatusEntryForDelta(entry->status, entry->head_to_index, entry->index_to_workdir); } isDirty = statusEntries.Any(entry => entry.State != FileStatus.Ignored && entry.State != FileStatus.Unaltered); @@ -92,12 +78,18 @@ private static GitStatusOptions CreateStatusOptions(StatusOptions options) { Version = 1, Show = (GitStatusShow)options.Show, - Flags = - GitStatusOptionFlags.IncludeIgnored | - GitStatusOptionFlags.IncludeUntracked | - GitStatusOptionFlags.RecurseUntrackedDirs, }; + if (options.IncludeIgnored) + { + coreOptions.Flags |= GitStatusOptionFlags.IncludeIgnored; + } + + if (options.IncludeUntracked) + { + coreOptions.Flags |= GitStatusOptionFlags.IncludeUntracked; + } + if (options.DetectRenamesInIndex) { coreOptions.Flags |= @@ -124,6 +116,12 @@ private static GitStatusOptions CreateStatusOptions(StatusOptions options) GitStatusOptionFlags.RecurseIgnoredDirs; } + if (options.RecurseUntrackedDirs) + { + coreOptions.Flags |= + GitStatusOptionFlags.RecurseUntrackedDirs; + } + if (options.PathSpec != null) { coreOptions.PathSpec = GitStrArrayManaged.BuildFrom(options.PathSpec); @@ -144,30 +142,30 @@ private static GitStatusOptions CreateStatusOptions(StatusOptions options) return coreOptions; } - private void AddStatusEntryForDelta(FileStatus gitStatus, GitDiffDelta deltaHeadToIndex, GitDiffDelta deltaIndexToWorkDir) + private unsafe void AddStatusEntryForDelta(FileStatus gitStatus, git_diff_delta* deltaHeadToIndex, git_diff_delta* deltaIndexToWorkDir) { RenameDetails headToIndexRenameDetails = null; RenameDetails indexToWorkDirRenameDetails = null; if ((gitStatus & FileStatus.RenamedInIndex) == FileStatus.RenamedInIndex) { - headToIndexRenameDetails = new RenameDetails( - LaxFilePathMarshaler.FromNative(deltaHeadToIndex.OldFile.Path).Native, - LaxFilePathMarshaler.FromNative(deltaHeadToIndex.NewFile.Path).Native, - (int)deltaHeadToIndex.Similarity); + headToIndexRenameDetails = + new RenameDetails(LaxUtf8Marshaler.FromNative(deltaHeadToIndex->old_file.Path), + LaxUtf8Marshaler.FromNative(deltaHeadToIndex->new_file.Path), + (int)deltaHeadToIndex->similarity); } if ((gitStatus & FileStatus.RenamedInWorkdir) == FileStatus.RenamedInWorkdir) { - indexToWorkDirRenameDetails = new RenameDetails( - LaxFilePathMarshaler.FromNative(deltaIndexToWorkDir.OldFile.Path).Native, - LaxFilePathMarshaler.FromNative(deltaIndexToWorkDir.NewFile.Path).Native, - (int)deltaIndexToWorkDir.Similarity); + indexToWorkDirRenameDetails = + new RenameDetails(LaxUtf8Marshaler.FromNative(deltaIndexToWorkDir->old_file.Path), + LaxUtf8Marshaler.FromNative(deltaIndexToWorkDir->new_file.Path), + (int)deltaIndexToWorkDir->similarity); } - var filePath = (deltaIndexToWorkDir != null) ? - LaxFilePathMarshaler.FromNative(deltaIndexToWorkDir.NewFile.Path).Native : - LaxFilePathMarshaler.FromNative(deltaHeadToIndex.NewFile.Path).Native; + var filePath = LaxUtf8Marshaler.FromNative(deltaIndexToWorkDir != null ? + deltaIndexToWorkDir->new_file.Path : + deltaHeadToIndex->new_file.Path); StatusEntry statusEntry = new StatusEntry(filePath, gitStatus, headToIndexRenameDetails, indexToWorkDirRenameDetails); @@ -323,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/RevertOptions.cs b/LibGit2Sharp/RevertOptions.cs index 04e43e44f..882afb082 100644 --- a/LibGit2Sharp/RevertOptions.cs +++ b/LibGit2Sharp/RevertOptions.cs @@ -1,7 +1,4 @@ -using LibGit2Sharp.Core; -using LibGit2Sharp.Handlers; - -namespace LibGit2Sharp +namespace LibGit2Sharp { /// /// Options controlling Revert behavior. @@ -13,8 +10,7 @@ public sealed class RevertOptions : MergeAndCheckoutOptionsBase /// 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 diff --git a/LibGit2Sharp/RewriteHistoryOptions.cs b/LibGit2Sharp/RewriteHistoryOptions.cs index 031839c38..59a982dc2 100644 --- a/LibGit2Sharp/RewriteHistoryOptions.cs +++ b/LibGit2Sharp/RewriteHistoryOptions.cs @@ -77,5 +77,13 @@ public RewriteHistoryOptions() /// /// public Action OnError { get; set; } + + /// + /// Specifies Commit message prettifying behavior during rewrite. + /// NOTE: Prettifying may result in losing one or multiple lines in the commit message. + /// As such it is recommended to leave this set to false. + /// + /// true if Commit messages are prettified; otherwise, false. + public bool PrettifyMessages { get; set; } } } diff --git a/LibGit2Sharp/SecureUsernamePasswordCredentials.cs b/LibGit2Sharp/SecureUsernamePasswordCredentials.cs index 5e1d49314..16427ddf3 100644 --- a/LibGit2Sharp/SecureUsernamePasswordCredentials.cs +++ b/LibGit2Sharp/SecureUsernamePasswordCredentials.cs @@ -1,7 +1,7 @@ using System; -using LibGit2Sharp.Core; -using System.Security; using System.Runtime.InteropServices; +using System.Security; +using LibGit2Sharp.Core; namespace LibGit2Sharp { diff --git a/LibGit2Sharp/Signature.cs b/LibGit2Sharp/Signature.cs index bc3f8143f..bad656d05 100644 --- a/LibGit2Sharp/Signature.cs +++ b/LibGit2Sharp/Signature.cs @@ -17,13 +17,11 @@ public sealed class Signature : IEquatable private static readonly LambdaEqualityHelper equalityHelper = new LambdaEqualityHelper(x => x.Name, x => x.Email, x => x.When); - internal Signature(IntPtr signaturePtr) + internal unsafe Signature(git_signature* sig) { - var handle = signaturePtr.MarshalAs(); - - name = LaxUtf8Marshaler.FromNative(handle.Name); - email = LaxUtf8Marshaler.FromNative(handle.Email); - when = Epoch.ToDateTimeOffset(handle.When.Time, handle.When.Offset); + name = LaxUtf8Marshaler.FromNative(sig->name); + email = LaxUtf8Marshaler.FromNative(sig->email); + when = DateTimeOffset.FromUnixTimeSeconds(sig->when.time).ToOffset(TimeSpan.FromMinutes(sig->when.offset)); } /// @@ -58,7 +56,7 @@ public Signature(Identity identity, DateTimeOffset when) this.when = when; } - internal SignatureSafeHandle BuildHandle() + internal SignatureHandle BuildHandle() { return Proxy.git_signature_new(name, email, when); } @@ -147,4 +145,23 @@ public override string ToString() return string.Format(CultureInfo.InvariantCulture, "{0} <{1}>", Name, Email); } } + + internal static class SignatureHelpers + { + /// + /// Build the handle for the Signature, or return a handle + /// to an empty signature. + /// + /// + /// + public static unsafe SignatureHandle SafeBuildHandle(this Signature signature) + { + if (signature == null) + { + return new SignatureHandle(null, false); + } + + return signature.BuildHandle(); + } + } } diff --git a/LibGit2Sharp/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/SmartSubtransport.cs b/LibGit2Sharp/SmartSubtransport.cs index 1203cedf7..c6d747073 100644 --- a/LibGit2Sharp/SmartSubtransport.cs +++ b/LibGit2Sharp/SmartSubtransport.cs @@ -1,6 +1,8 @@ using System; using System.Runtime.InteropServices; +using System.Text; using LibGit2Sharp.Core; +using LibGit2Sharp.Core.Handles; namespace LibGit2Sharp { @@ -47,6 +49,109 @@ public abstract class RpcSmartSubtransport : SmartSubtransport /// public abstract class SmartSubtransport { + internal IntPtr Transport { get; set; } + + /// + /// Call the certificate check callback + /// + /// The certificate to send + /// Whether we consider the certificate to be valid + /// The hostname we connected to + public int CertificateCheck(Certificate cert, bool valid, string hostname) + { + CertificateSsh sshCert = cert as CertificateSsh; + CertificateX509 x509Cert = cert as CertificateX509; + + if (sshCert == null && x509Cert == null) + { + throw new InvalidOperationException("Unsupported certificate type"); + } + + int ret; + if (sshCert != null) + { + var certPtr = sshCert.ToPointer(); + ret = NativeMethods.git_transport_smart_certificate_check(Transport, certPtr, valid ? 1 : 0, hostname); + Marshal.FreeHGlobal(certPtr); + } else { + IntPtr certPtr, dataPtr; + certPtr = x509Cert.ToPointers(out dataPtr); + ret = NativeMethods.git_transport_smart_certificate_check(Transport, certPtr, valid ? 1 : 0, hostname); + Marshal.FreeHGlobal(dataPtr); + Marshal.FreeHGlobal(certPtr); + } + + if (ret > 0 || ret == (int)GitErrorCode.PassThrough) + { + ret = valid ? 0 : -1; + } + + return ret; + } + + /// + /// Acquires credentials. + /// + /// Receives the credentials if the operation is successful. + /// The username. + /// The credential types allowed. The only supported one is . May be empty but should not be null. + /// 0 if successful; a non-zero error code that came from otherwise. + public int AcquireCredentials(out Credentials cred, string user, params Type[] methods) + { + // Convert the user-provided types to libgit2's flags + int allowed = 0; + foreach (var method in methods) + { + if (method == typeof(UsernamePasswordCredentials)) + { + allowed |= (int)GitCredentialType.UserPassPlaintext; + } + else if (method == typeof(DefaultCredentials)) + { + allowed |= (int)GitCredentialType.Default; + } + else + { + throw new InvalidOperationException("Unknown type passes as allowed credential"); + } + } + + IntPtr credHandle = IntPtr.Zero; + int res = Proxy.git_transport_smart_credentials(out credHandle, Transport, user, allowed); + if (res != 0) + { + cred = null; + return res; + } + + if (credHandle == IntPtr.Zero) + { + throw new InvalidOperationException("credentials callback indicated success but returned no credentials"); + } + + unsafe + { + var baseCred = (GitCredential*) credHandle; + switch (baseCred->credtype) + { + case GitCredentialType.UserPassPlaintext: + cred = UsernamePasswordCredentials.FromNative((GitCredentialUserpass*) credHandle); + return 0; + case GitCredentialType.Default: + cred = new DefaultCredentials(); + return 0; + default: + throw new InvalidOperationException("User returned an unkown credential type"); + } + } + } + + /// + /// libgit2 will call an action back with a null url to indicate that + /// it should re-use the prior url; store the url so that we can replay. + /// + private string LastActionUrl { get; set; } + /// /// Invoked by libgit2 to create a connection using this subtransport. /// @@ -60,8 +165,7 @@ public abstract class SmartSubtransport /// Override this method to add additional cleanup steps to your subclass. Be sure to call base.Close(). /// protected virtual void Close() - { - } + { } /// /// Invoked by libgit2 when this subtransport is being freed. Override this method to add additional @@ -123,43 +227,57 @@ private static int Action( SmartSubtransport t = GCHandle.FromIntPtr(Marshal.ReadIntPtr(subtransport, GitSmartSubtransport.GCHandleOffset)).Target as SmartSubtransport; String urlAsString = LaxUtf8Marshaler.FromNative(url); - if (null != t && - !String.IsNullOrEmpty(urlAsString)) + if (t == null) { - try - { - stream = t.Action(urlAsString, action).GitSmartTransportStreamPointer; + Proxy.giterr_set_str(GitErrorCategory.Net, "no subtransport provided"); + return (int)GitErrorCode.Error; + } - return 0; - } - catch (Exception ex) - { - Proxy.giterr_set_str(GitErrorCategory.Net, ex); - } + if (String.IsNullOrEmpty(urlAsString)) + { + urlAsString = t.LastActionUrl; + } + + if (String.IsNullOrEmpty(urlAsString)) + { + Proxy.giterr_set_str(GitErrorCategory.Net, "no url provided"); + return (int)GitErrorCode.Error; } - return (int)GitErrorCode.Error; + try + { + stream = t.Action(urlAsString, action).GitSmartTransportStreamPointer; + t.LastActionUrl = urlAsString; + return 0; + } + catch (Exception ex) + { + Proxy.giterr_set_str(GitErrorCategory.Net, ex); + return (int)GitErrorCode.Error; + } } private static int Close(IntPtr subtransport) { SmartSubtransport t = GCHandle.FromIntPtr(Marshal.ReadIntPtr(subtransport, GitSmartSubtransport.GCHandleOffset)).Target as SmartSubtransport; - if (null != t) + if (t == null) { - try - { - t.Close(); - - return 0; - } - catch (Exception ex) - { - Proxy.giterr_set_str(GitErrorCategory.Net, ex); - } + Proxy.giterr_set_str(GitErrorCategory.Net, "no subtransport provided"); + return (int)GitErrorCode.Error; } - return (int)GitErrorCode.Error; + try + { + t.Close(); + + return 0; + } + catch (Exception ex) + { + Proxy.giterr_set_str(GitErrorCategory.Net, ex); + return (int)GitErrorCode.Error; + } } private static void Free(IntPtr subtransport) diff --git a/LibGit2Sharp/SmartSubtransportRegistration.cs b/LibGit2Sharp/SmartSubtransportRegistration.cs index 8247b023c..adb9ba23d 100644 --- a/LibGit2Sharp/SmartSubtransportRegistration.cs +++ b/LibGit2Sharp/SmartSubtransportRegistration.cs @@ -1,6 +1,8 @@ using System; +using System.Reflection; using System.Runtime.InteropServices; using LibGit2Sharp.Core; +using LibGit2Sharp.Core.Handles; namespace LibGit2Sharp { @@ -9,7 +11,7 @@ namespace LibGit2Sharp /// under a particular scheme (eg "http"). /// /// The type of SmartSubtransport to register - public sealed class SmartSubtransportRegistration + public sealed class SmartSubtransportRegistration : SmartSubtransportRegistrationData where T : SmartSubtransport, new() { /// @@ -24,27 +26,6 @@ internal SmartSubtransportRegistration(string scheme) FunctionPointer = CreateFunctionPointer(); } - /// - /// The URI scheme (eg "http") for this transport. - /// - public string Scheme - { - get; - private set; - } - - internal IntPtr RegistrationPointer - { - get; - private set; - } - - internal IntPtr FunctionPointer - { - get; - private set; - } - private IntPtr CreateRegistrationPointer() { var registration = new GitSmartSubtransportRegistration(); @@ -87,7 +68,9 @@ private static int Subtransport( try { - subtransport = new T().GitSmartSubtransportPointer; + var obj = new T(); + obj.Transport = transport; + subtransport = obj.GitSmartSubtransportPointer; return 0; } diff --git a/LibGit2Sharp/SmartSubtransportRegistrationData.cs b/LibGit2Sharp/SmartSubtransportRegistrationData.cs new file mode 100644 index 000000000..470d64393 --- /dev/null +++ b/LibGit2Sharp/SmartSubtransportRegistrationData.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace LibGit2Sharp +{ + /// + /// Information about a smart subtransport registration. + /// + public abstract class SmartSubtransportRegistrationData + { + /// + /// The URI scheme for this transport, for example "http" or "ssh". + /// + public string Scheme { get; internal set; } + + internal IntPtr RegistrationPointer { get; set; } + + internal IntPtr FunctionPointer { get; set; } + } +} diff --git a/LibGit2Sharp/SmartSubtransportStream.cs b/LibGit2Sharp/SmartSubtransportStream.cs index 7048370ea..ce8aebdf0 100644 --- a/LibGit2Sharp/SmartSubtransportStream.cs +++ b/LibGit2Sharp/SmartSubtransportStream.cs @@ -44,29 +44,35 @@ protected virtual void Free() } /// - /// Requests that the stream write the next length bytes of the stream to the provided Stream object. + /// Reads from the transport into the provided object. /// - public abstract int Read( - Stream dataStream, - long length, - out long bytesRead); + /// The stream to copy the read bytes into. + /// The number of bytes expected from the underlying transport. + /// Receives the number of bytes actually read. + /// The error code to propagate back to the native code that requested this operation. 0 is expected, and exceptions may be thrown. + public abstract int Read(Stream dataStream, long length, out long bytesRead); /// - /// Requests that the stream write the first length bytes of the provided Stream object to the stream. + /// Writes the content of a given stream to the transport. /// - public abstract int Write( - Stream dataStream, - long length); + /// The stream with the data to write to the transport. + /// The number of bytes to read from . + /// The error code to propagate back to the native code that requested this operation. 0 is expected, and exceptions may be thrown. + public abstract int Write(Stream dataStream, long length); /// /// The smart transport that this stream represents a connection over. /// public virtual SmartSubtransport SmartTransport { - get - { - return this.subtransport; - } + get { return this.subtransport; } + } + + private Exception StoredError { get; set; } + + internal void SetError(Exception ex) + { + StoredError = ex; } private SmartSubtransport subtransport; @@ -104,6 +110,19 @@ private static class EntryPoints public static GitSmartSubtransportStream.write_callback WriteCallback = new GitSmartSubtransportStream.write_callback(Write); public static GitSmartSubtransportStream.free_callback FreeCallback = new GitSmartSubtransportStream.free_callback(Free); + private static int SetError(SmartSubtransportStream stream, Exception caught) + { + Exception ret = (stream.StoredError != null) ? stream.StoredError : caught; + GitErrorCode errorCode = GitErrorCode.Error; + + if (ret is NativeException) + { + errorCode = ((NativeException)ret).ErrorCode; + } + + return (int)errorCode; + } + private unsafe static int Read( IntPtr stream, IntPtr buffer, @@ -112,65 +131,78 @@ private unsafe static int Read( { bytes_read = UIntPtr.Zero; - SmartSubtransportStream transportStream = GCHandle.FromIntPtr(Marshal.ReadIntPtr(stream, GitSmartSubtransportStream.GCHandleOffset)).Target as SmartSubtransportStream; + SmartSubtransportStream transportStream = + GCHandle.FromIntPtr(Marshal.ReadIntPtr(stream, GitSmartSubtransportStream.GCHandleOffset)).Target as SmartSubtransportStream; + + if (transportStream == null) + { + Proxy.giterr_set_str(GitErrorCategory.Net, "no transport stream provided"); + return (int)GitErrorCode.Error; + } + + if (buf_size.ToUInt64() >= (ulong)long.MaxValue) + { + Proxy.giterr_set_str(GitErrorCategory.Net, "buffer size is too large"); + return (int)GitErrorCode.Error; + } - if (transportStream != null && - buf_size.ToUInt64() < (ulong)long.MaxValue) + try { - using (UnmanagedMemoryStream memoryStream = new UnmanagedMemoryStream((byte*)buffer, 0, (long)buf_size.ToUInt64(), FileAccess.ReadWrite)) + using (UnmanagedMemoryStream memoryStream = new UnmanagedMemoryStream((byte*)buffer, 0, + (long)buf_size.ToUInt64(), + FileAccess.ReadWrite)) { - try - { - long longBytesRead; + long longBytesRead; - int toReturn = transportStream.Read(memoryStream, (long)buf_size.ToUInt64(), out longBytesRead); + int toReturn = transportStream.Read(memoryStream, (long)buf_size.ToUInt64(), out longBytesRead); - bytes_read = new UIntPtr((ulong)Math.Max(0, longBytesRead)); + bytes_read = new UIntPtr((ulong)Math.Max(0, longBytesRead)); - return toReturn; - } - catch (Exception ex) - { - Proxy.giterr_set_str(GitErrorCategory.Net, ex); - } + return toReturn; } } - - return (int)GitErrorCode.Error; + catch (Exception ex) + { + return SetError(transportStream, ex); + } } - private static unsafe int Write( - IntPtr stream, - IntPtr buffer, - UIntPtr len) + private static unsafe int Write(IntPtr stream, IntPtr buffer, UIntPtr len) { - SmartSubtransportStream transportStream = GCHandle.FromIntPtr(Marshal.ReadIntPtr(stream, GitSmartSubtransportStream.GCHandleOffset)).Target as SmartSubtransportStream; + SmartSubtransportStream transportStream = + GCHandle.FromIntPtr(Marshal.ReadIntPtr(stream, GitSmartSubtransportStream.GCHandleOffset)).Target as SmartSubtransportStream; - if (transportStream != null && - len.ToUInt64() < (ulong)long.MaxValue) + if (transportStream == null) + { + Proxy.giterr_set_str(GitErrorCategory.Net, "no transport stream provided"); + return (int)GitErrorCode.Error; + } + + if (len.ToUInt64() >= (ulong)long.MaxValue) + { + Proxy.giterr_set_str(GitErrorCategory.Net, "write length is too large"); + return (int)GitErrorCode.Error; + } + + try { long length = (long)len.ToUInt64(); using (UnmanagedMemoryStream dataStream = new UnmanagedMemoryStream((byte*)buffer, length)) { - try - { - return transportStream.Write(dataStream, length); - } - catch (Exception ex) - { - Proxy.giterr_set_str(GitErrorCategory.Net, ex); - } + return transportStream.Write(dataStream, length); } } - - return (int)GitErrorCode.Error; + catch (Exception ex) + { + return SetError(transportStream, ex); + } } - private static void Free( - IntPtr stream) + private static void Free(IntPtr stream) { - SmartSubtransportStream transportStream = GCHandle.FromIntPtr(Marshal.ReadIntPtr(stream, GitSmartSubtransportStream.GCHandleOffset)).Target as SmartSubtransportStream; + SmartSubtransportStream transportStream = + GCHandle.FromIntPtr(Marshal.ReadIntPtr(stream, GitSmartSubtransportStream.GCHandleOffset)).Target as SmartSubtransportStream; if (transportStream != null) { diff --git a/LibGit2Sharp/SshAgentCredentials.cs b/LibGit2Sharp/SshAgentCredentials.cs new file mode 100644 index 000000000..5812df2d3 --- /dev/null +++ b/LibGit2Sharp/SshAgentCredentials.cs @@ -0,0 +1,36 @@ +using System; +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + /// + /// Class that holds SSH agent credentials for remote repository access. + /// + public sealed class SshAgentCredentials : 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 (!GlobalSettings.Version.Features.HasFlag(BuiltInFeatures.Ssh)) + { + throw new InvalidOperationException("LibGit2 was not built with SSH support."); + } + + if (Username == null) + { + throw new InvalidOperationException("SshAgentCredentials contains a null Username."); + } + + return NativeMethods.git_cred_ssh_key_from_agent(out cred, Username); + } + + /// + /// Username for SSH authentication. + /// + public string Username { get; set; } + } +} diff --git a/LibGit2Sharp/SshUserKeyCredentials.cs b/LibGit2Sharp/SshUserKeyCredentials.cs new file mode 100644 index 000000000..e5c9e4701 --- /dev/null +++ b/LibGit2Sharp/SshUserKeyCredentials.cs @@ -0,0 +1,66 @@ +using System; +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + /// + /// Class that holds SSH username with key credentials for remote repository access. + /// + public sealed class SshUserKeyCredentials : 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 (!GlobalSettings.Version.Features.HasFlag(BuiltInFeatures.Ssh)) + { + throw new InvalidOperationException("LibGit2 was not built with SSH support."); + } + + if (Username == null) + { + throw new InvalidOperationException("SshUserKeyCredentials contains a null Username."); + } + + if (Passphrase == null) + { + throw new InvalidOperationException("SshUserKeyCredentials contains a null Passphrase."); + } + + if (PublicKey == null) + { + throw new InvalidOperationException("SshUserKeyCredentials contains a null PublicKey."); + } + + if (PrivateKey == null) + { + throw new InvalidOperationException("SshUserKeyCredentials contains a null PrivateKey."); + } + + return NativeMethods.git_cred_ssh_key_new(out cred, Username, PublicKey, PrivateKey, Passphrase); + } + + /// + /// Username for SSH authentication. + /// + public string Username { get; set; } + + /// + /// Public key file location for SSH authentication. + /// + public string PublicKey { get; set; } + + /// + /// Private key file location for SSH authentication. + /// + public string PrivateKey { get; set; } + + /// + /// Passphrase for SSH authentication. + /// + public string Passphrase { get; set; } + } +} 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 1bc509a27..5fe775eba 100644 --- a/LibGit2Sharp/StashCollection.cs +++ b/LibGit2Sharp/StashCollection.cs @@ -42,8 +42,9 @@ internal StashCollection(Repository repo) /// An object that can be used to iterate through the collection. public virtual IEnumerator GetEnumerator() { - return Proxy.git_stash_foreach(repo.Handle, - (index, message, commitId) => new Stash(repo, new ObjectId(commitId), index)).GetEnumerator(); + Func resultSelector = (index, message, commitId) => new Stash(repo, new ObjectId(commitId), index); + + return Proxy.git_stash_foreach(repo.Handle, resultSelector).GetEnumerator(); } /// @@ -69,10 +70,15 @@ public virtual Stash this[int index] throw new ArgumentOutOfRangeException("index", "The passed index must be a positive integer."); } - GitObject stashCommit = repo.Lookup( - string.Format(CultureInfo.InvariantCulture, "stash@{{{0}}}", index), GitObjectType.Commit, LookUpOptions.None); + GitObject stashCommit = repo.Lookup(string.Format(CultureInfo.InvariantCulture, + "stash@{{{0}}}", + index), + GitObjectType.Commit, + LookUpOptions.None); - return stashCommit == null ? null : new Stash(repo, stashCommit.Id, index); + return stashCommit == null + ? null + : new Stash(repo, stashCommit.Id, index); } } @@ -131,6 +137,92 @@ public virtual Stash Add(Signature stasher, string message, StashModifiers optio 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.", "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.", "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. /// @@ -149,8 +241,7 @@ private string DebuggerDisplay { get { - return string.Format(CultureInfo.InvariantCulture, - "Count = {0}", this.Count()); + return string.Format(CultureInfo.InvariantCulture, "Count = {0}", this.Count()); } } } diff --git a/LibGit2Sharp/StatusEntry.cs b/LibGit2Sharp/StatusEntry.cs index 1d62a0983..7008712c6 100644 --- a/LibGit2Sharp/StatusEntry.cs +++ b/LibGit2Sharp/StatusEntry.cs @@ -123,8 +123,9 @@ private string DebuggerDisplay if ((State & FileStatus.RenamedInIndex) == FileStatus.RenamedInIndex || (State & FileStatus.RenamedInWorkdir) == FileStatus.RenamedInWorkdir) { - string oldFilePath = ((State & FileStatus.RenamedInIndex) == FileStatus.RenamedInIndex) ? - HeadToIndexRenameDetails.OldFilePath : IndexToWorkDirRenameDetails.OldFilePath; + string oldFilePath = ((State & FileStatus.RenamedInIndex) != 0) + ? HeadToIndexRenameDetails.OldFilePath + : IndexToWorkDirRenameDetails.OldFilePath; return string.Format(CultureInfo.InvariantCulture, "{0}: {1} -> {2}", State, oldFilePath, FilePath); } diff --git a/LibGit2Sharp/StatusOptions.cs b/LibGit2Sharp/StatusOptions.cs index f389303af..47dba9197 100644 --- a/LibGit2Sharp/StatusOptions.cs +++ b/LibGit2Sharp/StatusOptions.cs @@ -35,6 +35,9 @@ public sealed class StatusOptions public StatusOptions() { DetectRenamesInIndex = true; + IncludeIgnored = true; + IncludeUntracked = true; + RecurseUntrackedDirs = true; } /// @@ -62,6 +65,11 @@ public StatusOptions() /// 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 /// @@ -84,5 +92,18 @@ public StatusOptions() /// Unaltered meaning the file is identical in the working directory, the index and HEAD. /// public bool IncludeUnaltered { get; set; } + + /// + /// Include ignored files when scanning for status + /// + /// + /// ignored meaning present in .gitignore. Defaults to true for back compat but may improve perf to not include if you have thousands of ignored files. + /// + public bool IncludeIgnored { get; set; } + + /// + /// Include untracked files when scanning for status + /// + public bool IncludeUntracked { get; set; } } } diff --git a/LibGit2Sharp/Submodule.cs b/LibGit2Sharp/Submodule.cs index d832609a9..ace995205 100644 --- a/LibGit2Sharp/Submodule.cs +++ b/LibGit2Sharp/Submodule.cs @@ -103,10 +103,7 @@ internal Submodule(Repository repo, string name, string path, string url) /// The of this submodule. public virtual SubmoduleStatus RetrieveStatus() { - using (var handle = Proxy.git_submodule_lookup(repo.Handle, Name)) - { - return Proxy.git_submodule_status(handle); - } + return Proxy.git_submodule_status(repo.Handle, Name); } /// @@ -151,8 +148,7 @@ private string DebuggerDisplay { get { - return string.Format(CultureInfo.InvariantCulture, - "{0} => {1}", Name, Url); + return string.Format(CultureInfo.InvariantCulture, "{0} => {1}", Name, Url); } } diff --git a/LibGit2Sharp/SubmoduleCollection.cs b/LibGit2Sharp/SubmoduleCollection.cs index 14ff416a3..fc508107a 100644 --- a/LibGit2Sharp/SubmoduleCollection.cs +++ b/LibGit2Sharp/SubmoduleCollection.cs @@ -41,10 +41,9 @@ public virtual Submodule this[string name] { Ensure.ArgumentNotNullOrEmptyString(name, "name"); - return Lookup(name, handle => - new Submodule(repo, name, - Proxy.git_submodule_path(handle), - Proxy.git_submodule_url(handle))); + return Lookup(name, handle => new Submodule(repo, name, + Proxy.git_submodule_path(handle), + Proxy.git_submodule_url(handle))); } } @@ -63,9 +62,8 @@ public virtual void Init(string name, bool overwrite) { if (handle == null) { - throw new NotFoundException(string.Format( - CultureInfo.InvariantCulture, - "Submodule lookup failed for '{0}'.", name)); + throw new NotFoundException("Submodule lookup failed for '{0}'.", + name); } Proxy.git_submodule_init(handle, overwrite); @@ -91,9 +89,8 @@ public virtual void Update(string name, SubmoduleUpdateOptions options) { if (handle == null) { - throw new NotFoundException(string.Format( - CultureInfo.InvariantCulture, - "Submodule lookup failed for '{0}'.", name)); + throw new NotFoundException("Submodule lookup failed for '{0}'.", + name); } using (GitCheckoutOptsWrapper checkoutOptionsWrapper = new GitCheckoutOptsWrapper(options)) @@ -103,11 +100,11 @@ public virtual void Update(string name, SubmoduleUpdateOptions options) var remoteCallbacks = new RemoteCallbacks(options); var gitRemoteCallbacks = remoteCallbacks.GenerateCallbacks(); - var gitSubmoduleUpdateOpts = new GitSubmoduleOptions + var gitSubmoduleUpdateOpts = new GitSubmoduleUpdateOptions { Version = 1, CheckoutOptions = gitCheckoutOptions, - FetchOptions = new GitFetchOptions { RemoteCallbacks = gitRemoteCallbacks }, + FetchOptions = new GitFetchOptions { ProxyOptions = new GitProxyOptions { Version = 1 }, RemoteCallbacks = gitRemoteCallbacks }, CloneCheckoutStrategy = CheckoutStrategy.GIT_CHECKOUT_SAFE }; @@ -138,17 +135,18 @@ IEnumerator IEnumerable.GetEnumerator() internal bool TryStage(string relativePath, bool writeIndex) { - return Lookup(relativePath, handle => - { - if (handle == null) - return false; - - Proxy.git_submodule_add_to_index(handle, writeIndex); - return true; - }); + return Lookup(relativePath, + handle => + { + if (handle == null) + return false; + + Proxy.git_submodule_add_to_index(handle, writeIndex); + return true; + }); } - internal T Lookup(string name, Func selector, bool throwIfNotFound = false) + internal T Lookup(string name, Func selector, bool throwIfNotFound = false) { using (var handle = Proxy.git_submodule_lookup(repo.Handle, name)) { @@ -160,9 +158,7 @@ internal T Lookup(string name, Func selector, bool th if (throwIfNotFound) { - throw new LibGit2SharpException(string.Format( - CultureInfo.InvariantCulture, - "Submodule lookup failed for '{0}'.", name)); + throw new LibGit2SharpException("Submodule lookup failed for '{0}'.", name); } return default(T); @@ -173,8 +169,7 @@ private string DebuggerDisplay { get { - return string.Format(CultureInfo.InvariantCulture, - "Count = {0}", this.Count()); + return string.Format(CultureInfo.InvariantCulture, "Count = {0}", this.Count()); } } } diff --git a/LibGit2Sharp/SupportedCredentialTypes.cs b/LibGit2Sharp/SupportedCredentialTypes.cs index bc38a259e..077597011 100644 --- a/LibGit2Sharp/SupportedCredentialTypes.cs +++ b/LibGit2Sharp/SupportedCredentialTypes.cs @@ -18,5 +18,15 @@ public enum SupportedCredentialTypes /// Ask Windows to provide its default credentials for the current user (e.g. NTLM) /// Default = (1 << 1), + + /// + /// SSH with username and public/private keys. (SshUserKeyCredentials, SshAgentCredentials). + /// + Ssh = (1 << 2), + + /// + /// Queries the server with the given username, then later returns the supported credential types. + /// + UsernameQuery = (1 << 3), } } diff --git a/LibGit2Sharp/SymbolicReference.cs b/LibGit2Sharp/SymbolicReference.cs index 5bb475312..4615389e7 100644 --- a/LibGit2Sharp/SymbolicReference.cs +++ b/LibGit2Sharp/SymbolicReference.cs @@ -45,9 +45,12 @@ private string DebuggerDisplay get { return string.Format(CultureInfo.InvariantCulture, - "{0} => {1} => \"{2}\"", - CanonicalName, TargetIdentifier, - (Target != null) ? Target.TargetIdentifier : "?"); + "{0} => {1} => \"{2}\"", + CanonicalName, + TargetIdentifier, + (Target != null) + ? Target.TargetIdentifier + : "?"); } } } diff --git a/LibGit2Sharp/Tag.cs b/LibGit2Sharp/Tag.cs index 35e02120c..cb2436346 100644 --- a/LibGit2Sharp/Tag.cs +++ b/LibGit2Sharp/Tag.cs @@ -13,8 +13,7 @@ protected Tag() internal Tag(Repository repo, Reference reference, string canonicalName) : base(repo, reference, _ => canonicalName) - { - } + { } /// /// Gets the optional information associated to this tag. diff --git a/LibGit2Sharp/TagAnnotation.cs b/LibGit2Sharp/TagAnnotation.cs index 3028cfc16..66a84d556 100644 --- a/LibGit2Sharp/TagAnnotation.cs +++ b/LibGit2Sharp/TagAnnotation.cs @@ -23,8 +23,12 @@ internal TagAnnotation(Repository repo, ObjectId id) : base(repo, id) { lazyName = GitObjectLazyGroup.Singleton(repo, id, Proxy.git_tag_name); - lazyTarget = GitObjectLazyGroup.Singleton(repo, id, - obj => BuildFrom(repo, Proxy.git_tag_target_id(obj), Proxy.git_tag_target_type(obj), null)); + lazyTarget = GitObjectLazyGroup.Singleton(repo, + id, + obj => BuildFrom(repo, + Proxy.git_tag_target_id(obj), + Proxy.git_tag_target_type(obj), + null)); group = new GitObjectLazyGroup(repo, id); lazyTagger = group.AddLazy(Proxy.git_tag_tagger); diff --git a/LibGit2Sharp/TagCollection.cs b/LibGit2Sharp/TagCollection.cs index 603ab66d5..8bd9168b0 100644 --- a/LibGit2Sharp/TagCollection.cs +++ b/LibGit2Sharp/TagCollection.cs @@ -69,6 +69,60 @@ IEnumerator IEnumerable.GetEnumerator() #endregion + /// + /// Creates an annotated tag with the specified name. + /// + /// The name. + /// Revparse spec for the target object. + /// The tagger. + /// The message. + public virtual Tag Add(string name, string objectish, Signature tagger, string message) + { + return Add(name, objectish, tagger, message, false); + } + + /// + /// Creates an annotated tag with the specified name. + /// + /// The name. + /// Revparse spec for the target object. + /// The tagger. + /// The message. + /// True to allow silent overwriting a potentially existing tag, false otherwise. + public virtual Tag Add(string name, string objectish, Signature tagger, string message, bool allowOverwrite) + { + Ensure.ArgumentNotNullOrEmptyString(objectish, "target"); + + GitObject objectToTag = repo.Lookup(objectish, GitObjectType.Any, LookUpOptions.ThrowWhenNoGitObjectHasBeenFound); + + return Add(name, objectToTag, tagger, message, allowOverwrite); + } + + /// + /// Creates a lightweight tag with the specified name. + /// + /// The name. + /// Revparse spec for the target object. + public virtual Tag Add(string name, string objectish) + { + return Add(name, objectish, false); + } + + /// + /// Creates a lightweight tag with the specified name. + /// + /// The name. + /// Revparse spec for the target object. + /// True to allow silent overwriting a potentially existing tag, false otherwise. + public virtual Tag Add( string name, string objectish, bool allowOverwrite) + { + Ensure.ArgumentNotNullOrEmptyString(objectish, "objectish"); + + GitObject objectToTag = repo.Lookup(objectish, GitObjectType.Any, LookUpOptions.ThrowWhenNoGitObjectHasBeenFound); + + return Add(name, objectToTag, allowOverwrite); + } + /// /// Creates an annotated tag with the specified name. /// @@ -133,6 +187,17 @@ public virtual Tag Add(string name, GitObject target, bool allowOverwrite) return this[name]; } + /// + /// Deletes the tag with the specified name. + /// + /// The short or canonical name of the tag to delete. + public virtual void Remove(string name) + { + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + + Proxy.git_tag_delete(repo.Handle, UnCanonicalizeName(name)); + } + /// /// Deletes the tag with the specified name. /// @@ -141,7 +206,7 @@ public virtual void Remove(Tag tag) { Ensure.ArgumentNotNull(tag, "tag"); - this.Remove(tag.CanonicalName); + Remove(tag.CanonicalName); } private static string NormalizeToCanonicalName(string name) @@ -156,7 +221,7 @@ private static string NormalizeToCanonicalName(string name) return string.Concat(Reference.TagPrefix, name); } - internal static string UnCanonicalizeName(string name) + private static string UnCanonicalizeName(string name) { Ensure.ArgumentNotNullOrEmptyString(name, "name"); @@ -172,8 +237,7 @@ private string DebuggerDisplay { get { - return string.Format(CultureInfo.InvariantCulture, - "Count = {0}", this.Count()); + return string.Format(CultureInfo.InvariantCulture, "Count = {0}", this.Count()); } } } diff --git a/LibGit2Sharp/TagCollectionExtensions.cs b/LibGit2Sharp/TagCollectionExtensions.cs deleted file mode 100644 index 20ad5305d..000000000 --- a/LibGit2Sharp/TagCollectionExtensions.cs +++ /dev/null @@ -1,80 +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. - /// The being worked with. - public static Tag Add(this TagCollection tags, string name, string objectish, Signature tagger, string message) - { - return tags.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. - /// The being worked with. - public static Tag Add(this TagCollection tags, string name, string objectish, Signature tagger, string message, bool allowOverwrite) - { - 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. - /// The being worked with. - public static Tag Add(this TagCollection tags, string name, string objectish) - { - return tags.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. - /// The being worked with. - public static Tag Add(this TagCollection tags, string name, string objectish, bool allowOverwrite) - { - 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/TarArchiver.cs b/LibGit2Sharp/TarArchiver.cs index 0710734fb..3c9ecdd51 100644 --- a/LibGit2Sharp/TarArchiver.cs +++ b/LibGit2Sharp/TarArchiver.cs @@ -29,11 +29,23 @@ public override void BeforeArchiving(Tree tree, ObjectId oid, DateTimeOffset mod } // Store the sha in the pax_global_header - using (var stream = new MemoryStream(Encoding.ASCII.GetBytes( - string.Format(CultureInfo.InvariantCulture, "52 comment={0}\n", oid.Sha)))) + using (var stream = + new MemoryStream(Encoding.ASCII.GetBytes(string.Format(CultureInfo.InvariantCulture, + "52 comment={0}\n", + oid.Sha)))) { - writer.Write("pax_global_header", stream, modificationTime, "666".OctalToInt32(), - "0", "0", 'g', "root", "root", "0", "0", oid.Sha, false); + writer.Write("pax_global_header", + stream, modificationTime, + "666".OctalToInt32(), + "0", + "0", + 'g', + "root", + "root", + "0", + "0", + oid.Sha, + false); } } @@ -43,27 +55,55 @@ protected override void AddTreeEntry(string path, TreeEntry entry, DateTimeOffse { case Mode.GitLink: case Mode.Directory: - writer.Write(path + "/", null, modificationTime, "775".OctalToInt32(), - "0", "0", '5', "root", "root", "0", "0", entry.TargetId.Sha, false); + writer.Write(path + "/", + null, + modificationTime, + "775".OctalToInt32(), + "0", + "0", + '5', + "root", + "root", + "0", + "0", + entry.TargetId.Sha, + false); break; case Mode.ExecutableFile: case Mode.NonExecutableFile: case Mode.NonExecutableGroupWritableFile: - var blob = ((Blob) entry.Target); + var blob = ((Blob)entry.Target); - WriteStream(path, entry, modificationTime, - () => blob.IsBinary ? blob.GetContentStream() : blob.GetContentStream(new FilteringOptions(path))); + WriteStream(path, + entry, + modificationTime, + () => blob.IsBinary + ? blob.GetContentStream() + : blob.GetContentStream(new FilteringOptions(path))); break; case Mode.SymbolicLink: using (Stream contentStream = ((Blob)entry.Target).GetContentStream(new FilteringOptions(path))) { - writer.Write(path, contentStream, modificationTime, "777".OctalToInt32(), - "0", "0", '2', "root", "root", "0", "0", entry.TargetId.Sha, true); + writer.Write(path, + contentStream, + modificationTime, + "777".OctalToInt32(), + "0", + "0", + '2', + "root", + "root", + "0", + "0", + entry.TargetId.Sha, + true); } break; default: - throw new InvalidOperationException( - string.Format(CultureInfo.InvariantCulture, "Unsupported file mode: {0} (sha1: {1}).", entry.Mode, entry.TargetId.Sha)); + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, + "Unsupported file mode: {0} (sha1: {1}).", + entry.Mode, + entry.TargetId.Sha)); } } @@ -71,9 +111,21 @@ private void WriteStream(string path, TreeEntry entry, DateTimeOffset modificati { using (Stream contentStream = streamer()) { - writer.Write(path, contentStream, modificationTime, - (entry.Mode == Mode.ExecutableFile) ? "775".OctalToInt32() : "664".OctalToInt32(), - "0", "0", '0', "root", "root", "0", "0", entry.TargetId.Sha, false); + writer.Write(path, + contentStream, + modificationTime, + (entry.Mode == Mode.ExecutableFile) + ? "775".OctalToInt32() + : "664".OctalToInt32(), + "0", + "0", + '0', + "root", + "root", + "0", + "0", + entry.TargetId.Sha, + false); } } diff --git a/LibGit2Sharp/TransferProgress.cs b/LibGit2Sharp/TransferProgress.cs index 29638103a..984c1741e 100644 --- a/LibGit2Sharp/TransferProgress.cs +++ b/LibGit2Sharp/TransferProgress.cs @@ -31,10 +31,7 @@ internal TransferProgress(GitTransferProgress gitTransferProgress) /// public virtual int TotalObjects { - get - { - return (int) gitTransferProgress.total_objects; - } + get { return (int)gitTransferProgress.total_objects; } } /// @@ -42,10 +39,7 @@ public virtual int TotalObjects /// public virtual int IndexedObjects { - get - { - return (int) gitTransferProgress.indexed_objects; - } + get { return (int)gitTransferProgress.indexed_objects; } } /// @@ -53,10 +47,7 @@ public virtual int IndexedObjects /// public virtual int ReceivedObjects { - get - { - return (int) gitTransferProgress.received_objects; - } + get { return (int)gitTransferProgress.received_objects; } } /// @@ -64,10 +55,7 @@ public virtual int ReceivedObjects /// public virtual long ReceivedBytes { - get - { - return (long) gitTransferProgress.received_bytes; - } + get { return (long)gitTransferProgress.received_bytes; } } private string DebuggerDisplay @@ -75,7 +63,10 @@ private string DebuggerDisplay get { return string.Format(CultureInfo.InvariantCulture, - "{0}/{1}, {2} bytes", ReceivedObjects, TotalObjects, ReceivedBytes); + "{0}/{1}, {2} bytes", + ReceivedObjects, + TotalObjects, + ReceivedBytes); } } } diff --git a/LibGit2Sharp/TransientIndex.cs b/LibGit2Sharp/TransientIndex.cs new file mode 100644 index 000000000..65b7b7872 --- /dev/null +++ b/LibGit2Sharp/TransientIndex.cs @@ -0,0 +1,31 @@ +using System; +using LibGit2Sharp.Core.Handles; + +namespace LibGit2Sharp +{ + /// + /// An implementation of with disposal managed by the caller + /// (instead of automatically disposing when the repository is disposed) + /// + public class TransientIndex: Index, IDisposable + { + /// + /// Needed for mocking purposes. + /// + protected TransientIndex() + { } + + internal TransientIndex(IndexHandle handle, Repository repo) + : base(handle, repo) + { + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + this.Handle.SafeDispose(); + } + } +} diff --git a/LibGit2Sharp/Tree.cs b/LibGit2Sharp/Tree.cs index c70eb9ee1..ca7055183 100644 --- a/LibGit2Sharp/Tree.cs +++ b/LibGit2Sharp/Tree.cs @@ -5,6 +5,8 @@ using System.Linq; using LibGit2Sharp.Core; using LibGit2Sharp.Core.Handles; +using System.Text; +using System; namespace LibGit2Sharp { @@ -14,7 +16,7 @@ namespace LibGit2Sharp [DebuggerDisplay("{DebuggerDisplay,nq}")] public class Tree : GitObject, IEnumerable { - private readonly FilePath path; + private readonly string path; private readonly ILazy lazyCount; @@ -24,7 +26,7 @@ public class Tree : GitObject, IEnumerable protected Tree() { } - internal Tree(Repository repo, ObjectId id, FilePath path) + internal Tree(Repository repo, ObjectId id, string path) : base(repo, id) { this.path = path ?? ""; @@ -47,34 +49,56 @@ public virtual TreeEntry this[string relativePath] get { return RetrieveFromPath(relativePath); } } - private TreeEntry RetrieveFromPath(FilePath relativePath) + private unsafe TreeEntry RetrieveFromPath(string relativePath) { - if (relativePath.IsNullOrEmpty()) + if (string.IsNullOrEmpty(relativePath)) { return null; } - using (TreeEntrySafeHandle_Owned treeEntryPtr = Proxy.git_tree_entry_bypath(repo.Handle, Id, relativePath)) + using (TreeEntryHandle treeEntry = Proxy.git_tree_entry_bypath(repo.Handle, Id, relativePath)) { - if (treeEntryPtr == null) + if (treeEntry == null) { return null; } - string posixPath = relativePath.Posix; - string filename = posixPath.Split('/').Last(); - string parentPath = posixPath.Substring(0, posixPath.Length - filename.Length); - return new TreeEntry(treeEntryPtr, Id, repo, path.Combine(parentPath)); + string filename = relativePath.Split('/').Last(); + string parentPath = relativePath.Substring(0, relativePath.Length - filename.Length); + return new TreeEntry(treeEntry, Id, repo, Tree.CombinePath(path, parentPath)); } } internal string Path { - get { return path.Native; } + get { return path; } } #region IEnumerable Members + unsafe TreeEntry byIndex(ObjectSafeWrapper obj, uint i, ObjectId parentTreeId, Repository repo, string parentPath) + { + using (var entryHandle = Proxy.git_tree_entry_byindex(obj.ObjectPtr, i)) + { + return new TreeEntry(entryHandle, parentTreeId, repo, parentPath); + } + } + + internal static string CombinePath(string a, string b) + { + var bld = new StringBuilder(); + bld.Append(a); + if (!String.IsNullOrEmpty(a) && + !a.EndsWith("/", StringComparison.Ordinal) && + !b.StartsWith("/", StringComparison.Ordinal)) + { + bld.Append('/'); + } + bld.Append(b); + + return bld.ToString(); + } + /// /// Returns an enumerator that iterates through the collection. /// @@ -83,10 +107,8 @@ public virtual IEnumerator GetEnumerator() { using (var obj = new ObjectSafeWrapper(Id, repo.Handle)) { - 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); + for (uint i = 0; i < Count; i++) { + yield return byIndex(obj, i, Id, repo, path); } } } @@ -107,7 +129,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 385770875..6e8a0eff5 100644 --- a/LibGit2Sharp/TreeChanges.cs +++ b/LibGit2Sharp/TreeChanges.cs @@ -15,32 +15,10 @@ namespace LibGit2Sharp /// To obtain the actual patch of the diff, use the class when calling Compare.. /// [DebuggerDisplay("{DebuggerDisplay,nq}")] - public class TreeChanges : IEnumerable + public class TreeChanges : IEnumerable, IDiffResult { - private readonly List changes = new List(); - private readonly List added = new List(); - private readonly List deleted = new List(); - private readonly List modified = new List(); - private readonly List typeChanged = new List(); - private readonly List unmodified = new List(); - private readonly List renamed = new List(); - private readonly List copied = new List(); - - private readonly IDictionary> fileDispatcher = Build(); - - private static IDictionary> Build() - { - return new Dictionary> - { - { ChangeKind.Modified, (de, d) => de.modified.Add(d) }, - { ChangeKind.Deleted, (de, d) => de.deleted.Add(d) }, - { ChangeKind.Added, (de, d) => de.added.Add(d) }, - { ChangeKind.TypeChanged, (de, d) => de.typeChanged.Add(d) }, - { ChangeKind.Unmodified, (de, d) => de.unmodified.Add(d) }, - { ChangeKind.Renamed, (de, d) => de.renamed.Add(d) }, - { ChangeKind.Copied, (de, d) => de.copied.Add(d) }, - }; - } + private readonly DiffHandle diff; + private readonly Lazy count; /// /// Needed for mocking purposes. @@ -48,23 +26,47 @@ private static IDictionary> Bu protected TreeChanges() { } - internal TreeChanges(DiffSafeHandle diff) + internal unsafe TreeChanges(DiffHandle diff) { - Proxy.git_diff_foreach(diff, FileCallback, null, null); + this.diff = diff; + this.count = new Lazy(() => Proxy.git_diff_num_deltas(diff)); } - private int FileCallback(GitDiffDelta delta, float progress, IntPtr payload) + /// + /// Enumerates the diff and yields deltas with the specified change kind. + /// + /// Change type to filter on. + private IEnumerable GetChangesOfKind(ChangeKind changeKind) { - AddFileChange(delta); - return 0; + TreeEntryChanges entry; + for (int i = 0; i < Count; i++) + { + if (TryGetEntryWithChangeTypeAt(i, changeKind, out entry)) + { + yield return entry; + } + } } - private void AddFileChange(GitDiffDelta delta) + /// + /// This is method exists to work around .net not allowing unsafe code + /// in iterators. + /// + private unsafe bool TryGetEntryWithChangeTypeAt(int index, ChangeKind changeKind, out TreeEntryChanges entry) { - var treeEntryChanges = new TreeEntryChanges(delta); + if (index < 0 || index > count.Value) + throw new ArgumentOutOfRangeException("index", "Index was out of range. Must be non-negative and less than the size of the collection."); + + var delta = Proxy.git_diff_get_delta(diff, index); - fileDispatcher[treeEntryChanges.Status](this, treeEntryChanges); - changes.Add(treeEntryChanges); + if (TreeEntryChanges.GetStatusFromChangeKind(delta->status) == changeKind) + { + entry = new TreeEntryChanges(delta); + return true; + } + + entry = null; + return false; } #region IEnumerable Members @@ -75,7 +77,22 @@ private void AddFileChange(GitDiffDelta delta) /// An object that can be used to iterate through the collection. public virtual IEnumerator GetEnumerator() { - return changes.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("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)); } /// @@ -89,13 +106,12 @@ IEnumerator IEnumerable.GetEnumerator() #endregion - /// /// List of that have been been added. /// public virtual IEnumerable Added { - get { return added; } + get { return GetChangesOfKind(ChangeKind.Added); } } /// @@ -103,7 +119,7 @@ public virtual IEnumerable Added /// public virtual IEnumerable Deleted { - get { return deleted; } + get { return GetChangesOfKind(ChangeKind.Deleted); } } /// @@ -111,7 +127,7 @@ public virtual IEnumerable Deleted /// public virtual IEnumerable Modified { - get { return modified; } + get { return GetChangesOfKind(ChangeKind.Modified); } } /// @@ -119,7 +135,7 @@ public virtual IEnumerable Modified /// public virtual IEnumerable TypeChanged { - get { return typeChanged; } + get { return GetChangesOfKind(ChangeKind.TypeChanged); } } /// @@ -127,7 +143,7 @@ public virtual IEnumerable TypeChanged /// public virtual IEnumerable Renamed { - get { return renamed; } + get { return GetChangesOfKind(ChangeKind.Renamed); } } /// @@ -135,7 +151,7 @@ public virtual IEnumerable Renamed /// public virtual IEnumerable Copied { - get { return copied; } + get { return GetChangesOfKind(ChangeKind.Copied); } } /// @@ -143,7 +159,23 @@ public virtual IEnumerable Copied /// public virtual IEnumerable Unmodified { - get { return unmodified; } + get { return GetChangesOfKind(ChangeKind.Unmodified); } + } + + /// + /// List of which are conflicted + /// + public virtual IEnumerable Conflicted + { + get { return GetChangesOfKind(ChangeKind.Conflicted); } + } + + /// + /// Gets the number of in this comparison. + /// + public virtual int Count + { + get { return count.Value; } } private string DebuggerDisplay @@ -151,10 +183,32 @@ private string DebuggerDisplay get { return string.Format(CultureInfo.InvariantCulture, - "+{0} ~{1} -{2} \u00B1{3} R{4} C{5}", - Added.Count(), Modified.Count(), Deleted.Count(), - TypeChanged.Count(), Renamed.Count(), Copied.Count()); + "+{0} ~{1} -{2} \u00B1{3} R{4} C{5}", + Added.Count(), + Modified.Count(), + Deleted.Count(), + TypeChanged.Count(), + Renamed.Count(), + Copied.Count()); } } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases unmanaged and - optionally - managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + diff.SafeDispose(); + } } } diff --git a/LibGit2Sharp/TreeDefinition.cs b/LibGit2Sharp/TreeDefinition.cs index a88a24b53..73c21aac7 100644 --- a/LibGit2Sharp/TreeDefinition.cs +++ b/LibGit2Sharp/TreeDefinition.cs @@ -57,6 +57,24 @@ private void AddEntry(string targetTreeEntryName, TreeEntryDefinition treeEntryD entries.Add(targetTreeEntryName, treeEntryDefinition); } + /// + /// Removes the located at each of the + /// specified . + /// + /// The paths within this . + /// The current . + public virtual TreeDefinition Remove(IEnumerable treeEntryPaths) + { + Ensure.ArgumentNotNull(treeEntryPaths, "treeEntryPaths"); + + foreach (var treeEntryPath in treeEntryPaths) + { + Remove(treeEntryPath); + } + + return this; + } + /// /// Removes a located the specified path. /// @@ -108,17 +126,15 @@ public virtual TreeDefinition Add(string targetTreeEntryPath, TreeEntryDefinitio Ensure.ArgumentNotNullOrEmptyString(targetTreeEntryPath, "targetTreeEntryPath"); Ensure.ArgumentNotNull(treeEntryDefinition, "treeEntryDefinition"); - if (Path.IsPathRooted(targetTreeEntryPath)) - { - throw new ArgumentException("The provided path is an absolute path."); - } - if (treeEntryDefinition is TransientTreeTreeEntryDefinition) { throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, - "The {0} references a target which hasn't been created in the {1} yet. " + - "This situation can occur when the target is a whole new {2} being created, or when an existing {2} is being updated because some of its children were added/removed.", - typeof(TreeEntryDefinition).Name, typeof(ObjectDatabase).Name, typeof(Tree).Name)); + "The {0} references a target which hasn't been created in the {1} yet. " + + "This situation can occur when the target is a whole new {2} being created, " + + "or when an existing {2} is being updated because some of its children were added/removed.", + typeof(TreeEntryDefinition).Name, + typeof(ObjectDatabase).Name, + typeof(Tree).Name)); } Tuple segments = ExtractPosixLeadingSegment(targetTreeEntryPath); @@ -185,6 +201,23 @@ public virtual TreeDefinition Add(string targetTreeEntryPath, string filePath, M return Add(targetTreeEntryPath, ted); } + /// + /// Adds or replaces a from an existing blob specified by its Object ID at the specified location. + /// + /// The path within this . + /// The object ID for this entry. + /// The file related attributes. + /// The current . + public virtual TreeDefinition Add(string targetTreeEntryPath, ObjectId id, Mode mode) + { + Ensure.ArgumentNotNull(id, "id"); + Ensure.ArgumentConformsTo(mode, m => m.HasAny(TreeEntryDefinition.BlobModes), "mode"); + + TreeEntryDefinition ted = TreeEntryDefinition.From(id, mode); + + return Add(targetTreeEntryPath, ted); + } + /// /// Adds or replaces a , dynamically built from the provided , at the specified location. /// @@ -352,7 +385,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; @@ -360,9 +395,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)))) { @@ -374,7 +409,7 @@ private static Tuple ExtractPosixLeadingSegment(FilePath targetP private class TreeBuilder : IDisposable { - private readonly TreeBuilderSafeHandle handle; + private readonly TreeBuilderHandle handle; public TreeBuilder(Repository repo) { diff --git a/LibGit2Sharp/TreeDefinitionExtensions.cs b/LibGit2Sharp/TreeDefinitionExtensions.cs deleted file mode 100644 index 4ff8c62cb..000000000 --- a/LibGit2Sharp/TreeDefinitionExtensions.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Collections.Generic; -using LibGit2Sharp.Core; - -namespace LibGit2Sharp -{ - /// - /// Provides helper overloads to a . - /// - public static class TreeDefinitionExtensions - { - /// - /// Removes the located at each of the - /// specified . - /// - /// The . - /// The paths within this . - /// The current . - public static TreeDefinition Remove(this TreeDefinition td, IEnumerable treeEntryPaths) - { - Ensure.ArgumentNotNull(td, "td"); - Ensure.ArgumentNotNull(treeEntryPaths, "treeEntryPaths"); - - foreach (var treeEntryPath in treeEntryPaths) - { - td.Remove(treeEntryPath); - } - - return td; - } - } -} diff --git a/LibGit2Sharp/TreeEntry.cs b/LibGit2Sharp/TreeEntry.cs index 54dd95998..e500a8ee1 100644 --- a/LibGit2Sharp/TreeEntry.cs +++ b/LibGit2Sharp/TreeEntry.cs @@ -1,13 +1,15 @@ using System; +using System.Diagnostics; using System.Globalization; -using System.Runtime.InteropServices; using LibGit2Sharp.Core; +using LibGit2Sharp.Core.Handles; namespace LibGit2Sharp { /// /// Representation of an entry in a . /// + [DebuggerDisplay("{DebuggerDisplay,nq}")] public class TreeEntry : IEquatable { private readonly ObjectId parentTreeId; @@ -25,20 +27,20 @@ public class TreeEntry : IEquatable protected TreeEntry() { } - internal TreeEntry(SafeHandle obj, ObjectId parentTreeId, Repository repo, FilePath parentPath) + internal unsafe TreeEntry(TreeEntryHandle entry, ObjectId parentTreeId, Repository repo, string parentPath) { this.parentTreeId = parentTreeId; this.repo = repo; - targetOid = Proxy.git_tree_entry_id(obj); + targetOid = Proxy.git_tree_entry_id(entry); - GitObjectType treeEntryTargetType = Proxy.git_tree_entry_type(obj); + GitObjectType treeEntryTargetType = Proxy.git_tree_entry_type(entry); TargetType = treeEntryTargetType.ToTreeEntryTargetType(); target = new Lazy(RetrieveTreeEntryTarget); - Mode = Proxy.git_tree_entry_attributes(obj); - Name = Proxy.git_tree_entry_name(obj); - path = new Lazy(() => System.IO.Path.Combine(parentPath.Native, Name)); + Mode = Proxy.git_tree_entry_attributes(entry); + Name = Proxy.git_tree_entry_name(entry); + path = new Lazy(() => Tree.CombinePath(parentPath, Name)); } /// @@ -84,10 +86,9 @@ private GitObject RetrieveTreeEntryTarget() return GitObject.BuildFrom(repo, targetOid, TargetType.ToGitObjectType(), Path); default: - throw new InvalidOperationException( - string.Format(CultureInfo.InvariantCulture, - "TreeEntry target of type '{0}' is not supported.", - TargetType)); + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, + "TreeEntry target of type '{0}' is not supported.", + TargetType)); } } @@ -141,5 +142,16 @@ public override int GetHashCode() { return !Equals(left, right); } + + private string DebuggerDisplay + { + get + { + return string.Format(CultureInfo.InvariantCulture, + "TreeEntry: {0} => {1}", + Path, + TargetId); + } + } } } diff --git a/LibGit2Sharp/TreeEntryChanges.cs b/LibGit2Sharp/TreeEntryChanges.cs index 205ff42c3..1ab1a6172 100644 --- a/LibGit2Sharp/TreeEntryChanges.cs +++ b/LibGit2Sharp/TreeEntryChanges.cs @@ -16,19 +16,35 @@ public class TreeEntryChanges protected TreeEntryChanges() { } - internal TreeEntryChanges(GitDiffDelta delta) + internal unsafe TreeEntryChanges(git_diff_delta* delta) { - Path = LaxFilePathMarshaler.FromNative(delta.NewFile.Path).Native; - OldPath = LaxFilePathMarshaler.FromNative(delta.OldFile.Path).Native; + Path = LaxUtf8Marshaler.FromNative(delta->new_file.Path); + OldPath = LaxUtf8Marshaler.FromNative(delta->old_file.Path); - Mode = (Mode)delta.NewFile.Mode; - OldMode = (Mode)delta.OldFile.Mode; - Oid = delta.NewFile.Id; - OldOid = delta.OldFile.Id; + Mode = (Mode)delta->new_file.Mode; + OldMode = (Mode)delta->old_file.Mode; + Oid = ObjectId.BuildFromPtr(&delta->new_file.Id); + OldOid = ObjectId.BuildFromPtr(&delta->old_file.Id); + Exists = (delta->new_file.Flags & GitDiffFlags.GIT_DIFF_FLAG_EXISTS) != 0; + OldExists = (delta->old_file.Flags & GitDiffFlags.GIT_DIFF_FLAG_EXISTS) != 0; - Status = (delta.Status == ChangeKind.Untracked || delta.Status == ChangeKind.Ignored) - ? ChangeKind.Added - : delta.Status; + Status = GetStatusFromChangeKind(delta->status); + } + + // This treatment of change kind was apparently introduced in order to be able + // to compare a tree against the index, see commit fdc972b. It's extracted + // here so that TreeEntry can use the same rules without having to instantiate + // a TreeEntryChanges object. + internal static ChangeKind GetStatusFromChangeKind(ChangeKind changeKind) + { + switch (changeKind) + { + case ChangeKind.Untracked: + case ChangeKind.Ignored: + return ChangeKind.Added; + default: + return changeKind; + } } /// @@ -46,6 +62,17 @@ internal TreeEntryChanges(GitDiffDelta delta) /// public virtual ObjectId Oid { get; private set; } + /// + /// The file exists in the new side of the diff. + /// This is useful in determining if you have content in + /// the ours or theirs side of a conflict. This will + /// be false during a conflict that deletes both the + /// "ours" and "theirs" sides, or when the diff is a + /// delete and the status is + /// . + /// + public virtual bool Exists { get; private set; } + /// /// The kind of change that has been done (added, deleted, modified ...). /// @@ -66,13 +93,26 @@ internal TreeEntryChanges(GitDiffDelta delta) /// public virtual ObjectId OldOid { get; private set; } + /// + /// The file exists in the old side of the diff. + /// This is useful in determining if you have an ancestor + /// side to a conflict. This will be false during a + /// conflict that involves both the "ours" and "theirs" + /// side being added, or when the diff is an add and the + /// status is . + /// + public virtual bool OldExists { get; private set; } + private string DebuggerDisplay { get { return string.Format(CultureInfo.InvariantCulture, - "Path = {0}, File {1}", - !string.IsNullOrEmpty(Path) ? Path : OldPath, Status); + "Path = {0}, File {1}", + !string.IsNullOrEmpty(Path) + ? Path + : OldPath, + Status); } } } diff --git a/LibGit2Sharp/TreeEntryDefinition.cs b/LibGit2Sharp/TreeEntryDefinition.cs index c0eb979d3..2a3ceb35f 100644 --- a/LibGit2Sharp/TreeEntryDefinition.cs +++ b/LibGit2Sharp/TreeEntryDefinition.cs @@ -19,8 +19,7 @@ public class TreeEntryDefinition : IEquatable /// Needed for mocking purposes. /// protected TreeEntryDefinition() - { - } + { } /// /// Gets file mode. @@ -45,23 +44,38 @@ internal virtual GitObject Target internal static TreeEntryDefinition From(TreeEntry treeEntry) { return new TreeEntryDefinition - { - Mode = treeEntry.Mode, - TargetType = treeEntry.TargetType, - TargetId = treeEntry.TargetId, - target = new Lazy(() => treeEntry.Target) - }; + { + Mode = treeEntry.Mode, + TargetType = treeEntry.TargetType, + TargetId = treeEntry.TargetId, + target = new Lazy(() => treeEntry.Target) + }; } internal static TreeEntryDefinition From(Blob blob, Mode mode) { + Ensure.ArgumentNotNull(blob, "blob"); + + return new TreeEntryDefinition + { + Mode = mode, + TargetType = TreeEntryTargetType.Blob, + TargetId = blob.Id, + target = new Lazy(() => blob) + }; + } + + internal static TreeEntryDefinition From(ObjectId id, Mode mode) + { + Ensure.ArgumentNotNull(id, "id"); + Ensure.ArgumentNotNull(mode, "mode"); + return new TreeEntryDefinition - { - Mode = mode, - TargetType = TreeEntryTargetType.Blob, - TargetId = blob.Id, - target = new Lazy(() => blob) - }; + { + Mode = mode, + TargetType = TreeEntryTargetType.Blob, + TargetId = id + }; } internal static TreeEntryDefinition TransientBlobFrom(string filePath, Mode mode) @@ -69,32 +83,32 @@ internal static TreeEntryDefinition TransientBlobFrom(string filePath, Mode mode Ensure.ArgumentConformsTo(mode, m => m.HasAny(BlobModes), "mode"); return new TransientBlobTreeEntryDefinition - { - Builder = odb => odb.CreateBlob(filePath), - Mode = mode, - }; + { + Builder = odb => odb.CreateBlob(filePath), + Mode = mode, + }; } internal static TreeEntryDefinition From(ObjectId objectId) { return new TreeEntryDefinition - { - Mode = Mode.GitLink, - TargetType = TreeEntryTargetType.GitLink, - TargetId = objectId, - target = new Lazy(() => { throw new InvalidOperationException("Shouldn't be necessary."); }), - }; + { + Mode = Mode.GitLink, + TargetType = TreeEntryTargetType.GitLink, + TargetId = objectId, + target = new Lazy(() => { throw new InvalidOperationException("Shouldn't be necessary."); }), + }; } internal static TreeEntryDefinition From(Tree tree) { return new TreeEntryDefinition - { - Mode = Mode.Directory, - TargetType = TreeEntryTargetType.Tree, - TargetId = tree.Id, - target = new Lazy(() => tree) - }; + { + Mode = Mode.Directory, + TargetType = TreeEntryTargetType.Tree, + TargetId = tree.Id, + target = new Lazy(() => tree) + }; } /// diff --git a/LibGit2Sharp/TreeEntryTargetType.cs b/LibGit2Sharp/TreeEntryTargetType.cs index a4e54d73a..181708ec7 100644 --- a/LibGit2Sharp/TreeEntryTargetType.cs +++ b/LibGit2Sharp/TreeEntryTargetType.cs @@ -38,8 +38,9 @@ public static GitObjectType ToGitObjectType(this TreeEntryTargetType type) return GitObjectType.Blob; default: - throw new InvalidOperationException( - string.Format(CultureInfo.InvariantCulture, "Cannot map {0} to a GitObjectType.", type)); + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, + "Cannot map {0} to a GitObjectType.", + type)); } } } diff --git a/LibGit2Sharp/UnbornBranchException.cs b/LibGit2Sharp/UnbornBranchException.cs index 2704d1a93..34ef437cb 100644 --- a/LibGit2Sharp/UnbornBranchException.cs +++ b/LibGit2Sharp/UnbornBranchException.cs @@ -14,8 +14,7 @@ public class UnbornBranchException : LibGit2SharpException /// Initializes a new instance of the class. /// public UnbornBranchException() - { - } + { } /// /// Initializes a new instance of the class with a specified error message. @@ -23,8 +22,16 @@ public UnbornBranchException() /// A message that describes the error. public UnbornBranchException(string message) : base(message) - { - } + { } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// A composite format string for use in . + /// An object array that contains zero or more objects to format. + public UnbornBranchException(string format, params object[] args) + : base(format, args) + { } /// /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. @@ -33,8 +40,7 @@ public UnbornBranchException(string message) /// The exception that is the cause of the current exception. If the parameter is not a null reference, the current exception is raised in a catch block that handles the inner exception. public UnbornBranchException(string message, Exception innerException) : base(message, innerException) - { - } + { } /// /// Initializes a new instance of the class with a serialized data. @@ -43,7 +49,6 @@ public UnbornBranchException(string message, Exception innerException) /// The that contains contextual information about the source or destination. protected UnbornBranchException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } + { } } } diff --git a/LibGit2Sharp/UniqueIdentifier.targets b/LibGit2Sharp/UniqueIdentifier.targets deleted file mode 100644 index f6eb926e3..000000000 --- a/LibGit2Sharp/UniqueIdentifier.targets +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - . - $(MSBuildThisFileDirectory) - $(LibGit2SharpPath)\Core\UniqueIdentifier.cs - $(CoreCompileDependsOn);GenerateUniqueIdentifierCs - $(CoreCleanDependsOn);CleanUniqueIdentifierCs - - - - - - - - - - - diff --git a/LibGit2Sharp/UnmatchedPathException.cs b/LibGit2Sharp/UnmatchedPathException.cs index e63b35149..7d118346d 100644 --- a/LibGit2Sharp/UnmatchedPathException.cs +++ b/LibGit2Sharp/UnmatchedPathException.cs @@ -13,8 +13,7 @@ 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. @@ -22,8 +21,16 @@ public UnmatchedPathException() /// A message that describes the error. public UnmatchedPathException(string message) : base(message) - { - } + { } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// A composite format string for use in . + /// An object array that contains zero or more objects to format. + public UnmatchedPathException(string format, params object[] args) + : base(format, args) + { } /// /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. @@ -32,8 +39,7 @@ public UnmatchedPathException(string message) /// The exception that is the cause of the current exception. If the parameter is not a null reference, the current exception is raised in a catch block that handles the inner exception. public UnmatchedPathException(string message, Exception innerException) : base(message, innerException) - { - } + { } /// /// Initializes a new instance of the class with a serialized data. @@ -42,7 +48,6 @@ public UnmatchedPathException(string message, Exception innerException) /// The that contains contextual information about the source or destination. protected UnmatchedPathException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } + { } } } diff --git a/LibGit2Sharp/UnmergedIndexEntriesException.cs b/LibGit2Sharp/UnmergedIndexEntriesException.cs index f221b4a61..7594049b1 100644 --- a/LibGit2Sharp/UnmergedIndexEntriesException.cs +++ b/LibGit2Sharp/UnmergedIndexEntriesException.cs @@ -9,14 +9,13 @@ namespace LibGit2Sharp /// is performed against an index with unmerged entries /// [Serializable] - public class UnmergedIndexEntriesException : LibGit2SharpException + public class UnmergedIndexEntriesException : NativeException { /// /// Initializes a new instance of the class. /// public UnmergedIndexEntriesException() - { - } + { } /// /// Initializes a new instance of the class with a specified error message. @@ -24,8 +23,16 @@ public UnmergedIndexEntriesException() /// A message that describes the error. public UnmergedIndexEntriesException(string message) : base(message) - { - } + { } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// A composite format string for use in . + /// An object array that contains zero or more objects to format. + public UnmergedIndexEntriesException(string format, params object[] args) + : base(format, args) + { } /// /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. @@ -34,8 +41,7 @@ public UnmergedIndexEntriesException(string message) /// The exception that is the cause of the current exception. If the parameter is not a null reference, the current exception is raised in a catch block that handles the inner exception. public UnmergedIndexEntriesException(string message, Exception innerException) : base(message, innerException) - { - } + { } /// /// Initializes a new instance of the class with a serialized data. @@ -44,12 +50,18 @@ public UnmergedIndexEntriesException(string message, Exception innerException) /// The that contains contextual information about the source or destination. protected UnmergedIndexEntriesException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } + { } + + internal UnmergedIndexEntriesException(string message, GitErrorCategory category) + : base(message, category) + { } - internal UnmergedIndexEntriesException(string message, GitErrorCode code, GitErrorCategory category) - : base(message, code, category) + internal override GitErrorCode ErrorCode { + get + { + return GitErrorCode.UnmergedEntries; + } } } } diff --git a/LibGit2Sharp/UserCanceledException.cs b/LibGit2Sharp/UserCanceledException.cs index fdc03396e..ba6458049 100644 --- a/LibGit2Sharp/UserCanceledException.cs +++ b/LibGit2Sharp/UserCanceledException.cs @@ -8,14 +8,13 @@ namespace LibGit2Sharp /// The exception that is thrown when an operation is canceled. /// [Serializable] - public class UserCancelledException : LibGit2SharpException + public class UserCancelledException : NativeException { /// /// Initializes a new instance of the class. /// public UserCancelledException() - { - } + { } /// /// Initializes a new instance of the class with a specified error message. @@ -23,8 +22,16 @@ public UserCancelledException() /// A message that describes the error. public UserCancelledException(string message) : base(message) - { - } + { } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// A composite format string for use in . + /// An object array that contains zero or more objects to format. + public UserCancelledException(string format, params object[] args) + : base(format, args) + { } /// /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. @@ -33,8 +40,7 @@ public UserCancelledException(string message) /// The exception that is the cause of the current exception. If the parameter is not a null reference, the current exception is raised in a catch block that handles the inner exception. public UserCancelledException(string message, Exception innerException) : base(message, innerException) - { - } + { } /// /// Initializes a new instance of the class with a serialized data. @@ -43,12 +49,18 @@ public UserCancelledException(string message, Exception innerException) /// The that contains contextual information about the source or destination. protected UserCancelledException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } + { } + + internal UserCancelledException(string message, GitErrorCategory category) + : base(message, category) + { } - internal UserCancelledException(string message, GitErrorCode code, GitErrorCategory category) - : base(message, code, category) + internal override GitErrorCode ErrorCode { + get + { + return GitErrorCode.User; + } } } } diff --git a/LibGit2Sharp/UsernamePasswordCredentials.cs b/LibGit2Sharp/UsernamePasswordCredentials.cs index 3d977a733..761be5c74 100644 --- a/LibGit2Sharp/UsernamePasswordCredentials.cs +++ b/LibGit2Sharp/UsernamePasswordCredentials.cs @@ -1,4 +1,6 @@ using System; +using System.Text; +using System.Runtime.InteropServices; using LibGit2Sharp.Core; namespace LibGit2Sharp @@ -23,6 +25,15 @@ protected internal override int GitCredentialHandler(out IntPtr cred) return NativeMethods.git_cred_userpass_plaintext_new(out cred, Username, Password); } + static internal unsafe UsernamePasswordCredentials FromNative(GitCredentialUserpass* gitCred) + { + return new UsernamePasswordCredentials() + { + Username = LaxUtf8Marshaler.FromNative(gitCred->username), + Password = LaxUtf8Marshaler.FromNative(gitCred->password), + }; + } + /// /// Username for username/password authentication (as in HTTP basic auth). /// diff --git a/LibGit2Sharp/UsernameQueryCredentials.cs b/LibGit2Sharp/UsernameQueryCredentials.cs new file mode 100644 index 000000000..14981d74e --- /dev/null +++ b/LibGit2Sharp/UsernameQueryCredentials.cs @@ -0,0 +1,31 @@ +using System; +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + /// + /// Class that holds username query credentials for remote repository access. + /// + public sealed class UsernameQueryCredentials : 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) + { + throw new InvalidOperationException("UsernameQueryCredentials contains a null Username."); + } + + return NativeMethods.git_cred_username_new(out cred, Username); + } + + /// + /// Username for querying the server for supported authentication. + /// + public string Username { get; set; } + } +} diff --git a/LibGit2Sharp/Version.cs b/LibGit2Sharp/Version.cs index a3f7edb6b..3795382a3 100644 --- a/LibGit2Sharp/Version.cs +++ b/LibGit2Sharp/Version.cs @@ -1,7 +1,4 @@ using System.Globalization; -using System.IO; -using System.Linq; -using System.Reflection; using LibGit2Sharp.Core; namespace LibGit2Sharp @@ -11,8 +8,6 @@ namespace LibGit2Sharp /// public class Version { - private readonly Assembly assembly = typeof(Repository).Assembly; - /// /// Needed for mocking purposes. /// @@ -27,17 +22,7 @@ internal static Version Build() /// /// Returns version of the LibGit2Sharp library. /// - public virtual string InformationalVersion - { - get - { - var attribute = (AssemblyInformationalVersionAttribute)assembly - .GetCustomAttributes(typeof(AssemblyInformationalVersionAttribute), false) - .Single(); - - return attribute.InformationalVersion; - } - } + public virtual string InformationalVersion => ThisAssembly.AssemblyInformationalVersion; /// /// Returns all the optional features that were compiled into @@ -46,32 +31,23 @@ public virtual string InformationalVersion /// A enumeration. public virtual BuiltInFeatures Features { - get - { - return Proxy.git_libgit2_features(); - } + get { return Proxy.git_libgit2_features(); } } /// /// Returns the SHA hash for the libgit2 library. /// - public virtual string LibGit2CommitSha - { - get - { - return ReadContentFromResource(assembly, "libgit2_hash.txt").Substring(0, 7); - } - } + public virtual string LibGit2CommitSha => RetrieveAbbrevShaFrom(AssemblyCommitIds.LibGit2CommitSha); /// /// Returns the SHA hash for the LibGit2Sharp library. /// - public virtual string LibGit2SharpCommitSha + public virtual string LibGit2SharpCommitSha => RetrieveAbbrevShaFrom(AssemblyCommitIds.LibGit2SharpCommitSha); + + private string RetrieveAbbrevShaFrom(string sha) { - get - { - return ReadContentFromResource(assembly, "libgit2sharp_hash.txt").Substring(0, 7); - } + var index = sha.Length > 7 ? 7 : sha.Length; + return sha.Substring(0, index); } /// @@ -79,7 +55,7 @@ public virtual string LibGit2SharpCommitSha /// /// /// The format of the version number is as follows: - /// Major.Minor.Patch-LibGit2Sharp_abbrev_hash-libgit2_abbrev_hash (x86|amd64 - features) + /// Major.Minor.Patch[-previewTag]+{LibGit2Sharp_abbrev_hash}.libgit2-{libgit2_abbrev_hash} (x86|x64 - features) /// /// public override string ToString() @@ -91,23 +67,11 @@ private string RetrieveVersion() { string features = Features.ToString(); - return string.Format( - CultureInfo.InvariantCulture, - "{0}-{1}-{2} ({3} - {4})", - InformationalVersion, - LibGit2SharpCommitSha, - LibGit2CommitSha, - Platform.ProcessorArchitecture, - features); - } - - private string ReadContentFromResource(Assembly assembly, string partialResourceName) - { - string name = string.Format(CultureInfo.InvariantCulture, "LibGit2Sharp.{0}", partialResourceName); - using (var sr = new StreamReader(assembly.GetManifestResourceStream(name))) - { - return sr.ReadLine(); - } + return string.Format(CultureInfo.InvariantCulture, + "{0} ({1} - {2})", + InformationalVersion, + Platform.ProcessorArchitecture, + features); } } } diff --git a/LibGit2Sharp/Worktree.cs b/LibGit2Sharp/Worktree.cs new file mode 100644 index 000000000..13fea072b --- /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..9822e882c --- /dev/null +++ b/LibGit2Sharp/WorktreeCollection.cs @@ -0,0 +1,196 @@ +using LibGit2Sharp.Core; +using LibGit2Sharp.Core.Handles; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; + +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))); + } + } + + /// + /// + /// + /// + /// + /// + /// + /// + 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; + } + + git_worktree_add_options 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]; + } + + /// + /// + /// + /// + /// + /// + public virtual Worktree Add(string name, string path, bool isLocked) + { + git_worktree_add_options 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/libgit2sharp_hash.txt b/LibGit2Sharp/libgit2sharp_hash.txt deleted file mode 100644 index 354664565..000000000 --- a/LibGit2Sharp/libgit2sharp_hash.txt +++ /dev/null @@ -1 +0,0 @@ -unknown diff --git a/LibGit2Sharp/packages.config b/LibGit2Sharp/packages.config deleted file mode 100644 index 6565d1772..000000000 --- a/LibGit2Sharp/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/LibGit2SharpDev.sln b/LibGit2SharpDev.sln new file mode 100644 index 000000000..a81b0ce37 --- /dev/null +++ b/LibGit2SharpDev.sln @@ -0,0 +1,52 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28803.202 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibGit2Sharp", "LibGit2Sharp\LibGit2Sharp.csproj", "{EE6ED99F-CB12-4683-B055-D28FC7357A34}" +EndProject +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 + version.json = version.json + 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 + 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}.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}.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(ExtensibilityGlobals) = postSolution + SolutionGuid = {9BD5F77D-E47D-4621-9AA0-8598766902B9} + EndGlobalSection +EndGlobal diff --git a/NativeLibraryLoadTestApp/Directory.Build.props b/NativeLibraryLoadTestApp/Directory.Build.props new file mode 100644 index 000000000..c55b35c72 --- /dev/null +++ b/NativeLibraryLoadTestApp/Directory.Build.props @@ -0,0 +1,7 @@ + + + + false + + + diff --git a/NativeLibraryLoadTestApp/TestApp.cs b/NativeLibraryLoadTestApp/TestApp.cs new file mode 100644 index 000000000..234169a75 --- /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, (IntPtr.Size == 4) ? "x86" : "x64", 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 b3dd52d5f..c67e6ec8e 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,28 @@ # LibGit2Sharp +[![master azurepipelines][master-azurepipelines-badge]][master-azurepipelines] +[![master win][master-win-badge]][master-win] +[![master nix][master-nix-badge]][master-nix] +[![coverity][coverity-badge]][coverity-project] + +[master-azurepipelines-badge]: https://dev.azure.com/libgit2sharp/libgit2sharp/_apis/build/status/libgit2sharp?branchName=master +[master-azurepipelines]: https://dev.azure.com/libgit2sharp/libgit2sharp/_build/latest?definitionId=1 +[master-win-badge]: https://ci.appveyor.com/api/projects/status/8qxcoqdo9kp7x2w9/branch/master?svg=true +[master-win]: https://ci.appveyor.com/project/libgit2/libgit2sharp/branch/master +[master-nix-badge]: https://travis-ci.org/libgit2/libgit2sharp.svg?branch=master +[master-nix]: https://travis-ci.org/libgit2/libgit2sharp/branches + +[coverity-project]: https://scan.coverity.com/projects/2088 +[coverity-badge]: https://scan.coverity.com/projects/2088/badge.svg + **LibGit2Sharp brings all the might and speed of [libgit2][libgit2], a native Git implementation, to the managed world of .NET and Mono.** [libgit2]: http://libgit2.github.com/ ## Prerequisites - - **Windows:** .NET 4.0+ - - **Linux/Mac OS X:** Mono 3.6+ + - **Windows:** .NET 4.6.1+ + - **Linux/Mac OS X:** Mono 5.4+ ## Online resources @@ -27,26 +42,6 @@ [tracker]: https://github.com/libgit2/libgit2sharp/issues [twitter]: http://twitter.com/libgit2sharp -## Current project build status -The CI builds are generously hosted and run on the [Travis][travis] and [AppVeyor][appveyor] infrastructures. - -| | Windows (x86/amd64) | Linux/Mac OS X | -| :------ | :------: | :------: | -| **master** | [![master win][master-win-badge]][master-win] | [![master nix][master-nix-badge]][master-nix] | -| **vNext** | [![vNext win][vNext-win-badge]][vNext-win] | [![vNext nix][vNext-nix-badge]][vNext-nix] | - - - [travis]: http://travis-ci.org/ - [appveyor]: http://appveyor.com/ - [master-win-badge]: https://ci.appveyor.com/api/projects/status/8qxcoqdo9kp7x2w9/branch/master?svg=true - [master-win]: https://ci.appveyor.com/project/libgit2/libgit2sharp/branch/master - [master-nix-badge]: https://travis-ci.org/libgit2/libgit2sharp.svg?branch=master - [master-nix]: https://travis-ci.org/libgit2/libgit2sharp/branches - [vNext-win-badge]: https://ci.appveyor.com/api/projects/status/8qxcoqdo9kp7x2w9/branch/vNext?svg=true - [vNext-win]: https://ci.appveyor.com/project/libgit2/libgit2sharp/branch/vNext - [vNext-nix-badge]: https://travis-ci.org/libgit2/libgit2sharp.svg?branch=vNext - [vNext-nix]: https://travis-ci.org/libgit2/libgit2sharp/branches - ## Quick contributing guide - Fork and clone locally @@ -58,7 +53,7 @@ More thorough information available in the [wiki][wiki]. [wiki]: https://github.com/libgit2/libgit2sharp/wiki ## Optimizing unit testing -LibGit2Sharp strives to have comprehensive and robust unit test suite to insure the quality of the software and to assist new contributors and users who can use the tests as sample to jump start development. There are over one-thousand unit-tests for LibGit2Sharp, this number will only grow as functionality is added. +LibGit2Sharp strives to have 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 sample to jump start development. There are over one thousand unit-tests for LibGit2Sharp, this number will only grow as functionality is added. You can do a few things to optimize running unit-tests on Windows: @@ -66,7 +61,7 @@ You can do a few things to optimize running unit-tests on Windows: * 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 256M -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. + * 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. ## Authors diff --git a/Targets/CodeGenerator.targets b/Targets/CodeGenerator.targets new file mode 100644 index 000000000..9d4176def --- /dev/null +++ b/Targets/CodeGenerator.targets @@ -0,0 +1,86 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + + + + + $(IntermediateOutputPath)UniqueIdentifier.g.cs + $(IntermediateOutputPath)AssemblyCommitIds.g.cs + + + + + + + + $([System.Guid]::NewGuid()) + $(GitCommitId) + + namespace LibGit2Sharp.Core + { + internal static class UniqueId + { + public const string UniqueIdentifier = "$(UniqueIdentifier)"%3b + } + } + + + + + + + + + + + + + + + + + + + + + libgit2-$(libgit2_hash.Substring(0,7)) + + + + + + + + + + + + + + unknown + $(GitCommitId) + + 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..0c4dbaa3f --- /dev/null +++ b/Targets/GenerateNativeDllName.targets @@ -0,0 +1,33 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + + + + + $(IntermediateOutputPath)NativeDllName.g.cs + + + + + + + namespace LibGit2Sharp.Core + { + internal static class NativeDllName + { + public const string Name = "$(libgit2_filename)"%3b + } + } + + + + + + + + + + + diff --git a/appveyor.yml b/appveyor.yml index 642639093..6eeeedba4 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,78 +1,4 @@ -version: '{build}' - +# Disable AppVeyor branches: - only: - - master - - vNext - -skip_tags: true - -clone_folder: C:\projects\libgit2sharp - -environment: - version : 0.22.0 - matrix: - - xunit_runner: xunit.console.clr4.exe - Arch: 64 - - xunit_runner: xunit.console.clr4.x86.exe - Arch: 32 - -matrix: - fast_finish: true - -install: -- ps: | - Write-Host "Commit being built = $($Env:APPVEYOR_REPO_COMMIT)" - Write-Host "Current build version = $($Env:VERSION)" - Write-Host "Target branch = $($Env:APPVEYOR_REPO_BRANCH)" - Write-Host "Is a Pull Request = $($Env:APPVEYOR_PULL_REQUEST_NUMBER -ne $null)" - $BuildDate = (Get-Date).ToUniversalTime().ToString("yyyyMMddHHmmss") - Write-Host "Build UTC date = $BuildDate" - $VersionSuffix = "" - If ($Env:APPVEYOR_REPO_BRANCH -ne "master") - { - $VersionSuffix = "-pre$BuildDate" - } - $Version = "$($Env:VERSION)$($VersionSuffix)" - $Env:ASSEMBLY_INFORMATIONAL_VERSION = $Version - Write-Host "Assembly informational version = $($Env:ASSEMBLY_INFORMATIONAL_VERSION)" - $ShouldPublishNugetArtifact = "$($env:APPVEYOR_PULL_REQUEST_NUMBER -eq $null)" - $Env:SHOULD_PUBLISH_NUGET_ARTIFACT = $ShouldPublishNugetArtifact - Write-Host "Should publish Nuget artifact = $($Env:SHOULD_PUBLISH_NUGET_ARTIFACT)" - cinst sourcelink -y - -assembly_info: - patch: true - file: LibGit2Sharp\Properties\AssemblyInfo.cs - assembly_version: '$(VERSION)' - assembly_file_version: '$(VERSION)' - assembly_informational_version: '$(ASSEMBLY_INFORMATIONAL_VERSION)' - -cache: - - packages - -before_build: -- nuget restore "%APPVEYOR_BUILD_FOLDER%\LibGit2Sharp.sln" - -build_script: -- msbuild "%APPVEYOR_BUILD_FOLDER%\LibGit2Sharp.sln" /verbosity:normal /p:Configuration=Release /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" /property:ExtraDefine="LEAKS_IDENTIFYING" - -test_script: -- '%xunit_runner% "%APPVEYOR_BUILD_FOLDER%\LibGit2Sharp.Tests\bin\Release\LibGit2Sharp.Tests.dll" /appveyor' -- IF %ERRORLEVEL% NEQ 0 EXIT /B %ERRORLEVEL% - -on_success: -- ps: | - & "$env:APPVEYOR_BUILD_FOLDER\nuget.package\BuildNugetPackage.ps1" -commitSha "$env:APPVEYOR_REPO_COMMIT" -postBuild { sourcelink index -pr LibGit2Sharp.csproj -pp Configuration Release -nf Core\NativeDllName.cs -nf Core\UniqueIdentifier.cs -nf Properties\AssemblyInfo.cs -r .. -u 'https://raw.githubusercontent.com/libgit2/libgit2sharp/{0}/%var2%' } - Add-Type -Path "$env:APPVEYOR_BUILD_FOLDER\LibGit2Sharp\bin\Release\LibGit2Sharp.dll" - Write-Host "LibGit2Sharp version = $([LibGit2Sharp.GlobalSettings]::Version)" -ForegroundColor "Magenta" - If ($Env:SHOULD_PUBLISH_NUGET_ARTIFACT -eq $True) - { - Get-ChildItem "$env:APPVEYOR_BUILD_FOLDER\LibGit2sharp\*.nupkg" | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name } - } - -notifications: -- provider: Email - to: - - emeric.fermas@gmail.com - on_build_status_changed: true + only: + - NOTTHISONE diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 000000000..c25545a56 --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,12 @@ +trigger: +- master +- maint/* + +variables: + TreatWarningsAsErrors: true + DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true + BuildConfiguration: Release + NUGET_PACKAGES: $(Agent.TempDirectory)/.nuget/packages + +jobs: +- template: azure-pipelines/build.yml diff --git a/azure-pipelines/Set-EnvVars.ps1 b/azure-pipelines/Set-EnvVars.ps1 new file mode 100644 index 000000000..907659a7b --- /dev/null +++ b/azure-pipelines/Set-EnvVars.ps1 @@ -0,0 +1,79 @@ +<# +.SYNOPSIS + Set environment variables in the environment. + Azure Pipeline and CMD environments are considered. +.PARAMETER Variables + A hashtable of variables to be set. +.OUTPUTS + A boolean indicating whether the environment variables can be expected to propagate to the caller's environment. +#> +[CmdletBinding(SupportsShouldProcess=$true)] +Param( + [Parameter(Mandatory=$true, Position=1)] + $Variables, + [string[]]$PrependPath +) + +if ($Variables.Count -eq 0) { + return $true +} + +$cmdInstructions = !$env:TF_BUILD -and !$env:GITHUB_ACTIONS -and $env:PS1UnderCmd -eq '1' +if ($cmdInstructions) { + Write-Warning "Environment variables have been set that will be lost because you're running under cmd.exe" + Write-Host "Environment variables that must be set manually:" -ForegroundColor Blue +} else { + Write-Host "Environment variables set:" -ForegroundColor Blue + $envVars + if ($PrependPath) { + Write-Host "Paths prepended to PATH: $PrependPath" + } +} + +if ($env:TF_BUILD) { + Write-Host "Azure Pipelines detected. Logging commands will be used to propagate environment variables and prepend path." +} + +if ($env:GITHUB_ACTIONS) { + Write-Host "GitHub Actions detected. Logging commands will be used to propagate environment variables and prepend path." +} + +$Variables.GetEnumerator() |% { + Set-Item -Path env:$($_.Key) -Value $_.Value + + # If we're running in a cloud CI, set these environment variables so they propagate. + if ($env:TF_BUILD) { + Write-Host "##vso[task.setvariable variable=$($_.Key);]$($_.Value)" + } + if ($env:GITHUB_ACTIONS) { + Write-Host "::set-env name=$($_.Key)::$($_.Value)" + } + + if ($cmdInstructions) { + Write-Host "SET $($_.Key)=$($_.Value)" + } +} + +$pathDelimiter = ';' +if ($IsMacOS -or $IsLinux) { + $pathDelimiter = ':' +} + +if ($PrependPath) { + $PrependPath |% { + $newPathValue = "$_$pathDelimiter$env:PATH" + Set-Item -Path env:PATH -Value $newPathValue + if ($cmdInstructions) { + Write-Host "SET PATH=$newPathValue" + } + + if ($env:TF_BUILD) { + Write-Host "##vso[task.prependpath]$_" + } + if ($env:GITHUB_ACTIONS) { + Write-Host "::add-path::$_" + } + } +} + +return !$cmdInstructions diff --git a/azure-pipelines/artifacts/_all.ps1 b/azure-pipelines/artifacts/_all.ps1 new file mode 100644 index 000000000..6f62be5c3 --- /dev/null +++ b/azure-pipelines/artifacts/_all.ps1 @@ -0,0 +1,50 @@ +# This script returns all the artifacts that should be collected after a build. +# +# Each powershell artifact is expressed as an object with these properties: +# Source - the full path to the source file +# ArtifactName - the name of the artifact to upload to +# ContainerFolder - the relative path within the artifact in which the file should appear +# +# Each artifact aggregating .ps1 script should return a hashtable: +# Key = path to the directory from which relative paths within the artifact should be calculated +# Value = an array of paths (absolute or relative to the BaseDirectory) to files to include in the artifact. +# FileInfo objects are also allowed. + +$RepoRoot = [System.IO.Path]::GetFullPath("$PSScriptRoot\..\..") + +Function EnsureTrailingSlash($path) { + if ($path.length -gt 0 -and !$path.EndsWith('\') -and !$path.EndsWith('/')) { + $path = $path + [IO.Path]::DirectorySeparatorChar + } + + $path.Replace('\', [IO.Path]::DirectorySeparatorChar) +} + +Get-ChildItem "$PSScriptRoot\*.ps1" -Exclude "_*" -Recurse |% { + $ArtifactName = $_.BaseName + + $fileGroups = & $_ + if (!$fileGroups -or $fileGroups.Count -eq 0) { + Write-Warning "No files found for the `"$ArtifactName`" artifact." + } else { + $fileGroups.GetEnumerator() | % { + $BaseDirectory = New-Object Uri ((EnsureTrailingSlash $_.Key), [UriKind]::Absolute) + $_.Value | % { + if ($_.GetType() -eq [IO.FileInfo] -or $_.GetType() -eq [IO.DirectoryInfo]) { + $_ = $_.FullName + } + + $artifact = New-Object -TypeName PSObject + Add-Member -InputObject $artifact -MemberType NoteProperty -Name ArtifactName -Value $ArtifactName + + $SourceFullPath = New-Object Uri ($BaseDirectory, $_) + Add-Member -InputObject $artifact -MemberType NoteProperty -Name Source -Value $SourceFullPath.LocalPath + + $RelativePath = [Uri]::UnescapeDataString($BaseDirectory.MakeRelative($SourceFullPath)) + Add-Member -InputObject $artifact -MemberType NoteProperty -Name ContainerFolder -Value (Split-Path $RelativePath) + + Write-Output $artifact + } + } + } +} diff --git a/azure-pipelines/artifacts/_pipelines.ps1 b/azure-pipelines/artifacts/_pipelines.ps1 new file mode 100644 index 000000000..5bca7c6c1 --- /dev/null +++ b/azure-pipelines/artifacts/_pipelines.ps1 @@ -0,0 +1,10 @@ +# This script translates all the artifacts described by _all.ps1 +# into commands that instruct Azure Pipelines to actually collect those artifacts. + +param ( + [string]$ArtifactNameSuffix +) + +& "$PSScriptRoot/_stage_all.ps1" -ArtifactNameSuffix $ArtifactNameSuffix |% { + Write-Host "##vso[artifact.upload containerfolder=$($_.Name);artifactname=$($_.Name);]$($_.Path)" +} diff --git a/azure-pipelines/artifacts/_stage_all.ps1 b/azure-pipelines/artifacts/_stage_all.ps1 new file mode 100644 index 000000000..a05db5292 --- /dev/null +++ b/azure-pipelines/artifacts/_stage_all.ps1 @@ -0,0 +1,59 @@ +# This script links all the artifacts described by _all.ps1 +# into a staging directory, reading for uploading to a cloud build artifact store. +# It returns a sequence of objects with Name and Path properties. + +param ( + [string]$ArtifactNameSuffix +) + +$RepoRoot = [System.IO.Path]::GetFullPath((Join-Path $PSScriptRoot (Join-Path .. ..))) +if ($env:BUILD_ARTIFACTSTAGINGDIRECTORY) { + $ArtifactStagingFolder = $env:BUILD_ARTIFACTSTAGINGDIRECTORY +} else { + $ArtifactStagingFolder = Join-Path $RepoRoot (Join-Path obj _artifacts) + if (Test-Path $ArtifactStagingFolder) { + Remove-Item $ArtifactStagingFolder -Recurse -Force + } +} + +function Create-SymbolicLink { + param ( + $Link, + $Target + ) + + if ($Link -eq $Target) { + return + } + + if (Test-Path $Link) { Remove-Item $Link } + $LinkContainer = Split-Path $Link -Parent + if (!(Test-Path $LinkContainer)) { mkdir $LinkContainer } + Write-Verbose "Linking $Link to $Target" + if ($IsMacOS -or $IsLinux) { + ln $Target $Link | Out-Null + } else { + cmd /c mklink $Link $Target | Out-Null + } +} + +# Stage all artifacts +$Artifacts = & "$PSScriptRoot\_all.ps1" +$Artifacts |% { + $DestinationFolder = (Join-Path (Join-Path $ArtifactStagingFolder "$($_.ArtifactName)$ArtifactNameSuffix") $_.ContainerFolder).TrimEnd('\') + $Name = "$(Split-Path $_.Source -Leaf)" + + #Write-Host "$($_.Source) -> $($_.ArtifactName)\$($_.ContainerFolder)" -ForegroundColor Yellow + + if (-not (Test-Path $DestinationFolder)) { New-Item -ItemType Directory -Path $DestinationFolder | Out-Null } + if (Test-Path -PathType Leaf $_.Source) { # skip folders + Create-SymbolicLink -Link (Join-Path $DestinationFolder $Name) -Target $_.Source + } +} + +$Artifacts |% { "$($_.ArtifactName)$ArtifactNameSuffix" } | Get-Unique |% { + $artifact = New-Object -TypeName PSObject + Add-Member -InputObject $artifact -MemberType NoteProperty -Name Name -Value $_ + Add-Member -InputObject $artifact -MemberType NoteProperty -Name Path -Value (Join-Path $ArtifactStagingFolder $_) + Write-Output $artifact +} diff --git a/azure-pipelines/artifacts/build_logs.ps1 b/azure-pipelines/artifacts/build_logs.ps1 new file mode 100644 index 000000000..b55ba48f3 --- /dev/null +++ b/azure-pipelines/artifacts/build_logs.ps1 @@ -0,0 +1,12 @@ +if ($env:BUILD_ARTIFACTSTAGINGDIRECTORY) { + $artifactsRoot = $env:BUILD_ARTIFACTSTAGINGDIRECTORY +} else { + $RepoRoot = [System.IO.Path]::GetFullPath("$PSScriptRoot\..\..") + $artifactsRoot = "$RepoRoot\bin" +} + +if (!(Test-Path $artifactsRoot/build_logs)) { return } + +@{ + "$artifactsRoot/build_logs" = (Get-ChildItem -Recurse "$artifactsRoot/build_logs") +} diff --git a/azure-pipelines/artifacts/coverageResults.ps1 b/azure-pipelines/artifacts/coverageResults.ps1 new file mode 100644 index 000000000..7d1e9a35f --- /dev/null +++ b/azure-pipelines/artifacts/coverageResults.ps1 @@ -0,0 +1,22 @@ +$RepoRoot = [System.IO.Path]::GetFullPath("$PSScriptRoot\..\..") + +# Prepare code coverage reports for merging on another machine +if ($env:SYSTEM_DEFAULTWORKINGDIRECTORY) { + Write-Host "Substituting $env:SYSTEM_DEFAULTWORKINGDIRECTORY with `"{reporoot}`"" + $reports = Get-ChildItem "$RepoRoot/bin/coverage.cobertura.xml" -Recurse + $reports |% { + $content = Get-Content -Path $_ |% { $_ -Replace [regex]::Escape($env:SYSTEM_DEFAULTWORKINGDIRECTORY), "{reporoot}" } + Set-Content -Path $_ -Value $content -Encoding UTF8 + } +} else { + Write-Warning "coverageResults: Azure Pipelines not detected. Machine-neutral token replacement skipped." +} + +if (!((Test-Path $RepoRoot\bin) -and (Test-Path $RepoRoot\obj))) { return } + +@{ + $RepoRoot = ( + @(Get-ChildItem "$RepoRoot\bin\coverage.cobertura.xml" -Recurse) + + (Get-ChildItem "$RepoRoot\obj\*.cs" -Recurse) + ); +} diff --git a/azure-pipelines/artifacts/deployables.ps1 b/azure-pipelines/artifacts/deployables.ps1 new file mode 100644 index 000000000..94c48cdd9 --- /dev/null +++ b/azure-pipelines/artifacts/deployables.ps1 @@ -0,0 +1,13 @@ +$RepoRoot = [System.IO.Path]::GetFullPath("$PSScriptRoot\..\..") +$BuildConfiguration = $env:BUILDCONFIGURATION +if (!$BuildConfiguration) { + $BuildConfiguration = 'Debug' +} + +$PackagesRoot = "$RepoRoot/bin/Packages/$BuildConfiguration" + +if (!(Test-Path $PackagesRoot)) { return } + +@{ + "$PackagesRoot" = (Get-ChildItem $PackagesRoot -Recurse) +} diff --git a/azure-pipelines/artifacts/projectAssetsJson.ps1 b/azure-pipelines/artifacts/projectAssetsJson.ps1 new file mode 100644 index 000000000..d2e85ffbe --- /dev/null +++ b/azure-pipelines/artifacts/projectAssetsJson.ps1 @@ -0,0 +1,9 @@ +$ObjRoot = [System.IO.Path]::GetFullPath("$PSScriptRoot\..\..\obj") + +if (!(Test-Path $ObjRoot)) { return } + +@{ + "$ObjRoot" = ( + (Get-ChildItem "$ObjRoot\project.assets.json" -Recurse) + ); +} diff --git a/azure-pipelines/build.yml b/azure-pipelines/build.yml new file mode 100644 index 000000000..a73ccda4c --- /dev/null +++ b/azure-pipelines/build.yml @@ -0,0 +1,50 @@ +parameters: + windowsPool: Hosted Windows 2019 with VS2019 + +jobs: +- job: Windows + pool: ${{ parameters.windowsPool }} + steps: + - template: install-dependencies.yml + - template: dotnet.yml + +- job: Linux + pool: + vmImage: Ubuntu 18.04 + steps: + - template: install-dependencies.yml + - template: dotnet.yml + +- job: macOS + pool: + vmImage: macOS-10.15 + steps: + - template: install-dependencies.yml + - template: dotnet.yml + +- job: WrapUp + dependsOn: + - Windows + - Linux + - macOS + pool: + vmImage: Ubuntu 18.04 + condition: succeededOrFailed() + steps: + - template: install-dependencies.yml + parameters: + initArgs: -NoRestore + - template: publish-codecoverage.yml + - template: publish-deployables.yml + +- job: leak_check + pool: + vmImage: Ubuntu 18.04 + steps: + - template: install-dependencies.yml + - task: DotNetCoreCLI@2 + displayName: dotnet test -f netcoreapp2.1 + inputs: + command: test + arguments: --no-restore -c $(BuildConfiguration) -f netcoreapp2.1 --filter "TestCategory!=FailsInCloudTest" -v m /p:ExtraDefine=LEAKS_IDENTIFYING + testRunTitle: netcoreapp2.1-$(Agent.JobName) diff --git a/azure-pipelines/dotnet.yml b/azure-pipelines/dotnet.yml new file mode 100644 index 000000000..8c9f5f909 --- /dev/null +++ b/azure-pipelines/dotnet.yml @@ -0,0 +1,42 @@ +steps: + +- script: dotnet build --no-restore -c $(BuildConfiguration) /v:m /bl:"$(Build.ArtifactStagingDirectory)/build_logs/build.binlog" + displayName: dotnet build + +- script: dotnet pack --no-build -c $(BuildConfiguration) /v:m /bl:"$(Build.ArtifactStagingDirectory)/build_logs/pack.binlog" + displayName: dotnet pack + +- task: DotNetCoreCLI@2 + displayName: dotnet test -f net46 (w/ coverage) + inputs: + command: test + arguments: --no-build -c $(BuildConfiguration) -f net46 --filter "TestCategory!=FailsInCloudTest & TestCategory!=FailsWhileInstrumented" -v n /p:CollectCoverage=true + testRunTitle: net46-$(Agent.JobName) + condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT')) + +- task: DotNetCoreCLI@2 + displayName: dotnet test -f net46 (w/o coverage) + inputs: + command: test + arguments: --no-build -c $(BuildConfiguration) -f net46 --filter "TestCategory!=FailsInCloudTest & TestCategory=FailsWhileInstrumented" -v n + testRunTitle: net46-$(Agent.JobName)-nocoverage + condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT')) + +- task: DotNetCoreCLI@2 + displayName: dotnet test -f netcoreapp2.1 + inputs: + command: test + arguments: --no-build -c $(BuildConfiguration) -f netcoreapp2.1 --filter "TestCategory!=FailsInCloudTest" -v n /p:CollectCoverage=true + testRunTitle: netcoreapp2.1-$(Agent.JobName) + +- task: PowerShell@2 + inputs: + filePath: azure-pipelines/artifacts/_pipelines.ps1 + arguments: -ArtifactNameSuffix "-$(Agent.JobName)" + displayName: Publish artifacts + condition: succeededOrFailed() + +- bash: bash <(curl -s https://codecov.io/bash) + displayName: Publish code coverage results to codecov.io + timeoutInMinutes: 3 + continueOnError: true diff --git a/azure-pipelines/install-dependencies.yml b/azure-pipelines/install-dependencies.yml new file mode 100644 index 000000000..5b008c6e8 --- /dev/null +++ b/azure-pipelines/install-dependencies.yml @@ -0,0 +1,9 @@ +parameters: + initArgs: + +steps: + +- powershell: | + .\init.ps1 -AccessToken '$(System.AccessToken)' ${{ parameters['initArgs'] }} -UpgradePrerequisites + dotnet --info + displayName: Install prerequisites diff --git a/azure-pipelines/publish-codecoverage.yml b/azure-pipelines/publish-codecoverage.yml new file mode 100644 index 000000000..59dba9d40 --- /dev/null +++ b/azure-pipelines/publish-codecoverage.yml @@ -0,0 +1,31 @@ +steps: +- download: current + artifact: coverageResults-Windows + displayName: Download Windows code coverage results + continueOnError: true +- download: current + artifact: coverageResults-Linux + displayName: Download Linux code coverage results + continueOnError: true +- download: current + artifact: coverageResults-macOS + displayName: Download macOS code coverage results + continueOnError: true +- powershell: | + dotnet tool install --tool-path obj dotnet-reportgenerator-globaltool --version 4.2.2 + Copy-Item -Recurse $(Pipeline.Workspace)/coverageResults-Windows/obj/* $(System.DefaultWorkingDirectory)/obj + Write-Host "Substituting {reporoot} with $(System.DefaultWorkingDirectory)" + $reports = Get-ChildItem -Recurse "$(Pipeline.Workspace)/coverage.cobertura.xml" + $reports |% { + $content = Get-Content -Path $_ |% { $_.Replace("{reporoot}", "$(System.DefaultWorkingDirectory)") } + Set-Content -Path $_ -Value $content -Encoding UTF8 + } + $Inputs = [string]::join(';', ($reports |% { Resolve-Path -relative $_ })) + obj/reportgenerator -reports:"$Inputs" -targetdir:coveragereport -reporttypes:Cobertura + displayName: Merge coverage +- task: PublishCodeCoverageResults@1 + displayName: Publish code coverage results to Azure DevOps + inputs: + codeCoverageTool: cobertura + summaryFileLocation: 'coveragereport/Cobertura.xml' + failIfCoverageEmpty: true diff --git a/azure-pipelines/publish-deployables.yml b/azure-pipelines/publish-deployables.yml new file mode 100644 index 000000000..a89f389fd --- /dev/null +++ b/azure-pipelines/publish-deployables.yml @@ -0,0 +1,14 @@ +steps: +- download: current + displayName: Download deployables + artifact: deployables-Windows + +- task: NuGetCommand@2 + displayName: Push packages to CI feed + inputs: + command: push + packagesToPush: $(Pipeline.Workspace)/deployables-Windows/*.nupkg + nuGetFeedType: internal + publishVstsFeed: $(ci_feed) + allowPackageConflicts: true + condition: and(succeeded(), ne(variables['ci_feed'], ''), ne(variables['Build.Reason'], 'PullRequest')) diff --git a/azure-pipelines/variables/DotNetSdkVersion.ps1 b/azure-pipelines/variables/DotNetSdkVersion.ps1 new file mode 100644 index 000000000..b213fbc27 --- /dev/null +++ b/azure-pipelines/variables/DotNetSdkVersion.ps1 @@ -0,0 +1,2 @@ +$globalJson = Get-Content -Path "$PSScriptRoot\..\..\global.json" | ConvertFrom-Json +$globalJson.sdk.version diff --git a/build.libgit2sharp.cmd b/build.libgit2sharp.cmd deleted file mode 100644 index 6cd7d72cf..000000000 --- a/build.libgit2sharp.cmd +++ /dev/null @@ -1,35 +0,0 @@ -@ECHO OFF - -REM Sample usages: -REM -REM Building and running tests -REM - build.libgit2sharp.cmd -REM -REM Building, running tests and embedding the libgit2sharp commit sha -REM - build.libgit2sharp.cmd "6a6eb81272876fd63555165beef44de2aaa78a14" -REM -REM Building and identifying potential leaks while running tests -REM - build.libgit2sharp.cmd "unknown" "LEAKS_IDENTIFYING" - - -SETLOCAL - -SET BASEDIR=%~dp0 -SET FrameworkVersion=v4.0.30319 -SET FrameworkDir=%SystemRoot%\Microsoft.NET\Framework - -if exist "%SystemRoot%\Microsoft.NET\Framework64" ( - SET FrameworkDir=%SystemRoot%\Microsoft.NET\Framework64 -) - -ECHO ON - -SET CommitSha=%~1 -SET ExtraDefine=%~2 - -"%BASEDIR%Lib/NuGet/NuGet.exe" restore "%BASEDIR%LibGit2Sharp.sln" -"%FrameworkDir%\%FrameworkVersion%\msbuild.exe" "%BASEDIR%CI\build.msbuild" /property:CommitSha=%CommitSha% /property:ExtraDefine="%ExtraDefine%" - -ENDLOCAL - -EXIT /B %ERRORLEVEL% diff --git a/build.libgit2sharp.sh b/build.libgit2sharp.sh deleted file mode 100755 index acf425a29..000000000 --- a/build.libgit2sharp.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash -set -e - -EXTRADEFINE="$1" - -# Setting LD_LIBRARY_PATH to the current working directory is needed to run -# the tests successfully in linux. Without this, mono can't find libgit when -# the libgit2sharp assembly has been shadow copied. OS X includes the current -# working directory in its library search path, so it works without this value. -export LD_LIBRARY_PATH=. - -# Required for NuGet package restore to run. -mozroots --import --sync - -mono Lib/NuGet/NuGet.exe restore LibGit2Sharp.sln -xbuild CI/build.msbuild /target:Deploy /property:ExtraDefine="$EXTRADEFINE" - -exit $? diff --git a/global.json b/global.json new file mode 100644 index 000000000..f5efc4a53 --- /dev/null +++ b/global.json @@ -0,0 +1,5 @@ +{ + "sdk": { + "version": "3.1.401" + } +} diff --git a/init.cmd b/init.cmd new file mode 100644 index 000000000..970285c2f --- /dev/null +++ b/init.cmd @@ -0,0 +1,4 @@ +@echo off +SETLOCAL +set PS1UnderCmd=1 +powershell.exe -NoProfile -NoLogo -ExecutionPolicy bypass -Command "try { & '%~dpn0.ps1' %*; exit $LASTEXITCODE } catch { write-host $_; exit 1 }" diff --git a/init.ps1 b/init.ps1 new file mode 100644 index 000000000..907d85a5c --- /dev/null +++ b/init.ps1 @@ -0,0 +1,66 @@ +<# +.SYNOPSIS +Installs dependencies required to build and test the projects in this repository. +.DESCRIPTION +This MAY not require elevation, as the SDK and runtimes are installed to a per-user location, +unless the `-InstallLocality` switch is specified directing to a per-repo or per-machine location. +See detailed help on that switch for more information. +.PARAMETER InstallLocality +A value indicating whether dependencies should be installed locally to the repo or at a per-user location. +Per-user allows sharing the installed dependencies across repositories and allows use of a shared expanded package cache. +Visual Studio will only notice and use these SDKs/runtimes if VS is launched from the environment that runs this script. +Per-repo allows for high isolation, allowing for a more precise recreation of the environment within an Azure Pipelines build. +When using 'repo', environment variables are set to cause the locally installed dotnet SDK to be used. +Per-repo can lead to file locking issues when dotnet.exe is left running as a build server and can be mitigated by running `dotnet build-server shutdown`. +Per-machine requires elevation and will download and install all SDKs and runtimes to machine-wide locations so all applications can find it. +.PARAMETER NoPrerequisites +Skips the installation of prerequisite software (e.g. SDKs, tools). +.PARAMETER UpgradePrerequisites +Takes time to install prerequisites even if they are already present in case they need to be upgraded. +No effect if -NoPrerequisites is specified. +.PARAMETER NoRestore +Skips the package restore step. +.PARAMETER AccessToken +An optional access token for authenticating to Azure Artifacts authenticated feeds. +#> +[CmdletBinding(SupportsShouldProcess=$true)] +Param ( + [ValidateSet('repo','user','machine')] + [string]$InstallLocality='user', + [Parameter()] + [switch]$NoPrerequisites, + [Parameter()] + [switch]$UpgradePrerequisites, + [Parameter()] + [switch]$NoRestore, + [Parameter()] + [string]$AccessToken +) + +if (!$NoPrerequisites) { + & "$PSScriptRoot\tools\Install-DotNetSdk.ps1" -InstallLocality $InstallLocality +} + +# Workaround nuget credential provider bug that causes very unreliable package restores on Azure Pipelines +$env:NUGET_PLUGIN_HANDSHAKE_TIMEOUT_IN_SECONDS=20 +$env:NUGET_PLUGIN_REQUEST_TIMEOUT_IN_SECONDS=20 + +Push-Location $PSScriptRoot +try { + $HeaderColor = 'Green' + + if (!$NoRestore -and $PSCmdlet.ShouldProcess("NuGet packages", "Restore")) { + Write-Host "Restoring NuGet packages" -ForegroundColor $HeaderColor + dotnet restore + if ($lastexitcode -ne 0) { + throw "Failure while restoring packages." + } + } +} +catch { + Write-Error $error[0] + exit $lastexitcode +} +finally { + Pop-Location +} 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..19d85b78f --- /dev/null +++ b/nuget.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/nuget.package/BuildNugetPackage.ps1 b/nuget.package/BuildNugetPackage.ps1 deleted file mode 100644 index 6770c14fc..000000000 --- a/nuget.package/BuildNugetPackage.ps1 +++ /dev/null @@ -1,95 +0,0 @@ -<# -.SYNOPSIS - Generates the NuGet packages (including the symbols). - A clean build is performed the packaging. -.PARAMETER commitSha - The LibGit2Sharp commit sha that contains the version of the source code being packaged. -#> - -Param( - [Parameter(Mandatory=$true)] - [string]$commitSha, - [scriptblock]$postBuild -) - -$ErrorActionPreference = "Stop" -Set-StrictMode -Version Latest - -function Run-Command([scriptblock]$Command) { - $output = "" - - $exitCode = 0 - $global:lastexitcode = 0 - - & $Command - - if ($LastExitCode -ne 0) { - $exitCode = $LastExitCode - } elseif (!$?) { - $exitCode = 1 - } else { - return - } - - $error = "``$Command`` failed" - - if ($output) { - Write-Host -ForegroundColor "Red" $output - $error += ". See output above." - } - - Throw $error -} - -function Clean-OutputFolder($folder) { - - If (Test-Path $folder) { - Write-Host -ForegroundColor "Green" "Dropping `"$folder`" folder..." - - Run-Command { & Remove-Item -Recurse -Force "$folder" } - - Write-Host "Done." - } -} - -# From http://www.dougfinke.com/blog/index.php/2010/12/01/note-to-self-how-to-programmatically-get-the-msbuild-path-in-powershell/ - -Function Get-MSBuild { - $lib = [System.Runtime.InteropServices.RuntimeEnvironment] - $rtd = $lib::GetRuntimeDirectory() - Join-Path $rtd msbuild.exe -} - -################# - -$root = Split-Path -Parent -Path $MyInvocation.MyCommand.Definition -$projectPath = Join-Path $root "..\LibGit2Sharp" -$slnPath = Join-Path $projectPath "..\LibGit2Sharp.sln" - -Remove-Item (Join-Path $projectPath "*.nupkg") - -Clean-OutputFolder (Join-Path $projectPath "bin\") -Clean-OutputFolder (Join-Path $projectPath "obj\") - -# The nuspec file needs to be next to the csproj, so copy it there during the pack operation -Copy-Item (Join-Path $root "LibGit2Sharp.nuspec") $projectPath - -Push-Location $projectPath - -try { - Set-Content -Encoding ASCII $(Join-Path $projectPath "libgit2sharp_hash.txt") $commitSha - Run-Command { & "$(Join-Path $projectPath "..\Lib\NuGet\Nuget.exe")" Restore "$slnPath" } - Run-Command { & (Get-MSBuild) "$slnPath" "/verbosity:minimal" "/p:Configuration=Release" } - - If ($postBuild) { - Write-Host -ForegroundColor "Green" "Run post build script..." - Run-Command { & ($postBuild) } - } - - Run-Command { & "$(Join-Path $projectPath "..\Lib\NuGet\Nuget.exe")" Pack -Prop Configuration=Release } -} -finally { - Pop-Location - Remove-Item (Join-Path $projectPath "LibGit2Sharp.nuspec") - Set-Content -Encoding ASCII $(Join-Path $projectPath "libgit2sharp_hash.txt") "unknown" -} diff --git a/nuget.package/LibGit2Sharp.nuspec b/nuget.package/LibGit2Sharp.nuspec deleted file mode 100644 index c8381e2cf..000000000 --- a/nuget.package/LibGit2Sharp.nuspec +++ /dev/null @@ -1,22 +0,0 @@ - - - - $id$ - $version$ - $author$ - nulltoken - https://github.com/libgit2/libgit2sharp/raw/master/LICENSE.md - https://github.com/libgit2/libgit2sharp/ - false - $description$ - https://github.com/libgit2/libgit2sharp/blob/master/CHANGES.md#libgit2sharp-changes - https://github.com/libgit2/libgit2sharp/raw/master/square-logo.png - libgit2 git wrapper bindings API dvcs vcs - - - - - - - - diff --git a/tools/Install-DotNetSdk.ps1 b/tools/Install-DotNetSdk.ps1 new file mode 100644 index 000000000..10ed02b8b --- /dev/null +++ b/tools/Install-DotNetSdk.ps1 @@ -0,0 +1,160 @@ +<# +.SYNOPSIS +Installs the .NET SDK specified in the global.json file at the root of this repository, +along with supporting .NET Core runtimes used for testing. +.DESCRIPTION +This MAY not require elevation, as the SDK and runtimes are installed locally to this repo location, +unless `-InstallLocality machine` is specified. +.PARAMETER InstallLocality +A value indicating whether dependencies should be installed locally to the repo or at a per-user location. +Per-user allows sharing the installed dependencies across repositories and allows use of a shared expanded package cache. +Visual Studio will only notice and use these SDKs/runtimes if VS is launched from the environment that runs this script. +Per-repo allows for high isolation, allowing for a more precise recreation of the environment within an Azure Pipelines build. +When using 'repo', environment variables are set to cause the locally installed dotnet SDK to be used. +Per-repo can lead to file locking issues when dotnet.exe is left running as a build server and can be mitigated by running `dotnet build-server shutdown`. +Per-machine requires elevation and will download and install all SDKs and runtimes to machine-wide locations so all applications can find it. +#> +[CmdletBinding(SupportsShouldProcess=$true,ConfirmImpact='Medium')] +Param ( + [ValidateSet('repo','user','machine')] + [string]$InstallLocality='user' +) + +$DotNetInstallScriptRoot = "$PSScriptRoot/../obj/tools" +if (!(Test-Path $DotNetInstallScriptRoot)) { New-Item -ItemType Directory -Path $DotNetInstallScriptRoot | Out-Null } +$DotNetInstallScriptRoot = Resolve-Path $DotNetInstallScriptRoot + +# Look up actual required .NET Core SDK version from global.json +$sdkVersion = & "$PSScriptRoot/../azure-pipelines/variables/DotNetSdkVersion.ps1" + +# Search for all .NET Core runtime versions referenced from MSBuild projects and arrange to install them. +$runtimeVersions = @() +Get-ChildItem "$PSScriptRoot\..\*.*proj" -Recurse |% { + $projXml = [xml](Get-Content -Path $_) + $targetFrameworks = $projXml.Project.PropertyGroup.TargetFramework + if (!$targetFrameworks) { + $targetFrameworks = $projXml.Project.PropertyGroup.TargetFrameworks + if ($targetFrameworks) { + $targetFrameworks = $targetFrameworks -Split ';' + } + } + $targetFrameworks |? { $_ -match 'netcoreapp(\d+\.\d+)' } |% { + $runtimeVersions += $Matches[1] + } +} + +Function Get-FileFromWeb([Uri]$Uri, $OutDir) { + $OutFile = Join-Path $OutDir $Uri.Segments[-1] + if (!(Test-Path $OutFile)) { + Write-Verbose "Downloading $Uri..." + try { + (New-Object System.Net.WebClient).DownloadFile($Uri, $OutFile) + } finally { + # This try/finally causes the script to abort + } + } + + $OutFile +} + +Function Get-InstallerExe($Version, [switch]$Runtime) { + $sdkOrRuntime = 'Sdk' + if ($Runtime) { $sdkOrRuntime = 'Runtime' } + + # Get the latest/actual version for the specified one + if (([Version]$Version).Build -eq -1) { + $versionInfo = -Split (Invoke-WebRequest -Uri "https://dotnetcli.blob.core.windows.net/dotnet/$sdkOrRuntime/$Version/latest.version" -UseBasicParsing) + $Version = $versionInfo[-1] + } + + Get-FileFromWeb -Uri "https://dotnetcli.blob.core.windows.net/dotnet/$sdkOrRuntime/$Version/dotnet-$($sdkOrRuntime.ToLowerInvariant())-$Version-win-x64.exe" -OutDir "$DotNetInstallScriptRoot" +} + +Function Install-DotNet($Version, [switch]$Runtime) { + if ($Runtime) { $sdkSubstring = '' } else { $sdkSubstring = 'SDK ' } + Write-Host "Downloading .NET Core $sdkSubstring$Version..." + $Installer = Get-InstallerExe -Version $Version -Runtime:$Runtime + Write-Host "Installing .NET Core $sdkSubstring$Version..." + cmd /c start /wait $Installer /install /quiet + if ($LASTEXITCODE -ne 0) { + throw "Failure to install .NET Core SDK" + } +} + +if ($InstallLocality -eq 'machine') { + if ($IsMacOS -or $IsLinux) { + Write-Error "Installing the .NET Core SDK or runtime at a machine-wide location is only supported by this script on Windows." + exit 1 + } + + if ($PSCmdlet.ShouldProcess(".NET Core SDK $sdkVersion", "Install")) { + Install-DotNet -Version $sdkVersion + } + + $runtimeVersions |% { + if ($PSCmdlet.ShouldProcess(".NET Core runtime $_", "Install")) { + Install-DotNet -Version $_ -Runtime + } + } + + return +} + +$switches = @( + '-Architecture','x64' +) +$envVars = @{ + # For locally installed dotnet, skip first time experience which takes a long time + 'DOTNET_SKIP_FIRST_TIME_EXPERIENCE' = 'true'; +} + +if ($InstallLocality -eq 'repo') { + $DotNetInstallDir = "$DotNetInstallScriptRoot/.dotnet" +} elseif ($env:AGENT_TOOLSDIRECTORY) { + $DotNetInstallDir = "$env:AGENT_TOOLSDIRECTORY/dotnet" +} else { + $DotNetInstallDir = Join-Path $HOME .dotnet +} + +Write-Host "Installing .NET Core SDK and runtimes to $DotNetInstallDir" -ForegroundColor Blue + +if ($DotNetInstallDir) { + $switches += '-InstallDir',$DotNetInstallDir + $envVars['DOTNET_MULTILEVEL_LOOKUP'] = '0' + $envVars['DOTNET_ROOT'] = $DotNetInstallDir +} + +if ($IsMacOS -or $IsLinux) { + $DownloadUri = "https://dot.net/v1/dotnet-install.sh" + $DotNetInstallScriptPath = "$DotNetInstallScriptRoot/dotnet-install.sh" +} else { + $DownloadUri = "https://dot.net/v1/dotnet-install.ps1" + $DotNetInstallScriptPath = "$DotNetInstallScriptRoot/dotnet-install.ps1" +} + +if (-not (Test-Path $DotNetInstallScriptPath)) { + Invoke-WebRequest -Uri $DownloadUri -OutFile $DotNetInstallScriptPath -UseBasicParsing + if ($IsMacOS -or $IsLinux) { + chmod +x $DotNetInstallScriptPath + } +} + +if ($PSCmdlet.ShouldProcess(".NET Core SDK $sdkVersion", "Install")) { + Invoke-Expression -Command "$DotNetInstallScriptPath -Version $sdkVersion $switches" +} else { + Invoke-Expression -Command "$DotNetInstallScriptPath -Version $sdkVersion $switches -DryRun" +} + +$switches += '-Runtime','dotnet' + +$runtimeVersions | Get-Unique |% { + if ($PSCmdlet.ShouldProcess(".NET Core runtime $_", "Install")) { + Invoke-Expression -Command "$DotNetInstallScriptPath -Channel $_ $switches" + } else { + Invoke-Expression -Command "$DotNetInstallScriptPath -Channel $_ $switches -DryRun" + } +} + +if ($PSCmdlet.ShouldProcess("Set DOTNET environment variables to discover these installed runtimes?")) { + & "$PSScriptRoot/../azure-pipelines/Set-EnvVars.ps1" -Variables $envVars -PrependPath $DotNetInstallDir | Out-Null +} diff --git a/version.json b/version.json new file mode 100644 index 000000000..8d653a1cf --- /dev/null +++ b/version.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://raw.githubusercontent.com/AArnott/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", + "version": "0.27.0-preview", + "publicReleaseRefSpec": [ + "^refs/heads/dotdevelop$", // we release out of dotdevelop + "^refs/heads/maint/v\\d+(?:\\.\\d+)?$" // and maint/vNN branches + ], + "cloudBuild": { + "buildNumber": { + "enabled": false + } + } +}