├── .gitattributes ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── ManagedGitLib.ExtendedTests ├── ManagedGitLib.ExtendedTests.csproj ├── TestAgainstLibgit2MonoRepo.cs ├── TestAgainstRealMonoRepo.cs ├── TestUtils.cs ├── commit1-author-name └── commit2-message ├── ManagedGitLib.Tests ├── .gitattributes ├── 3596ffe59898103a2675547d4597e742e1f2389c.gz ├── DeltaStreamReaderTests.cs ├── GitAnnotatedTagReaderTests.cs ├── GitCommitReaderTests.cs ├── GitCommitTests.cs ├── GitObjectIdTests.cs ├── GitObjectStreamTests.cs ├── GitPackDeltafiedStreamTests.cs ├── GitPackIndexMappedReaderTests.cs ├── GitPackMemoryCacheTests.cs ├── GitPackTests.cs ├── GitRepositoryTests.cs ├── GitTreeStreamingReaderTests.cs ├── ManagedGitLib.Tests.csproj ├── RepoTestBase.Helpers.cs ├── RepoTestBase.cs ├── StreamExtensionsTests.cs ├── TestUtilities.cs ├── UnitTest1.cs ├── ZLibStreamTest.cs ├── commit-4497b0eaaa89abf0e6d70961ad5f04fd3a49cbc6 ├── commit-7507fb2859c12f6c561efc23d48dd1be0fc6cdee ├── commit-ab39e8acac105fa0db88514f259341c9f0201b22 ├── commit-ab39e8acac105fa0db88514f259341c9f0201b22-message ├── commit-d56dc3ed179053abef2097d1120b4507769bcf1a ├── commit-message ├── commit.delta ├── pack-7d6b2c56ffb97eedb92f4e28583c093f7ee4b3d9 .txt ├── pack-7d6b2c56ffb97eedb92f4e28583c093f7ee4b3d9.idx ├── pack-7d6b2c56ffb97eedb92f4e28583c093f7ee4b3d9.pack ├── tag-ceaa6f5b53565910b9b2dd211a9d8284445a9a9c ├── tree-bb36cf0ca445ccc8e5ce9cc88f7cf74128e96dc9 ├── tree-f914b48023c7c804a4f3be780d451f31aef74ac1 ├── tree.bin └── tree.delta ├── ManagedGitLib.sln ├── ManagedGitLib ├── DeltaInstruction.cs ├── DeltaInstructionType.cs ├── DeltaStreamReader.cs ├── FileHelpers.cs ├── GitAnnotatedTagReader.cs ├── GitCommit.cs ├── GitCommitReader.cs ├── GitException.cs ├── GitObjectId.cs ├── GitObjectStream.cs ├── GitPack.cs ├── GitPackCache.cs ├── GitPackDeltafiedStream.cs ├── GitPackIndexMappedReader.cs ├── GitPackIndexReader.cs ├── GitPackMemoryCache.cs ├── GitPackMemoryCacheStream.cs ├── GitPackMemoryCacheViewStream.cs ├── GitPackNullCache.cs ├── GitPackObjectType.cs ├── GitPackPooledStream.cs ├── GitPackReader.cs ├── GitReferenceReader.cs ├── GitRepository.cs ├── GitSignature.cs ├── GitTag.cs ├── GitTree.cs ├── GitTreeEntry.cs ├── GitTreeReader.cs ├── GitTreeStreamingReader.cs ├── ManagedGitLib.csproj ├── MemoryMappedStream.cs ├── NativeMethods.txt ├── StreamExtensions.cs └── ZLibStream.cs ├── Metadata ├── logo.png ├── logo.svg └── version.json ├── NOTICE ├── NerdBank.GitVersioning ├── AssemblyVersionOptionsConverter.cs ├── CloudBuild.cs ├── CloudBuildServices │ ├── AppVeyor.cs │ ├── AtlassianBamboo.cs │ ├── GitHubActions.cs │ ├── GitLab.cs │ ├── Jenkins.cs │ ├── SpaceAutomation.cs │ ├── TeamCity.cs │ ├── Travis.cs │ └── VisualStudioTeamServices.cs ├── Commands │ └── CloudCommand.cs ├── FilterPath.cs ├── FilterPathJsonConverter.cs ├── GitContext.cs ├── GitException.cs ├── ICloudBuild.cs ├── LibGit2 │ ├── LibGit2Context.cs │ ├── LibGit2GitExtensions.cs │ └── LibGit2VersionFile.cs ├── Managed │ ├── ManagedGitContext.cs │ ├── ManagedGitExtensions.cs │ └── ManagedVersionFile.cs ├── ManagedGit │ ├── DeltaInstruction.cs │ ├── DeltaInstructionType.cs │ ├── DeltaStreamReader.cs │ ├── FileHelpers.cs │ ├── GitCommit.cs │ ├── GitCommitReader.cs │ ├── GitObjectId.cs │ ├── GitObjectStream.cs │ ├── GitPack.cs │ ├── GitPackCache.cs │ ├── GitPackDeltafiedStream.cs │ ├── GitPackIndexMappedReader.cs │ ├── GitPackIndexReader.cs │ ├── GitPackMemoryCache.cs │ ├── GitPackMemoryCacheStream.cs │ ├── GitPackMemoryCacheViewStream.cs │ ├── GitPackNullCache.cs │ ├── GitPackObjectType.cs │ ├── GitPackPooledStream.cs │ ├── GitPackReader.cs │ ├── GitReferenceReader.cs │ ├── GitRepository.cs │ ├── GitSignature.cs │ ├── GitTree.cs │ ├── GitTreeEntry.cs │ ├── GitTreeReader.cs │ ├── GitTreeStreamingReader.cs │ ├── MemoryMappedStream.cs │ ├── StreamExtensions.cs │ └── ZLibStream.cs ├── NativeMethods.json ├── NativeMethods.txt ├── NerdBank.GitVersioning.csproj ├── NoGit │ ├── NoGitContext.cs │ └── NoGitVersionFile.cs ├── Properties │ └── AssemblyInfo.cs ├── ReleaseManager.cs ├── SemanticVersion.cs ├── SemanticVersionExtensions.cs ├── SemanticVersionJsonConverter.cs ├── Utilities.cs ├── VersionExtensions.cs ├── VersionFile.cs ├── VersionOptions.cs ├── VersionOptionsContractResolver.cs ├── VersionOracle.cs └── version.schema.json ├── README.md ├── global.json └── nuget.config /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | # Ensure shell scripts use LF line endings (linux only accepts LF) 7 | *.sh eol=lf 8 | *.ps1 eol=lf 9 | 10 | ManagedGitLib.ExtendedTests/commit* binary 11 | ManagedGitLib.Tests/commit-ab39e8acac105fa0db88514f259341c9f0201b22 binary 12 | ManagedGitLib.Tests/commit-ab39e8acac105fa0db88514f259341c9f0201b22-message binary 13 | ManagedGitLib.Tests/tag-ceaa6f5b53565910b9b2dd211a9d8284445a9a9c binary 14 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | release: 7 | types: [published] 8 | 9 | jobs: 10 | run-tests: 11 | name: Run Tests 12 | 13 | strategy: 14 | matrix: 15 | os: [ubuntu-20.04, windows-2019, macos-11] 16 | 17 | runs-on: ${{ matrix.os }} 18 | 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v2 22 | 23 | - name: Setup .NET 24 | uses: actions/setup-dotnet@v1.8.2 25 | 26 | - name: Run Tests 27 | run: dotnet test 28 | 29 | deploy: 30 | name: Deploy 31 | 32 | runs-on: ubuntu-20.04 33 | 34 | needs: run-tests 35 | 36 | if: ${{ github.event_name != 'pull_request' && github.repository_owner == 'GlebChili' }} 37 | 38 | steps: 39 | - name: Checkout 40 | uses: actions/checkout@v2 41 | 42 | - name: Setup .NET 43 | uses: actions/setup-dotnet@v1.8.2 44 | 45 | - name: Pack 46 | run: dotnet pack -c Release -o nupkgs 47 | 48 | - name: Upload nuget package to Azure DevOps 49 | run: | 50 | dotnet nuget update source glebchili-personal-public --username CI --password ${{ secrets.AZURE_DEVOPS_PAT }} --store-password-in-clear-text 51 | dotnet nuget push nupkgs/**.nupkg --source glebchili-personal-public --api-key az --skip-duplicate 52 | 53 | - name: Upload nuget package to NuGet.org 54 | if: ${{ github.event_name == 'release' }} 55 | run: dotnet nuget push nupkgs/**.nupkg --source nuget --api-key ${{ secrets.NUGET_ORG_PAT }} --skip-duplicate -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Gleb Krasilich 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ManagedGitLib.ExtendedTests/ManagedGitLib.ExtendedTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | runtime; build; native; contentfiles; analyzers; buildtransitive 15 | all 16 | 17 | 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | all 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /ManagedGitLib.ExtendedTests/TestAgainstLibgit2MonoRepo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using LibGit2Sharp; 8 | using Xunit; 9 | using Xunit.Abstractions; 10 | 11 | namespace ManagedGitLib.ExtendedTests 12 | { 13 | public class Libgit2MonoRepoProvider : IDisposable 14 | { 15 | readonly DirectoryInfo repoDirectory; 16 | readonly Repository repo; 17 | 18 | public DirectoryInfo RepoDirectory => repoDirectory; 19 | public Repository Repo => repo; 20 | 21 | public Libgit2MonoRepoProvider() 22 | { 23 | repoDirectory = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString())); 24 | 25 | Repository.Clone("https://github.com/mono/mono.git", repoDirectory.FullName); 26 | 27 | repo = new Repository(repoDirectory.FullName); 28 | } 29 | 30 | public void Dispose() 31 | { 32 | repo.Dispose(); 33 | 34 | // For some unknown issue, git pack files can't be deleted, when tests are running on GitHub Windows runners 35 | if (OperatingSystem.IsWindows() && Environment.GetEnvironmentVariable("GITHUB_ACTIONS") == "true") 36 | { 37 | Console.WriteLine($"{nameof(MonoRepoProvider)}: Tests are running on GitHub Windows runners. " + 38 | $"Test repository cleanup won't be performed"); 39 | } 40 | else 41 | { 42 | repoDirectory.SetFilesAttributesToNormal(); 43 | repoDirectory.Delete(true); 44 | } 45 | } 46 | } 47 | 48 | public class TestAgainstLibgit2MonoRepo : IClassFixture 49 | { 50 | readonly Libgit2MonoRepoProvider repoProvider; 51 | 52 | public TestAgainstLibgit2MonoRepo(Libgit2MonoRepoProvider repoProvider) 53 | { 54 | this.repoProvider = repoProvider; 55 | } 56 | 57 | [Fact] 58 | public void GetAnnotatedTag() 59 | { 60 | using GitRepository repo = GitRepository.Create(repoProvider.RepoDirectory.FullName)!; 61 | 62 | GitTag tag = repo.GetAnnotatedTag(GitObjectId.Parse("b148bbd1583868db22eb52f06985bed671ca3d9a")); 63 | 64 | Assert.Equal(GitObjectId.Parse("b148bbd1583868db22eb52f06985bed671ca3d9a"), tag.Sha); 65 | 66 | Assert.Equal(GitObjectId.Parse("6bf3922f3fdf8587302a8f7b1b6cbb4fad78a42c"), tag.Target); 67 | 68 | Assert.True(tag.IsAnnotated); 69 | 70 | Assert.Equal("commit", tag.TargetType); 71 | 72 | Assert.Equal("mono-5.8.1.0", tag.Name); 73 | 74 | GitSignature tagger = tag.Tagger!.Value; 75 | 76 | Assert.Equal("Xamarin Release Manager", tagger.Name); 77 | Assert.Equal("builder@xamarin.com", tagger.Email); 78 | Assert.Equal(DateTimeOffset.FromUnixTimeSeconds(1522083303), tagger.Date); 79 | 80 | Assert.Equal(@"Mono - 5.8.1.0", tag.Message); 81 | } 82 | 83 | [Fact] 84 | public void GetAllTags() 85 | { 86 | using GitRepository repo = GitRepository.Create(repoProvider.RepoDirectory.FullName)!; 87 | 88 | var expectedTags = repoProvider.Repo.Tags; 89 | 90 | var actualTags = repo.GetAllTags(); 91 | 92 | Assert.Equal(expectedTags.Count(), actualTags.Count); 93 | 94 | foreach (var expectedTag in expectedTags) 95 | { 96 | Assert.Contains(actualTags, t => 97 | t.Name == expectedTag.FriendlyName && 98 | t.Target.ToString() == expectedTag.Target.Sha && 99 | t.IsAnnotated == expectedTag.IsAnnotated); 100 | 101 | if (expectedTag.IsAnnotated) 102 | { 103 | Assert.Contains(actualTags, t => 104 | t.IsAnnotated && 105 | t.Name == expectedTag.FriendlyName && 106 | t.Target.ToString() == expectedTag.Target.Sha && 107 | t.Tagger!.Value.Name == expectedTag.Annotation.Tagger.Name && 108 | t.Tagger!.Value.Email == expectedTag.Annotation.Tagger.Email && 109 | t.Tagger!.Value.Date == expectedTag.Annotation.Tagger.When && 110 | t.Message == expectedTag.Annotation.Message.TrimEnd('\n')); 111 | } 112 | } 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /ManagedGitLib.ExtendedTests/TestUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.IO; 7 | using System.Reflection; 8 | using Validation; 9 | 10 | namespace ManagedGitLib.ExtendedTests 11 | { 12 | public class TestUtils 13 | { 14 | internal static Stream GetEmbeddedResource(string resourcePath) 15 | { 16 | Requires.NotNullOrEmpty(resourcePath, nameof(resourcePath)); 17 | 18 | return Assembly.GetExecutingAssembly() 19 | .GetManifestResourceStream($"ManagedGitLib.ExtendedTests.{resourcePath.Replace('\\', '.')}"); 20 | } 21 | } 22 | 23 | public static class HelperExtensions 24 | { 25 | public static void SetFilesAttributesToNormal(this DirectoryInfo directory) 26 | { 27 | foreach (FileInfo file in directory.GetFiles()) 28 | { 29 | File.SetAttributes(file.FullName, FileAttributes.Normal); 30 | File.Delete(file.FullName); 31 | } 32 | 33 | foreach (DirectoryInfo subdirectory in directory.GetDirectories()) 34 | { 35 | subdirectory.SetFilesAttributesToNormal(); 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /ManagedGitLib.ExtendedTests/commit1-author-name: -------------------------------------------------------------------------------- 1 | Alexander Köplinger -------------------------------------------------------------------------------- /ManagedGitLib.ExtendedTests/commit2-message: -------------------------------------------------------------------------------- 1 | 2020 02 backport metadata fixes (#21190) 2 | 3 | * [mono] Fix race during mono_image_storage_open (#21142) 4 | 5 | * The mono_refcount_inc call in mono_image_storage_trypublish or 6 | mono_image_storage_tryaddref may abort when racing against a 7 | mono_image_storage_dtor that already decremented the refcount. 8 | 9 | This race triggered in some cases when building aspnetcore using a Mono-based dotnet host SDK. The problem is that `mono_image_storage_close` runs outside the `mono_images_storage_lock` (and this may be unavoidable due to lock ordering concerns). Therefore, we can have a refcount that was already decremented to zero, but before `mono_image_storage_dtor` finishes removing the object from `images_storage_hash`, a parallel `mono_image_storage_trypublish` may have retrieved it from there. In that case, the `mono_refcount_inc` call will abort. 10 | 11 | Fixed by detecting that case via `mono_refcount_tryinc` instead, and simply treating the object as if it had already been removed. It will in time actually get removed, either by the parallel `mono_image_storage_dtor`, or else by the `g_hash_table_insert` in `mono_image_storage_trypublish` (which will safely replace it with the new object, and `mono_image_storage_dtor` will then detect that and skip removal). 12 | 13 | Co-authored-by: uweigand 14 | 15 | * [metadata] Handle MONO_TYPE_FNPTR case in collect_type_images (#19434) 16 | 17 | Fixes abort when PTR-FNPTR field signature is encountered. 18 | 19 | Fixes https://github.com/mono/mono/issues/12098 20 | Fixes https://github.com/mono/mono/issues/17113 21 | Fixes https://github.com/mono/mono/issues/19433 22 | 23 | * Ensure generic parameter constraint type is included when building image (#19395) 24 | 25 | sets. 26 | 27 | Making associated change to type_in_image to also check the constrained type for a match. Re-adding asserts now they they no longer trigger. 28 | 29 | fixup, accidentally used old function 30 | 31 | adjusting coding convention to K&R 32 | 33 | Co-authored-by: monojenkins 34 | Co-authored-by: uweigand 35 | Co-authored-by: Jeff Smith 36 | Co-authored-by: Alex Thibodeau -------------------------------------------------------------------------------- /ManagedGitLib.Tests/.gitattributes: -------------------------------------------------------------------------------- 1 | commit-* text eol=lf 2 | tree-* text eol=lf -------------------------------------------------------------------------------- /ManagedGitLib.Tests/3596ffe59898103a2675547d4597e742e1f2389c.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GlebChili/ManagedGitLib/d4ad0d2fe8ce137dcb311551c5b7df8bda67d702/ManagedGitLib.Tests/3596ffe59898103a2675547d4597e742e1f2389c.gz -------------------------------------------------------------------------------- /ManagedGitLib.Tests/GitAnnotatedTagReaderTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Xunit; 8 | 9 | namespace ManagedGitLib.Tests 10 | { 11 | public class GitAnnotatedTagReaderTests 12 | { 13 | [Fact] 14 | public void ReadAnnotatedTag() 15 | { 16 | using Stream stream = TestUtilities.GetEmbeddedResource(@"tag-ceaa6f5b53565910b9b2dd211a9d8284445a9a9c"); 17 | 18 | GitTag tag = 19 | GitAnnotatedTagReader.Read(stream, GitObjectId.Parse("ceaa6f5b53565910b9b2dd211a9d8284445a9a9c")); 20 | 21 | Assert.Equal(GitObjectId.Parse("ceaa6f5b53565910b9b2dd211a9d8284445a9a9c"), tag.Sha); 22 | 23 | Assert.Equal(GitObjectId.Parse("bef1e6335812d32f8eab648c0228fc624b9f8357"), tag.Target); 24 | 25 | Assert.True(tag.IsAnnotated); 26 | 27 | Assert.Equal("commit", tag.TargetType); 28 | 29 | Assert.Equal("mono-6.6.0.161", tag.Name); 30 | 31 | GitSignature? tagger = tag.Tagger; 32 | 33 | Assert.NotNull(tagger); 34 | 35 | Assert.Equal("Xamarin Public Jenkins (auto-signing)", tagger!.Value.Name); 36 | Assert.Equal("releng@xamarin.com", tagger!.Value.Email); 37 | Assert.Equal(DateTimeOffset.FromUnixTimeSeconds(1575995726), tagger!.Value.Date); 38 | 39 | Assert.Equal("Tag mono-6.6.0.161 for stable branch", tag.Message); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /ManagedGitLib.Tests/GitCommitTests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace ManagedGitLib.Tests 4 | { 5 | public class GitCommitTests 6 | { 7 | private readonly byte[] shaAsByteArray = new byte[] { 0x4e, 0x91, 0x27, 0x36, 0xc2, 0x7e, 0x40, 0xb3, 0x89, 0x90, 0x4d, 0x04, 0x6d, 0xc6, 0x3d, 0xc9, 0xf5, 0x78, 0x11, 0x7f }; 8 | 9 | [Fact] 10 | public void EqualsObjectTest() 11 | { 12 | var commit = new GitCommit() 13 | { 14 | Sha = GitObjectId.Parse(this.shaAsByteArray), 15 | }; 16 | 17 | var commit2 = new GitCommit() 18 | { 19 | Sha = GitObjectId.Parse(this.shaAsByteArray), 20 | }; 21 | 22 | var emptyCommit = new GitCommit() 23 | { 24 | Sha = GitObjectId.Empty, 25 | }; 26 | 27 | // Must be equal to itself 28 | Assert.True(commit.Equals((object)commit)); 29 | Assert.True(commit.Equals((object)commit2)); 30 | 31 | // Not equal to null 32 | Assert.False(commit.Equals(null)); 33 | 34 | // Not equal to other representations of the commit 35 | Assert.False(commit.Equals(this.shaAsByteArray)); 36 | Assert.False(commit.Equals(commit.Sha)); 37 | 38 | // Not equal to other object ids 39 | Assert.False(commit.Equals((object)emptyCommit)); 40 | } 41 | 42 | [Fact] 43 | public void EqualsCommitTest() 44 | { 45 | var commit = new GitCommit() 46 | { 47 | Sha = GitObjectId.Parse(this.shaAsByteArray), 48 | }; 49 | 50 | var commit2 = new GitCommit() 51 | { 52 | Sha = GitObjectId.Parse(this.shaAsByteArray), 53 | }; 54 | 55 | var emptyCommit = new GitCommit() 56 | { 57 | Sha = GitObjectId.Empty, 58 | }; 59 | 60 | // Must be equal to itself 61 | Assert.True(commit.Equals(commit2)); 62 | Assert.True(commit.Equals(commit2)); 63 | 64 | // Not equal to other object ids 65 | Assert.False(commit.Equals(emptyCommit)); 66 | } 67 | 68 | [Fact] 69 | public void GetHashCodeTest() 70 | { 71 | var commit = new GitCommit() 72 | { 73 | Sha = GitObjectId.Parse(this.shaAsByteArray), 74 | }; 75 | 76 | var emptyCommit = new GitCommit() 77 | { 78 | Sha = GitObjectId.Empty, 79 | }; 80 | 81 | // The hash code is the int32 representation of the first 4 bytes of the SHA hash 82 | Assert.Equal(0x3627914e, commit.GetHashCode()); 83 | Assert.Equal(0, emptyCommit.GetHashCode()); 84 | } 85 | 86 | [Fact] 87 | public void ToStringTest() 88 | { 89 | var commit = new GitCommit() 90 | { 91 | Sha = GitObjectId.Parse(this.shaAsByteArray), 92 | }; 93 | 94 | Assert.Equal("Git Commit: 4e912736c27e40b389904d046dc63dc9f578117f", commit.ToString()); 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /ManagedGitLib.Tests/GitObjectIdTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using System.Text; 4 | using Xunit; 5 | using Xunit.Abstractions; 6 | 7 | namespace ManagedGitLib.Tests 8 | { 9 | public class GitObjectIdTests 10 | { 11 | private readonly byte[] shaAsByteArray = new byte[] { 0x4e, 0x91, 0x27, 0x36, 0xc2, 0x7e, 0x40, 0xb3, 0x89, 0x90, 0x4d, 0x04, 0x6d, 0xc6, 0x3d, 0xc9, 0xf5, 0x78, 0x11, 0x7f }; 12 | private const string shaAsHexString = "4e912736c27e40b389904d046dc63dc9f578117f"; 13 | private readonly byte[] shaAsHexAsciiByteArray = Encoding.ASCII.GetBytes(shaAsHexString); 14 | 15 | [Fact] 16 | public void ParseByteArrayTest() 17 | { 18 | var objectId = GitObjectId.Parse(this.shaAsByteArray); 19 | 20 | Span value = stackalloc byte[20]; 21 | objectId.CopyTo(value); 22 | Assert.True(value.SequenceEqual(this.shaAsByteArray.AsSpan())); 23 | } 24 | 25 | [Fact] 26 | public void ParseStringTest() 27 | { 28 | var objectId = GitObjectId.Parse(shaAsHexString); 29 | 30 | Span value = stackalloc byte[20]; 31 | objectId.CopyTo(value); 32 | Assert.True(value.SequenceEqual(this.shaAsByteArray.AsSpan())); 33 | } 34 | 35 | [Fact] 36 | public void ParseHexArrayTest() 37 | { 38 | var objectId = GitObjectId.ParseHex(this.shaAsHexAsciiByteArray); 39 | 40 | Span value = stackalloc byte[20]; 41 | objectId.CopyTo(value); 42 | Assert.True(value.SequenceEqual(this.shaAsByteArray.AsSpan())); 43 | } 44 | 45 | [Fact] 46 | public void EqualsObjectTest() 47 | { 48 | var objectId = GitObjectId.ParseHex(this.shaAsHexAsciiByteArray); 49 | var objectId2 = GitObjectId.ParseHex(this.shaAsHexAsciiByteArray); 50 | 51 | // Must be equal to itself 52 | Assert.True(objectId.Equals((object)objectId)); 53 | Assert.True(objectId.Equals((object)objectId2)); 54 | 55 | // Not equal to null 56 | Assert.False(objectId.Equals(null)); 57 | 58 | // Not equal to other representations of the object id 59 | Assert.False(objectId.Equals(this.shaAsHexAsciiByteArray)); 60 | Assert.False(objectId.Equals(this.shaAsByteArray)); 61 | Assert.False(objectId.Equals(shaAsHexString)); 62 | 63 | // Not equal to other object ids 64 | Assert.False(objectId.Equals((object)GitObjectId.Empty)); 65 | } 66 | 67 | [Fact] 68 | public void EqualsObjectIdTest() 69 | { 70 | var objectId = GitObjectId.ParseHex(this.shaAsHexAsciiByteArray); 71 | var objectId2 = GitObjectId.ParseHex(this.shaAsHexAsciiByteArray); 72 | 73 | // Must be equal to itself 74 | Assert.True(objectId.Equals(objectId)); 75 | Assert.True(objectId.Equals(objectId2)); 76 | 77 | // Not equal to other object ids 78 | Assert.False(objectId.Equals(GitObjectId.Empty)); 79 | } 80 | 81 | [Fact] 82 | public void GetHashCodeTest() 83 | { 84 | // The hash code is the int32 representation of the first 4 bytes 85 | var objectId = GitObjectId.ParseHex(this.shaAsHexAsciiByteArray); 86 | Assert.Equal(0x3627914e, objectId.GetHashCode()); 87 | Assert.Equal(0, GitObjectId.Empty.GetHashCode()); 88 | } 89 | 90 | [Fact] 91 | public void AsUInt16Test() 92 | { 93 | // The hash code is the int32 representation of the first 4 bytes 94 | var objectId = GitObjectId.ParseHex(this.shaAsHexAsciiByteArray); 95 | Assert.Equal(0x4e91, objectId.AsUInt16()); 96 | Assert.Equal(0, GitObjectId.Empty.GetHashCode()); 97 | } 98 | 99 | [Fact] 100 | public void ToStringTest() 101 | { 102 | var objectId = GitObjectId.Parse(this.shaAsByteArray); 103 | Assert.Equal(shaAsHexString, objectId.ToString()); 104 | } 105 | 106 | [Fact] 107 | public void CopyToUtf16StringTest() 108 | { 109 | // Common use case: create the path to the object in the Git object store, 110 | // e.g. git/objects/[byte 0]/[bytes 1 - 19] 111 | byte[] valueAsBytes = Encoding.Unicode.GetBytes("git/objects/00/01020304050607080910111213141516171819"); 112 | Span valueAsChars = MemoryMarshal.Cast(valueAsBytes); 113 | 114 | var objectId = GitObjectId.ParseHex(this.shaAsHexAsciiByteArray); 115 | objectId.CopyAsHex(0, 1, valueAsChars.Slice(12, 1 * 2)); 116 | objectId.CopyAsHex(1, 19, valueAsChars.Slice(15, 19 * 2)); 117 | 118 | var path = Encoding.Unicode.GetString(valueAsBytes); 119 | Assert.Equal("git/objects/4e/912736c27e40b389904d046dc63dc9f578117f", path); 120 | } 121 | 122 | [Fact] 123 | public void CopyToTest() 124 | { 125 | var objectId = GitObjectId.Parse(this.shaAsByteArray); 126 | 127 | byte[] actual = new byte[20]; 128 | objectId.CopyTo(actual); 129 | 130 | Assert.Equal(this.shaAsByteArray, actual); 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /ManagedGitLib.Tests/GitObjectStreamTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.IO.Compression; 4 | using System.Security.Cryptography; 5 | using Xunit; 6 | 7 | namespace ManagedGitLib.Tests 8 | { 9 | public class GitObjectStreamTests 10 | { 11 | [Fact] 12 | public void ReadTest() 13 | { 14 | using (Stream rawStream = TestUtilities.GetEmbeddedResource(@"3596ffe59898103a2675547d4597e742e1f2389c.gz")) 15 | using (GitObjectStream stream = new GitObjectStream(rawStream, "commit")) 16 | using (var sha = SHA1.Create()) 17 | { 18 | Assert.Equal(137, stream.Length); 19 | var deflateStream = Assert.IsType(stream.BaseStream); 20 | Assert.Same(rawStream, deflateStream.BaseStream); 21 | Assert.Equal("commit", stream.ObjectType); 22 | Assert.Equal(0, stream.Position); 23 | 24 | var hash = sha.ComputeHash(stream); 25 | Assert.Equal("U1WYLbBP+xD47Y32m+hpCCTpnLA=", Convert.ToBase64String(hash)); 26 | 27 | Assert.Equal(stream.Length, stream.Position); 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /ManagedGitLib.Tests/GitPackDeltafiedStreamTests.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Xunit; 3 | 4 | namespace ManagedGitLib.Tests 5 | { 6 | public class GitPackDeltafiedStreamTests 7 | { 8 | // Reconstructs an object by reading the base stream and the delta stream. 9 | // You can create delta representations of an object by running the 10 | // test tool which is located in the t/helper/ folder of the Git source repository. 11 | // Use with the delta -d [base file,in] [updated file,in] [delta file,out] arguments. 12 | [Theory] 13 | [InlineData(@"commit-4497b0eaaa89abf0e6d70961ad5f04fd3a49cbc6", @"commit.delta", @"commit-d56dc3ed179053abef2097d1120b4507769bcf1a")] 14 | [InlineData(@"tree-bb36cf0ca445ccc8e5ce9cc88f7cf74128e96dc9", @"tree.delta", @"tree-f914b48023c7c804a4f3be780d451f31aef74ac1")] 15 | public void TestDeltaStream(string basePath, string deltaPath, string expectedPath) 16 | { 17 | byte[] expected = null; 18 | 19 | using (Stream expectedStream = TestUtilities.GetEmbeddedResource(expectedPath)) 20 | { 21 | expected = new byte[expectedStream.Length]; 22 | expectedStream.Read(expected); 23 | } 24 | 25 | byte[] actual = new byte[expected.Length]; 26 | 27 | using (Stream baseStream = TestUtilities.GetEmbeddedResource(basePath)) 28 | using (Stream deltaStream = TestUtilities.GetEmbeddedResource(deltaPath)) 29 | using (GitPackDeltafiedStream deltafiedStream = new GitPackDeltafiedStream(baseStream, deltaStream)) 30 | { 31 | // Assert.Equal(expected.Length, deltafiedStream.Length); 32 | 33 | deltafiedStream.Read(actual); 34 | 35 | Assert.Equal(expected, actual); 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /ManagedGitLib.Tests/GitPackIndexMappedReaderTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Xunit; 4 | 5 | namespace ManagedGitLib.Tests 6 | { 7 | public class GitPackIndexMappedReaderTests 8 | { 9 | [Fact] 10 | public void ConstructorNullTest() 11 | { 12 | Assert.Throws(() => new GitPackIndexMappedReader(null)); 13 | } 14 | 15 | [Fact] 16 | public void GetOffsetTest() 17 | { 18 | var indexFile = Path.GetTempFileName(); 19 | 20 | using (Stream resourceStream = TestUtilities.GetEmbeddedResource(@"pack-7d6b2c56ffb97eedb92f4e28583c093f7ee4b3d9.idx")) 21 | using (FileStream stream = File.Open(indexFile, FileMode.Open)) 22 | { 23 | resourceStream.CopyTo(stream); 24 | } 25 | 26 | using (FileStream stream = File.OpenRead(indexFile)) 27 | using (GitPackIndexReader reader = new GitPackIndexMappedReader(stream)) 28 | { 29 | // Offset of an object which is present 30 | Assert.Equal(12, reader.GetOffset(GitObjectId.Parse("f5b401f40ad83f13030e946c9ea22cb54cb853cd"))); 31 | Assert.Equal(317, reader.GetOffset(GitObjectId.Parse("d6781552a0a94adbf73ed77696712084754dc274"))); 32 | 33 | // null for an object which is not present 34 | Assert.Null(reader.GetOffset(GitObjectId.Empty)); 35 | } 36 | 37 | try 38 | { 39 | File.Delete(indexFile); 40 | } 41 | catch (UnauthorizedAccessException) 42 | { 43 | // TBD: Figure out what's keeping a lock on the file. Seems to be unique to Windows. 44 | } 45 | 46 | } 47 | 48 | [Fact] 49 | public void GetOffsetFromPartialTest() 50 | { 51 | var indexFile = Path.GetTempFileName(); 52 | 53 | using (Stream resourceStream = TestUtilities.GetEmbeddedResource(@"pack-7d6b2c56ffb97eedb92f4e28583c093f7ee4b3d9.idx")) 54 | using (FileStream stream = File.Open(indexFile, FileMode.Open)) 55 | { 56 | resourceStream.CopyTo(stream); 57 | } 58 | 59 | using (FileStream stream = File.OpenRead(indexFile)) 60 | using (var reader = new GitPackIndexMappedReader(stream)) 61 | { 62 | // Offset of an object which is present 63 | (var offset, var objectId) = reader.GetOffset(new byte[] { 0xf5, 0xb4, 0x01, 0xf4 }); 64 | Assert.Equal(12, offset); 65 | Assert.Equal(GitObjectId.Parse("f5b401f40ad83f13030e946c9ea22cb54cb853cd"), objectId); 66 | 67 | (offset, objectId) = reader.GetOffset(new byte[] { 0xd6, 0x78, 0x15, 0x52 }); 68 | Assert.Equal(317, offset); 69 | Assert.Equal(GitObjectId.Parse("d6781552a0a94adbf73ed77696712084754dc274"), objectId); 70 | 71 | // null for an object which is not present 72 | (offset, objectId) = reader.GetOffset(new byte[] { 0x00, 0x00, 0x00, 0x00 }); 73 | Assert.Null(offset); 74 | Assert.Null(objectId); 75 | } 76 | 77 | try 78 | { 79 | File.Delete(indexFile); 80 | } 81 | catch (UnauthorizedAccessException) 82 | { 83 | // TBD: Figure out what's keeping a lock on the file. Seems to be unique to Windows. 84 | } 85 | 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /ManagedGitLib.Tests/GitPackMemoryCacheTests.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Xunit; 3 | 4 | namespace ManagedGitLib.Tests 5 | { 6 | /// 7 | /// Tests the class. 8 | /// 9 | public class GitPackMemoryCacheTests 10 | { 11 | [Fact] 12 | public void StreamsAreIndependent() 13 | { 14 | using (MemoryStream stream = new MemoryStream( 15 | new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 })) 16 | { 17 | var cache = new GitPackMemoryCache(); 18 | 19 | var stream1 = cache.Add(0, stream); 20 | Assert.True(cache.TryOpen(0, out Stream stream2)); 21 | 22 | using (stream1) 23 | using (stream2) 24 | { 25 | stream1.Seek(5, SeekOrigin.Begin); 26 | Assert.Equal(5, stream1.Position); 27 | Assert.Equal(0, stream2.Position); 28 | Assert.Equal(5, stream1.ReadByte()); 29 | 30 | Assert.Equal(6, stream1.Position); 31 | Assert.Equal(0, stream2.Position); 32 | 33 | Assert.Equal(0, stream2.ReadByte()); 34 | Assert.Equal(6, stream1.Position); 35 | Assert.Equal(1, stream2.Position); 36 | } 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /ManagedGitLib.Tests/GitTreeStreamingReaderTests.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | using Xunit; 4 | 5 | namespace ManagedGitLib.Tests 6 | { 7 | public class GitTreeStreamingReaderTests 8 | { 9 | [Fact] 10 | public void FindBlobTest() 11 | { 12 | using (Stream stream = TestUtilities.GetEmbeddedResource(@"tree.bin")) 13 | { 14 | var blobObjectId = GitTreeStreamingReader.FindNode(stream, Encoding.UTF8.GetBytes("version.json")); 15 | Assert.Equal("59552a5eed6779aa4e5bb4dc96e80f36bb6e7380", blobObjectId.ToString()); 16 | } 17 | } 18 | 19 | [Fact] 20 | public void FindTreeTest() 21 | { 22 | using (Stream stream = TestUtilities.GetEmbeddedResource(@"tree.bin")) 23 | { 24 | var blobObjectId = GitTreeStreamingReader.FindNode(stream, Encoding.UTF8.GetBytes("tools")); 25 | Assert.Equal("ec8e91fc4ad13d6a214584330f26d7a05495c8cc", blobObjectId.ToString()); 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /ManagedGitLib.Tests/ManagedGitLib.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | runtime; build; native; contentfiles; analyzers; buildtransitive 14 | all 15 | 16 | 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | all 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /ManagedGitLib.Tests/RepoTestBase.Helpers.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using LibGit2Sharp; 7 | using Nerdbank.GitVersioning; 8 | 9 | public partial class RepoTestBase 10 | { 11 | /// 12 | /// Gets the number of commits in the longest single path between 13 | /// the specified commit and the most distant ancestor (inclusive) 14 | /// that set the version to the value at . 15 | /// 16 | /// The commit, branch or tag to measure the height of. Leave as null to check HEAD. 17 | /// The repo-relative project directory for which to calculate the version. 18 | /// The height of the commit. Always a positive integer. 19 | protected int GetVersionHeight(string? committish, string? repoRelativeProjectDirectory = null) => this.GetVersionOracle(repoRelativeProjectDirectory, committish).VersionHeight; 20 | 21 | /// 22 | /// Gets the number of commits in the longest single path between 23 | /// HEAD in a repo and the most distant ancestor (inclusive) 24 | /// that set the version to the value in the working copy 25 | /// (or HEAD for bare repositories). 26 | /// 27 | /// The repo-relative project directory for which to calculate the version. 28 | /// The height of the repo at HEAD. Always a positive integer. 29 | protected int GetVersionHeight(string? repoRelativeProjectDirectory = null) => this.GetVersionHeight(committish: null, repoRelativeProjectDirectory); 30 | 31 | private class FakeCloudBuild : ICloudBuild 32 | { 33 | public FakeCloudBuild(string gitCommitId) 34 | { 35 | this.GitCommitId = gitCommitId; 36 | } 37 | 38 | public bool IsApplicable => true; 39 | 40 | public bool IsPullRequest => false; 41 | 42 | public string? BuildingBranch => null; 43 | 44 | public string? BuildingTag => null; 45 | 46 | public string? GitCommitId { get; private set; } 47 | 48 | public IReadOnlyDictionary SetCloudBuildNumber(string buildNumber, TextWriter? stdout, TextWriter? stderr) 49 | { 50 | return new Dictionary(); 51 | } 52 | 53 | public IReadOnlyDictionary SetCloudBuildVariable(string name, string value, TextWriter? stdout, TextWriter? stderr) 54 | { 55 | return new Dictionary(); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /ManagedGitLib.Tests/StreamExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Xunit; 3 | 4 | namespace ManagedGitLib.Tests 5 | { 6 | public class StreamExtensionsTests 7 | { 8 | [Fact] 9 | public void ReadTest() 10 | { 11 | byte[] data = new byte[] { 0b10010001, 0b00101110 }; 12 | 13 | using (MemoryStream stream = new MemoryStream(data)) 14 | { 15 | Assert.Equal(5905, stream.ReadMbsInt()); 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ManagedGitLib.Tests/TestUtilities.cs: -------------------------------------------------------------------------------- 1 | using Validation; 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Diagnostics; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Reflection; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | 12 | /// 13 | /// Test utility methods. 14 | /// 15 | internal static class TestUtilities 16 | { 17 | /// 18 | /// Recursively delete a directory, even after a git commit has been authored within it. 19 | /// 20 | /// The path to delete. 21 | internal static void DeleteDirectory(string path) 22 | { 23 | Requires.NotNullOrEmpty(path, nameof(path)); 24 | Requires.Argument(Path.IsPathRooted(path), nameof(path), "Must be rooted."); 25 | 26 | try 27 | { 28 | Directory.Delete(path, true); 29 | } 30 | catch (UnauthorizedAccessException) 31 | { 32 | // Unknown why this fails so often. 33 | // Somehow making commits with libgit2sharp locks files 34 | // such that we can't delete them (but Windows Explorer can). 35 | var psi = new ProcessStartInfo("cmd.exe", $"/c rd /s /q \"{path}\""); 36 | psi.WorkingDirectory = Path.GetTempPath(); 37 | psi.WindowStyle = ProcessWindowStyle.Hidden; 38 | var process = Process.Start(psi); 39 | process.WaitForExit(); 40 | } 41 | } 42 | 43 | internal static Stream GetEmbeddedResource(string resourcePath) 44 | { 45 | Requires.NotNullOrEmpty(resourcePath, nameof(resourcePath)); 46 | 47 | return Assembly.GetExecutingAssembly().GetManifestResourceStream($"ManagedGitLib.Tests.{resourcePath.Replace('\\', '.')}"); 48 | } 49 | 50 | internal static void ExtractEmbeddedResource(string resourcePath, string extractedFilePath) 51 | { 52 | Requires.NotNullOrEmpty(resourcePath, nameof(resourcePath)); 53 | Requires.NotNullOrEmpty(extractedFilePath, nameof(extractedFilePath)); 54 | 55 | using (var stream = GetEmbeddedResource(resourcePath)) 56 | { 57 | Requires.Argument(stream is not null, nameof(resourcePath), "Resource not found."); 58 | using (var extractedFile = File.OpenWrite(extractedFilePath)) 59 | { 60 | stream.CopyTo(extractedFile); 61 | } 62 | } 63 | } 64 | 65 | internal static ExpandedRepo ExtractRepoArchive(string repoArchiveName) 66 | { 67 | string archiveFilePath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); 68 | string expandedFolderPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); 69 | 70 | ExtractEmbeddedResource($"repos.{repoArchiveName}.zip", archiveFilePath); 71 | try 72 | { 73 | System.IO.Compression.ZipFile.ExtractToDirectory(archiveFilePath, expandedFolderPath); 74 | return new ExpandedRepo(expandedFolderPath); 75 | } 76 | finally 77 | { 78 | File.Delete(archiveFilePath); 79 | } 80 | } 81 | 82 | internal static string ToHex(ushort number) => number.ToString("X"); 83 | 84 | internal static ushort FromHex(string hex) => ushort.Parse(hex, System.Globalization.NumberStyles.HexNumber); 85 | 86 | internal class ExpandedRepo : IDisposable 87 | { 88 | internal ExpandedRepo(string repoPath) 89 | { 90 | Requires.NotNullOrEmpty(repoPath, nameof(repoPath)); 91 | this.RepoPath = repoPath; 92 | } 93 | 94 | public string RepoPath { get; private set; } 95 | 96 | public void Dispose() 97 | { 98 | if (Directory.Exists(this.RepoPath)) 99 | { 100 | DeleteDirectory(this.RepoPath); 101 | } 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /ManagedGitLib.Tests/UnitTest1.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace ManagedGitLib.Tests; 4 | 5 | public class UnitTest1 6 | { 7 | [Fact] 8 | public void Test1() 9 | { 10 | 11 | } 12 | } -------------------------------------------------------------------------------- /ManagedGitLib.Tests/ZLibStreamTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.IO.Compression; 4 | using System.Security.Cryptography; 5 | using System.Text; 6 | using Xunit; 7 | 8 | namespace ManagedGitLib.Tests 9 | { 10 | public class ZLibStreamTest 11 | { 12 | [Fact] 13 | public void ReadTest() 14 | { 15 | using (Stream rawStream = TestUtilities.GetEmbeddedResource(@"3596ffe59898103a2675547d4597e742e1f2389c.gz")) 16 | using (ZLibStream stream = new ZLibStream(rawStream, -1)) 17 | using (var sha = SHA1.Create()) 18 | { 19 | var deflateStream = Assert.IsType(stream.BaseStream); 20 | Assert.Same(rawStream, deflateStream.BaseStream); 21 | Assert.Equal(0, stream.Position); 22 | 23 | var hash = sha.ComputeHash(stream); 24 | Assert.Equal("NZb/5ZiYEDomdVR9RZfnQuHyOJw=", Convert.ToBase64String(hash)); 25 | 26 | Assert.Equal(148, stream.Position); 27 | } 28 | } 29 | 30 | [Fact] 31 | public void SeekTest() 32 | { 33 | using (Stream rawStream = TestUtilities.GetEmbeddedResource(@"3596ffe59898103a2675547d4597e742e1f2389c.gz")) 34 | using (ZLibStream stream = new ZLibStream(rawStream, -1)) 35 | { 36 | // Seek past the commit 137 header, and make sure we can read the 'tree' word 37 | Assert.Equal(11, stream.Seek(11, SeekOrigin.Begin)); 38 | var tree = new byte[4]; 39 | stream.Read(tree); 40 | Assert.Equal("tree", Encoding.UTF8.GetString(tree)); 41 | 42 | // Valid no-ops 43 | Assert.Equal(15, stream.Seek(0, SeekOrigin.Current)); 44 | Assert.Equal(15, stream.Seek(15, SeekOrigin.Begin)); 45 | 46 | // Invalid seeks 47 | Assert.Throws(() => stream.Seek(-1, SeekOrigin.Current)); 48 | Assert.Throws(() => stream.Seek(1, SeekOrigin.Current)); 49 | Assert.Throws(() => stream.Seek(-1, SeekOrigin.End)); 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /ManagedGitLib.Tests/commit-4497b0eaaa89abf0e6d70961ad5f04fd3a49cbc6: -------------------------------------------------------------------------------- 1 | tree f914b48023c7c804a4f3be780d451f31aef74ac1 2 | parent 06cc627f28736c0d13506b0414126580fe37c6f3 3 | author Andrew Arnott 1602013209 -0600 4 | committer Andrew Arnott 1602013209 -0600 5 | 6 | Set version to '3.4-alpha' 7 | -------------------------------------------------------------------------------- /ManagedGitLib.Tests/commit-7507fb2859c12f6c561efc23d48dd1be0fc6cdee: -------------------------------------------------------------------------------- 1 | tree 51a2be3228fb6c8bed8a3141384a25b36928136c 2 | parent e368cf51d8233bee7b0fcbc5174fe7030c882407 3 | author monojenkins 1599746832 -0400 4 | committer GitHub 1599746832 -0400 5 | gpgsig -----BEGIN PGP SIGNATURE----- 6 | 7 | wsBcBAABCAAQBQJfWjMQCRBK7hj4Ov3rIwAAdHIIABsTXgfvq1GoksuPtrQ5z3H4 8 | rL7zsWMfz0+Cb4VUaN5hCoHx58RYXMdmf/VLvFsQacUOvCVevAKaFm1g6fckJ0Rg 9 | p7SkE6Np9v0OisAj8SrHqsHNk9aoTvu2781doKtQmsBWXB+NYxNR3v3jmehn6h1v 10 | lTSwn4NHZRhDLEo1BRQR/ZuqZin437/73M6BY2LHwoEyA1ZDFigHHyuwaS4jWzn3 11 | qESGl9zNddpUvqkJtjDzQp7eoPI/fr76fBuFyrUMVe0yziNbuBUAU6UJKO0eS5y9 12 | QDN2Jfh1WnagHZ7L6GgYn72CK6q3QYFvNQSDHGJroj3Lc6rmxeD0/Jk1X43fDTE= 13 | =SzYu 14 | -----END PGP SIGNATURE----- 15 | 16 | 17 | [2020-02] Issue18826 fix (#20358) 18 | 19 | * Look for "xmonkeysloveplay" also at the end of LC_SYMTAB section, for signed Mac OS X binaries 20 | 21 | This extends Miguel de Icaza's previous work on allowing mono to load embedded 22 | assemblies / shared libraries, which was in turn a workaround on recent 23 | Mac OS X's increased security (and stopping LD_LIBRARY_PATH and friends from 24 | working). Miguel de Icaza came up with the idea of appending all of those 25 | embedded assemblies / shared libraries to the end of mono's main executable, 26 | and getting it to look at its own file and unpacking resources from an offset 27 | number + "xmonkeysloveplay" written to the end of file. 28 | 29 | Even more recent Mac OS X starts to prefer /require binaries to be code-signed. 30 | Code signing appends a "LC_CODE_SIGNATURE" section to the binary, thus breaking 31 | mono's embedded resource-loading. Also code-signing requires that the binary 32 | has no unknown parts. 33 | 34 | To make mkbundle / mono embedded resource-loading work in a code-signing 35 | situation, two parts are required: 36 | 37 | - adjust mkbundle to extend the LC_SYMTAB section to cover the appended 38 | assemblies / shared libraries, so code-signing can happen. 39 | ( addresses https://github.com/mono/mono/issues/17881 ) 40 | 41 | - adjust mono's main executable to also look at the end of the LC_SYMTAB 42 | section for the magic offset + "xmonkeysloveplay" token to find the embedded 43 | resources. 44 | ( addresses https://github.com/mono/mono/issues/18826 ) 45 | 46 | This change addresses the 2nd of the above. I also tried looking for the 47 | presence and beginnig of the LC_CODE_SIGNATURE section, but that proves 48 | unreliable: Code-signing can pad a few null bytes after "xmonkeysloveplay" 49 | to align the LC_CODE_SIGNATURE section to an 8(?)-byte boundary. 50 | 51 | * saving offset before read, so later lseek does not need to backtrack on the read 52 | 53 | This is a small simplification of the previous commit on this part of code. 54 | 55 | * fixes wrong drop-through from mis-positioning of #endif 56 | 57 | * Update mono/mini/main.c 58 | 59 | Adding spaces for clarity 60 | 61 | Co-authored-by: Ryan Lucia 62 | 63 | * Update mono/mini/main.c 64 | 65 | Adding spaces for clarity 66 | 67 | Co-authored-by: Ryan Lucia 68 | 69 | * Update mono/mini/main.c 70 | 71 | Adding spaces for clarity 72 | 73 | Co-authored-by: Ryan Lucia 74 | 75 | * Update mono/mini/main.c 76 | 77 | Adding spaces for clarity 78 | 79 | Co-authored-by: Ryan Lucia 80 | 81 | * Update mono/mini/main.c 82 | 83 | Added comment about what's going on. 84 | 85 | Co-authored-by: Ryan Lucia 86 | 87 | * Adding comments for clarity 88 | 89 | * rename variable "h" to "bin_header", for clarity 90 | 91 | * Limit Apple code-signing work around to only the desktop OS (vs iDevices/TV) 92 | 93 | * remove stylistic "else"; no change in functionality 94 | 95 | Co-authored-by: Hin-Tak Leung 96 | Co-authored-by: Ryan Lucia -------------------------------------------------------------------------------- /ManagedGitLib.Tests/commit-ab39e8acac105fa0db88514f259341c9f0201b22: -------------------------------------------------------------------------------- 1 | tree 0f118b0345501ba18c15e149c9ae49ce07352485 2 | parent e0b4d66ef7915417e04e88d5fa173185bb940029 3 | parent 10e67ce38fbee44b3f5584d4f9df6de6c5f4cc5c 4 | parent a7fef320334121af85dce4b9b731f6c9a9127cfd 5 | author Atsushi Eno 1278350964 -0000 6 | committer Atsushi Eno 1278350964 -0000 7 | 8 | 2010-07-05 Atsushi Enomoto 9 | 10 | * ChannelDispatcher.cs : 11 | moved IChannelDispatcherBoundListener from HttpChannelListener.cs. 12 | 13 | * SvcHttpHandler.cs : removed old code and #if blocks. 14 | 15 | * HttpStandaloneReplyChannel.cs 16 | HttpStandaloneRequestContext.cs 17 | HttpStandaloneChannelListener.cs 18 | HttpReplyChannel.cs 19 | HttpRequestContext.cs 20 | HttpChannelListener.cs : renamed former 3 files to latter 3 files. 21 | 22 | * System.ServiceModel.dll.sources : 23 | renamed new HTTP channel listener implementation sources, and 24 | removed old sources. 25 | 26 | 27 | 28 | svn path=/trunk/mcs/; revision=159913 29 | -------------------------------------------------------------------------------- /ManagedGitLib.Tests/commit-ab39e8acac105fa0db88514f259341c9f0201b22-message: -------------------------------------------------------------------------------- 1 | 2010-07-05 Atsushi Enomoto 2 | 3 | * ChannelDispatcher.cs : 4 | moved IChannelDispatcherBoundListener from HttpChannelListener.cs. 5 | 6 | * SvcHttpHandler.cs : removed old code and #if blocks. 7 | 8 | * HttpStandaloneReplyChannel.cs 9 | HttpStandaloneRequestContext.cs 10 | HttpStandaloneChannelListener.cs 11 | HttpReplyChannel.cs 12 | HttpRequestContext.cs 13 | HttpChannelListener.cs : renamed former 3 files to latter 3 files. 14 | 15 | * System.ServiceModel.dll.sources : 16 | renamed new HTTP channel listener implementation sources, and 17 | removed old sources. 18 | 19 | 20 | 21 | svn path=/trunk/mcs/; revision=159913 -------------------------------------------------------------------------------- /ManagedGitLib.Tests/commit-d56dc3ed179053abef2097d1120b4507769bcf1a: -------------------------------------------------------------------------------- 1 | tree f914b48023c7c804a4f3be780d451f31aef74ac1 2 | parent 4497b0eaaa89abf0e6d70961ad5f04fd3a49cbc6 3 | parent 0989e8fe0cd0e0900173b26decdfb24bc0cc8232 4 | author Andrew Arnott 1602013209 -0600 5 | committer Andrew Arnott 1602013209 -0600 6 | 7 | Merge branch 'v3.3' 8 | -------------------------------------------------------------------------------- /ManagedGitLib.Tests/commit-message: -------------------------------------------------------------------------------- 1 | [2020-02] Issue18826 fix (#20358) 2 | 3 | * Look for "xmonkeysloveplay" also at the end of LC_SYMTAB section, for signed Mac OS X binaries 4 | 5 | This extends Miguel de Icaza's previous work on allowing mono to load embedded 6 | assemblies / shared libraries, which was in turn a workaround on recent 7 | Mac OS X's increased security (and stopping LD_LIBRARY_PATH and friends from 8 | working). Miguel de Icaza came up with the idea of appending all of those 9 | embedded assemblies / shared libraries to the end of mono's main executable, 10 | and getting it to look at its own file and unpacking resources from an offset 11 | number + "xmonkeysloveplay" written to the end of file. 12 | 13 | Even more recent Mac OS X starts to prefer /require binaries to be code-signed. 14 | Code signing appends a "LC_CODE_SIGNATURE" section to the binary, thus breaking 15 | mono's embedded resource-loading. Also code-signing requires that the binary 16 | has no unknown parts. 17 | 18 | To make mkbundle / mono embedded resource-loading work in a code-signing 19 | situation, two parts are required: 20 | 21 | - adjust mkbundle to extend the LC_SYMTAB section to cover the appended 22 | assemblies / shared libraries, so code-signing can happen. 23 | ( addresses https://github.com/mono/mono/issues/17881 ) 24 | 25 | - adjust mono's main executable to also look at the end of the LC_SYMTAB 26 | section for the magic offset + "xmonkeysloveplay" token to find the embedded 27 | resources. 28 | ( addresses https://github.com/mono/mono/issues/18826 ) 29 | 30 | This change addresses the 2nd of the above. I also tried looking for the 31 | presence and beginnig of the LC_CODE_SIGNATURE section, but that proves 32 | unreliable: Code-signing can pad a few null bytes after "xmonkeysloveplay" 33 | to align the LC_CODE_SIGNATURE section to an 8(?)-byte boundary. 34 | 35 | * saving offset before read, so later lseek does not need to backtrack on the read 36 | 37 | This is a small simplification of the previous commit on this part of code. 38 | 39 | * fixes wrong drop-through from mis-positioning of #endif 40 | 41 | * Update mono/mini/main.c 42 | 43 | Adding spaces for clarity 44 | 45 | Co-authored-by: Ryan Lucia 46 | 47 | * Update mono/mini/main.c 48 | 49 | Adding spaces for clarity 50 | 51 | Co-authored-by: Ryan Lucia 52 | 53 | * Update mono/mini/main.c 54 | 55 | Adding spaces for clarity 56 | 57 | Co-authored-by: Ryan Lucia 58 | 59 | * Update mono/mini/main.c 60 | 61 | Adding spaces for clarity 62 | 63 | Co-authored-by: Ryan Lucia 64 | 65 | * Update mono/mini/main.c 66 | 67 | Added comment about what's going on. 68 | 69 | Co-authored-by: Ryan Lucia 70 | 71 | * Adding comments for clarity 72 | 73 | * rename variable "h" to "bin_header", for clarity 74 | 75 | * Limit Apple code-signing work around to only the desktop OS (vs iDevices/TV) 76 | 77 | * remove stylistic "else"; no change in functionality 78 | 79 | Co-authored-by: Hin-Tak Leung 80 | Co-authored-by: Ryan Lucia -------------------------------------------------------------------------------- /ManagedGitLib.Tests/commit.delta: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GlebChili/ManagedGitLib/d4ad0d2fe8ce137dcb311551c5b7df8bda67d702/ManagedGitLib.Tests/commit.delta -------------------------------------------------------------------------------- /ManagedGitLib.Tests/pack-7d6b2c56ffb97eedb92f4e28583c093f7ee4b3d9 .txt: -------------------------------------------------------------------------------- 1 | This packfile was generated by running git gc --aggressive on the repository 2 | generated by the VersionHeightResetsWithVersionSpecChanges unit test. 3 | 4 | You can view its contents by running: 5 | git verify-pack -v pack-7d6b2c56ffb97eedb92f4e28583c093f7ee4b3d9.idx 6 | 7 | f5b401f40ad83f13030e946c9ea22cb54cb853cd commit 222 169 12 8 | 8738061d8d4f882cac917d7a9de5bcf219e3b91a commit 187 136 181 9 | d6781552a0a94adbf73ed77696712084754dc274 commit 25 37 317 1 8738061d8d4f882cac917d7a9de5bcf219e3b91a 10 | 323deb231d7c892739511a4f5432e95cc9413cd1 commit 52 64 354 1 8738061d8d4f882cac917d7a9de5bcf219e3b91a 11 | 9bedd54fabfce593310f4fd35bd52a22fbe3cca5 commit 53 65 418 1 8738061d8d4f882cac917d7a9de5bcf219e3b91a 12 | d249b680c2092c475a3a32cc01a58eb0a8978b42 commit 53 65 483 1 8738061d8d4f882cac917d7a9de5bcf219e3b91a 13 | bf108f5ec235d9dd1f743b782e712fa79bdd942c commit 53 65 548 1 8738061d8d4f882cac917d7a9de5bcf219e3b91a 14 | c4fb7407f0d68e80bbda698989d6346731bfe374 commit 52 64 613 1 8738061d8d4f882cac917d7a9de5bcf219e3b91a 15 | 3c9254a93b2d4f067cda42b2bb4a33bf9a139301 commit 53 64 677 1 8738061d8d4f882cac917d7a9de5bcf219e3b91a 16 | 4f46ec9d5cad446b0ad6dd73041fa4a14a6edc5d commit 53 65 741 1 8738061d8d4f882cac917d7a9de5bcf219e3b91a 17 | 12b37694cb666cc32c09bd15c76912aec9fd3ef9 commit 53 65 806 1 8738061d8d4f882cac917d7a9de5bcf219e3b91a 18 | b4d44127e76276f3b389481956ac1a2a6df5fac3 commit 53 64 871 1 8738061d8d4f882cac917d7a9de5bcf219e3b91a 19 | ade83ea20b5882d81ad7addebca6c42886bbcf41 blob 174 140 935 20 | e2ddd74b76b4b557d36b8b29b9bc969ebb15ba79 tree 40 51 1075 21 | 126a30e8740bae4525e9d90dfcbd235fc04187c6 tree 40 51 1126 22 | 03284b6463f7f46e830a9ae0fb208a10bba48a15 blob 20 32 1177 1 ade83ea20b5882d81ad7addebca6c42886bbcf41 23 | non delta: 5 objects 24 | chain length = 1: 11 objects 25 | .git\objects\pack\pack-7d6b2c56ffb97eedb92f4e28583c093f7ee4b3d9.pack: ok -------------------------------------------------------------------------------- /ManagedGitLib.Tests/pack-7d6b2c56ffb97eedb92f4e28583c093f7ee4b3d9.idx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GlebChili/ManagedGitLib/d4ad0d2fe8ce137dcb311551c5b7df8bda67d702/ManagedGitLib.Tests/pack-7d6b2c56ffb97eedb92f4e28583c093f7ee4b3d9.idx -------------------------------------------------------------------------------- /ManagedGitLib.Tests/pack-7d6b2c56ffb97eedb92f4e28583c093f7ee4b3d9.pack: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GlebChili/ManagedGitLib/d4ad0d2fe8ce137dcb311551c5b7df8bda67d702/ManagedGitLib.Tests/pack-7d6b2c56ffb97eedb92f4e28583c093f7ee4b3d9.pack -------------------------------------------------------------------------------- /ManagedGitLib.Tests/tag-ceaa6f5b53565910b9b2dd211a9d8284445a9a9c: -------------------------------------------------------------------------------- 1 | object bef1e6335812d32f8eab648c0228fc624b9f8357 2 | type commit 3 | tag mono-6.6.0.161 4 | tagger Xamarin Public Jenkins (auto-signing) 1575995726 -0500 5 | 6 | Tag mono-6.6.0.161 for stable branch 7 | -------------------------------------------------------------------------------- /ManagedGitLib.Tests/tree-bb36cf0ca445ccc8e5ce9cc88f7cf74128e96dc9: -------------------------------------------------------------------------------- 1 | 100644 blob 98824c08ae19a0fcd17487f6dd163c9375174e90 .editorconfig 2 | 100644 blob e9ae1b43947e3832b0dbc064179f46eec35aa8b1 .gitattributes 3 | 040000 tree d7ef61eb64243d30552bda9de6de94d368855fdf .github 4 | 100644 blob f62635e01f3d9f6dffdb88a3fc255fb5636c6c38 .gitignore 5 | 100644 blob ccb9654d76d331adfc86c03c7ee211afd57c829d .gitmodules 6 | 100644 blob 775f221c98e117849f2dff6b3f6c0965935ddd36 CODE-OF-CONDUCT.md 7 | 100644 blob 09656a68a1ee6c4ef3e1d1981e7313612f540f92 CONTRIBUTING.md 8 | 100644 blob 3fae055e8f95a702c8ad87118954a5c37dc1c9ad CONTRIBUTORS 9 | 100644 blob 9a833a03454615f40b83db5870d0296c1789792e Directory.Build.rsp 10 | 100644 blob 56e51b1c06b5c4dead2b36c916acd983c347966e LICENSE 11 | 100644 blob 786a72bf6c10e28f5fcd801b7badf457a585a833 NOTICE.md 12 | 100644 blob e683c5d2e44077b81ad545f6648d938d79076eef README.md 13 | 100644 blob 228bd3155183cadcd9bc18685a95ade076620a9a azure-pipelines.yml 14 | 040000 tree c6b4b836da8f42abc6ca2d0122bb7bd8cb8dcaf9 azure-pipelines 15 | 100644 blob 7cb727e0ddf77480194ae7c85ed1fbafdfb32f9e build.cmd 16 | 100644 blob f49055e51191ca37b5bc84faf20dde7ca0d63e1c build.ps1 17 | 040000 tree aa92c5a940b472c44e722cbb25209b0e9e5c1d70 doc 18 | 100644 blob e9aac8c222ec33ebb429e5cf8f9eeeff66588717 global.json 19 | 100644 blob 970285c2f4bc72e54b10b058c9629867880eec08 init.cmd 20 | 100755 blob 8b11d60b6d4112782ddbcec198f65433e9e375db init.ps1 21 | 040000 tree f3264024e88924d229cdcff93ee654f6c6019bcd src 22 | 040000 tree ec8e91fc4ad13d6a214584330f26d7a05495c8cc tools 23 | 100644 blob 8f0e0c8831c0ff516d3899b3b1fd02d8f7c0e12a version.json 24 | 160000 commit 45e49432d270bef14295024b6ac02ae804747908 wiki 25 | -------------------------------------------------------------------------------- /ManagedGitLib.Tests/tree-f914b48023c7c804a4f3be780d451f31aef74ac1: -------------------------------------------------------------------------------- 1 | 100644 blob 98824c08ae19a0fcd17487f6dd163c9375174e90 .editorconfig 2 | 100644 blob e9ae1b43947e3832b0dbc064179f46eec35aa8b1 .gitattributes 3 | 040000 tree d7ef61eb64243d30552bda9de6de94d368855fdf .github 4 | 100644 blob f62635e01f3d9f6dffdb88a3fc255fb5636c6c38 .gitignore 5 | 100644 blob ccb9654d76d331adfc86c03c7ee211afd57c829d .gitmodules 6 | 100644 blob 775f221c98e117849f2dff6b3f6c0965935ddd36 CODE-OF-CONDUCT.md 7 | 100644 blob 09656a68a1ee6c4ef3e1d1981e7313612f540f92 CONTRIBUTING.md 8 | 100644 blob 3fae055e8f95a702c8ad87118954a5c37dc1c9ad CONTRIBUTORS 9 | 100644 blob 9a833a03454615f40b83db5870d0296c1789792e Directory.Build.rsp 10 | 100644 blob 56e51b1c06b5c4dead2b36c916acd983c347966e LICENSE 11 | 100644 blob 786a72bf6c10e28f5fcd801b7badf457a585a833 NOTICE.md 12 | 100644 blob e683c5d2e44077b81ad545f6648d938d79076eef README.md 13 | 100644 blob 228bd3155183cadcd9bc18685a95ade076620a9a azure-pipelines.yml 14 | 040000 tree c6b4b836da8f42abc6ca2d0122bb7bd8cb8dcaf9 azure-pipelines 15 | 100644 blob 7cb727e0ddf77480194ae7c85ed1fbafdfb32f9e build.cmd 16 | 100644 blob f49055e51191ca37b5bc84faf20dde7ca0d63e1c build.ps1 17 | 040000 tree aa92c5a940b472c44e722cbb25209b0e9e5c1d70 doc 18 | 100644 blob e9aac8c222ec33ebb429e5cf8f9eeeff66588717 global.json 19 | 100644 blob 970285c2f4bc72e54b10b058c9629867880eec08 init.cmd 20 | 100755 blob 8b11d60b6d4112782ddbcec198f65433e9e375db init.ps1 21 | 040000 tree f3264024e88924d229cdcff93ee654f6c6019bcd src 22 | 040000 tree ec8e91fc4ad13d6a214584330f26d7a05495c8cc tools 23 | 100644 blob 59552a5eed6779aa4e5bb4dc96e80f36bb6e7380 version.json 24 | 160000 commit 45e49432d270bef14295024b6ac02ae804747908 wiki 25 | -------------------------------------------------------------------------------- /ManagedGitLib.Tests/tree.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GlebChili/ManagedGitLib/d4ad0d2fe8ce137dcb311551c5b7df8bda67d702/ManagedGitLib.Tests/tree.bin -------------------------------------------------------------------------------- /ManagedGitLib.Tests/tree.delta: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GlebChili/ManagedGitLib/d4ad0d2fe8ce137dcb311551c5b7df8bda67d702/ManagedGitLib.Tests/tree.delta -------------------------------------------------------------------------------- /ManagedGitLib.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.1.31911.260 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ManagedGitLib", "ManagedGitLib\ManagedGitLib.csproj", "{664877E7-1DD2-4823-843C-3DC5CE8A803D}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ManagedGitLib.Tests", "ManagedGitLib.Tests\ManagedGitLib.Tests.csproj", "{A9FD29FF-594D-4C7D-8351-FCCEE12904A7}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NerdBank.GitVersioning", "NerdBank.GitVersioning\NerdBank.GitVersioning.csproj", "{7011673D-CB14-4584-88EA-236667AA4AD6}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ManagedGitLib.ExtendedTests", "ManagedGitLib.ExtendedTests\ManagedGitLib.ExtendedTests.csproj", "{72AC2D60-43F6-4B71-BCD0-5E40A72C805C}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | Release|Any CPU = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {664877E7-1DD2-4823-843C-3DC5CE8A803D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {664877E7-1DD2-4823-843C-3DC5CE8A803D}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {664877E7-1DD2-4823-843C-3DC5CE8A803D}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {664877E7-1DD2-4823-843C-3DC5CE8A803D}.Release|Any CPU.Build.0 = Release|Any CPU 24 | {A9FD29FF-594D-4C7D-8351-FCCEE12904A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {A9FD29FF-594D-4C7D-8351-FCCEE12904A7}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {A9FD29FF-594D-4C7D-8351-FCCEE12904A7}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {A9FD29FF-594D-4C7D-8351-FCCEE12904A7}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {7011673D-CB14-4584-88EA-236667AA4AD6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {7011673D-CB14-4584-88EA-236667AA4AD6}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {7011673D-CB14-4584-88EA-236667AA4AD6}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {7011673D-CB14-4584-88EA-236667AA4AD6}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {72AC2D60-43F6-4B71-BCD0-5E40A72C805C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {72AC2D60-43F6-4B71-BCD0-5E40A72C805C}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {72AC2D60-43F6-4B71-BCD0-5E40A72C805C}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {72AC2D60-43F6-4B71-BCD0-5E40A72C805C}.Release|Any CPU.Build.0 = Release|Any CPU 36 | EndGlobalSection 37 | GlobalSection(SolutionProperties) = preSolution 38 | HideSolutionNode = FALSE 39 | EndGlobalSection 40 | GlobalSection(ExtensibilityGlobals) = postSolution 41 | SolutionGuid = {08987C74-E59D-4053-BE08-9649758DF868} 42 | EndGlobalSection 43 | EndGlobal 44 | -------------------------------------------------------------------------------- /ManagedGitLib/DeltaInstruction.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | namespace ManagedGitLib 4 | { 5 | /// 6 | /// Represents an instruction in a deltified stream. 7 | /// 8 | /// 9 | public struct DeltaInstruction 10 | { 11 | /// 12 | /// Gets or sets the type of the current instruction. 13 | /// 14 | public DeltaInstructionType InstructionType; 15 | 16 | /// 17 | /// If the is , 18 | /// the offset of the base stream to start copying from. 19 | /// 20 | public int Offset; 21 | 22 | /// 23 | /// The number of bytes to copy or insert. 24 | /// 25 | public int Size; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /ManagedGitLib/DeltaInstructionType.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | namespace ManagedGitLib 4 | { 5 | /// 6 | /// Enumerates the various instruction types which can be found in a deltafied stream. 7 | /// 8 | /// 9 | public enum DeltaInstructionType 10 | { 11 | /// 12 | /// Instructs the caller to insert a new byte range into the object. 13 | /// 14 | Insert = 0, 15 | 16 | /// 17 | /// Instructs the caller to copy a byte range from the source object. 18 | /// 19 | Copy = 1, 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /ManagedGitLib/FileHelpers.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Diagnostics.CodeAnalysis; 5 | using System.IO; 6 | using System.Runtime.InteropServices; 7 | using Microsoft.Win32.SafeHandles; 8 | using Windows.Win32; 9 | using Windows.Win32.Storage.FileSystem; 10 | using Windows.Win32.Foundation; 11 | 12 | namespace ManagedGitLib 13 | { 14 | internal static class FileHelpers 15 | { 16 | #if NET6_0_OR_GREATER 17 | private static readonly bool IsWindows = OperatingSystem.IsWindowsVersionAtLeast(5, 1, 2600); 18 | #else 19 | private static readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); 20 | #endif 21 | 22 | /// 23 | /// Opens the file with a given path, if it exists. 24 | /// 25 | /// The path to the file. 26 | /// The stream to open to, if the file exists. 27 | /// if the file exists; otherwise . 28 | internal static bool TryOpen(string path, out FileStream? stream) 29 | { 30 | if (IsWindows) 31 | { 32 | var handle = PInvoke.CreateFile(path, FILE_ACCESS_FLAGS.FILE_GENERIC_READ, FILE_SHARE_MODE.FILE_SHARE_READ, lpSecurityAttributes: null, FILE_CREATION_DISPOSITION.OPEN_EXISTING, FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_NORMAL, null); 33 | 34 | if (!handle.IsInvalid) 35 | { 36 | var fileHandle = new SafeFileHandle(handle.DangerousGetHandle(), ownsHandle: true); 37 | handle.SetHandleAsInvalid(); 38 | stream = new FileStream(fileHandle, System.IO.FileAccess.Read); 39 | return true; 40 | } 41 | else 42 | { 43 | stream = null; 44 | return false; 45 | } 46 | } 47 | else 48 | { 49 | if (!File.Exists(path)) 50 | { 51 | stream = null; 52 | return false; 53 | } 54 | 55 | stream = File.OpenRead(path); 56 | return true; 57 | } 58 | } 59 | 60 | /// 61 | /// Opens the file with a given path, if it exists. 62 | /// 63 | /// The path to the file, as a null-terminated UTF-16 character array. 64 | /// The stream to open to, if the file exists. 65 | /// if the file exists; otherwise . 66 | internal static unsafe bool TryOpen(ReadOnlySpan path, [NotNullWhen(true)] out FileStream? stream) 67 | { 68 | if (IsWindows) 69 | { 70 | HANDLE handle; 71 | fixed (char* pPath = &path[0]) 72 | { 73 | handle = PInvoke.CreateFile(pPath, FILE_ACCESS_FLAGS.FILE_GENERIC_READ, FILE_SHARE_MODE.FILE_SHARE_READ, null, FILE_CREATION_DISPOSITION.OPEN_EXISTING, FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_NORMAL, default); 74 | } 75 | 76 | if (!handle.Equals(PInvoke.INVALID_HANDLE_VALUE)) 77 | { 78 | var fileHandle = new SafeFileHandle(handle, ownsHandle: true); 79 | stream = new FileStream(fileHandle, System.IO.FileAccess.Read); 80 | return true; 81 | } 82 | else 83 | { 84 | stream = null; 85 | return false; 86 | } 87 | } 88 | else 89 | { 90 | // Make sure to trim the trailing \0 91 | string fullPath = GetUtf16String(path.Slice(0, path.Length - 1)); 92 | 93 | if (!File.Exists(fullPath)) 94 | { 95 | stream = null; 96 | return false; 97 | } 98 | 99 | stream = File.OpenRead(fullPath); 100 | return true; 101 | } 102 | } 103 | 104 | private static unsafe string GetUtf16String(ReadOnlySpan chars) 105 | { 106 | fixed (char* pChars = chars) 107 | { 108 | return new string(pChars, 0, chars.Length); 109 | } 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /ManagedGitLib/GitException.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Runtime.Serialization; 5 | 6 | namespace ManagedGitLib 7 | { 8 | /// 9 | /// The exception which is thrown by the managed Git layer. 10 | /// 11 | [Serializable] 12 | public class GitException : Exception 13 | { 14 | /// 15 | /// Initializes a new instance of the class. 16 | /// 17 | public GitException() 18 | { 19 | } 20 | 21 | /// 22 | /// Initializes a new instance of the class. 23 | /// 24 | /// 25 | public GitException(string message) 26 | : base(message) 27 | { 28 | } 29 | 30 | /// 31 | /// Initializes a new instance of the with an 32 | /// error message and an inner message. 33 | /// 34 | /// 35 | /// A message which describes the error. 36 | /// 37 | /// 38 | /// The which caused this exception to be thrown. 39 | /// 40 | public GitException(string message, Exception innerException) 41 | : base(message, innerException) 42 | { 43 | } 44 | 45 | /// 46 | /// Initializes a new instance of the class. 47 | /// 48 | protected GitException(SerializationInfo info, StreamingContext context) 49 | : base(info, context) 50 | { 51 | this.ErrorCode = (ErrorCodes)info.GetUInt32(nameof(this.ErrorCode)); 52 | this.iSShallowClone = info.GetBoolean(nameof(this.iSShallowClone)); 53 | } 54 | 55 | /// 56 | /// Gets the error code for this exception. 57 | /// 58 | public ErrorCodes ErrorCode { get; set; } 59 | 60 | /// 61 | /// Gets a value indicating whether the exception was thrown from a shallow clone. 62 | /// 63 | public bool iSShallowClone { get; set; } 64 | 65 | /// 66 | public override void GetObjectData(SerializationInfo info, StreamingContext context) 67 | { 68 | base.GetObjectData(info, context); 69 | info.AddValue(nameof(this.ErrorCode), (int)this.ErrorCode); 70 | info.AddValue(nameof(this.iSShallowClone), this.iSShallowClone); 71 | } 72 | 73 | /// 74 | /// Describes specific error conditions that may warrant branching code paths. 75 | /// 76 | public enum ErrorCodes 77 | { 78 | /// 79 | /// No error code was specified. 80 | /// 81 | Unspecified = 0, 82 | 83 | /// 84 | /// An object could not be found. 85 | /// 86 | ObjectNotFound, 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /ManagedGitLib/GitObjectStream.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.IO; 5 | 6 | namespace ManagedGitLib 7 | { 8 | /// 9 | /// A which reads data stored in the Git object store. The data is stored 10 | /// as a gz-compressed stream, and is prefixed with the object type and data length. 11 | /// 12 | public class GitObjectStream : ZLibStream 13 | { 14 | /// 15 | /// Initializes a new instance of the class. 16 | /// 17 | /// 18 | /// The from which to read data. 19 | /// 20 | /// 21 | /// The expected object type of the git object. 22 | /// 23 | #pragma warning disable CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable. ObjectType is assigned in ReadObjectTypeAndLength. 24 | public GitObjectStream(Stream stream, string objectType) 25 | #pragma warning restore CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable. 26 | : base(stream, -1) 27 | { 28 | this.ReadObjectTypeAndLength(objectType); 29 | } 30 | 31 | /// 32 | /// Gets the object type of this Git object. 33 | /// 34 | public string ObjectType { get; private set; } 35 | 36 | /// 37 | public override bool CanRead => true; 38 | 39 | /// 40 | public override bool CanSeek => true; 41 | 42 | /// 43 | public override bool CanWrite => false; 44 | 45 | private void ReadObjectTypeAndLength(string objectType) 46 | { 47 | Span buffer = stackalloc byte[128]; 48 | this.Read(buffer.Slice(0, objectType.Length + 1)); 49 | 50 | var actualObjectType = GitRepository.GetString(buffer.Slice(0, objectType.Length)); 51 | this.ObjectType = actualObjectType; 52 | 53 | int headerLength = 0; 54 | long length = 0; 55 | 56 | while (headerLength < buffer.Length) 57 | { 58 | this.Read(buffer.Slice(headerLength, 1)); 59 | 60 | if (buffer[headerLength] == 0) 61 | { 62 | break; 63 | } 64 | 65 | // Direct conversion from ASCII to int 66 | length = (10 * length) + (buffer[headerLength] - (byte)'0'); 67 | 68 | headerLength += 1; 69 | } 70 | 71 | this.Initialize(length); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /ManagedGitLib/GitPackCache.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Diagnostics.CodeAnalysis; 5 | using System.IO; 6 | using System.Text; 7 | 8 | namespace ManagedGitLib 9 | { 10 | /// 11 | /// Represents a cache in which objects retrieved from a 12 | /// are cached. Caching these objects can be of interest, because retrieving 13 | /// data from a can be potentially expensive: the data is 14 | /// compressed and can be deltified. 15 | /// 16 | public abstract class GitPackCache : IDisposable 17 | { 18 | /// 19 | /// Attempts to retrieve a Git object from cache. 20 | /// 21 | /// 22 | /// The offset of the Git object in the Git pack. 23 | /// 24 | /// 25 | /// A which will be set to the cached Git object. 26 | /// 27 | /// 28 | /// if the object was found in cache; otherwise, 29 | /// . 30 | /// 31 | public abstract bool TryOpen(long offset, [NotNullWhen(true)] out Stream? stream); 32 | 33 | /// 34 | /// Gets statistics about the cache usage. 35 | /// 36 | /// 37 | /// A to which to write the statistics. 38 | /// 39 | public abstract void GetCacheStatistics(StringBuilder builder); 40 | 41 | /// 42 | /// Adds a Git object to this cache. 43 | /// 44 | /// 45 | /// The offset of the Git object in the Git pack. 46 | /// 47 | /// 48 | /// A which represents the object to add. This stream 49 | /// will be copied to the cache. 50 | /// 51 | /// 52 | /// A which represents the cached entry. 53 | /// 54 | public abstract Stream Add(long offset, Stream stream); 55 | 56 | /// 57 | public void Dispose() 58 | { 59 | this.Dispose(true); 60 | GC.SuppressFinalize(this); 61 | } 62 | 63 | /// 64 | /// Disposes of native and managed resources associated by this object. 65 | /// 66 | /// to dispose managed and native resources; to only dispose of native resources. 67 | protected virtual void Dispose(bool disposing) 68 | { 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /ManagedGitLib/GitPackIndexReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ManagedGitLib 4 | { 5 | /// 6 | /// Base class for classes which support reading data stored in a Git Pack file. 7 | /// 8 | /// 9 | public abstract class GitPackIndexReader : IDisposable 10 | { 11 | /// 12 | /// The header of the index file. 13 | /// 14 | protected static readonly byte[] Header = new byte[] { 0xff, 0x74, 0x4f, 0x63 }; 15 | 16 | /// 17 | /// Gets the offset of a Git object in the index file. 18 | /// 19 | /// 20 | /// The Git object Id of the Git object for which to get the offset. 21 | /// 22 | /// 23 | /// If found, the offset of the Git object in the index file; otherwise, 24 | /// . 25 | /// 26 | public long? GetOffset(GitObjectId objectId) 27 | { 28 | Span name = stackalloc byte[20]; 29 | objectId.CopyTo(name); 30 | (var offset, var _) = this.GetOffset(name); 31 | return offset; 32 | } 33 | 34 | /// 35 | /// Gets the offset of a Git object in the index file. 36 | /// 37 | /// 38 | /// A partial or full Git object id, in its binary representation. 39 | /// 40 | /// if ends with a byte whose last 4 bits are all zeros and not intended for inclusion in the search; otherwise. 41 | /// 42 | /// If found, the offset of the Git object in the index file; otherwise, 43 | /// . 44 | /// 45 | public abstract (long?, GitObjectId?) GetOffset(Span objectId, bool endsWithHalfByte = false); 46 | 47 | /// 48 | public abstract void Dispose(); 49 | } 50 | } -------------------------------------------------------------------------------- /ManagedGitLib/GitPackMemoryCache.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System.Collections.Generic; 4 | using System.Diagnostics.CodeAnalysis; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Text; 8 | 9 | namespace ManagedGitLib 10 | { 11 | /// 12 | /// 13 | /// The implements the abstract class. 14 | /// When a is added to the , it is wrapped in a 15 | /// . This stream allows for just-in-time, random, read-only 16 | /// access to the underlying data (which may deltafied and/or compressed). 17 | /// 18 | /// 19 | /// Whenever data is read from a , the call is forwarded to the 20 | /// underlying and cached in a . If the same data is read 21 | /// twice, it is read from the , rather than the underlying . 22 | /// 23 | /// 24 | /// and return 25 | /// objects which may operate on the same underlying , but independently maintain 26 | /// their state. 27 | /// 28 | /// 29 | public class GitPackMemoryCache : GitPackCache 30 | { 31 | private readonly Dictionary cache = new Dictionary(); 32 | 33 | /// 34 | public override Stream Add(long offset, Stream stream) 35 | { 36 | var cacheStream = new GitPackMemoryCacheStream(stream); 37 | this.cache.Add(offset, cacheStream); 38 | return new GitPackMemoryCacheViewStream(cacheStream); 39 | } 40 | 41 | /// 42 | public override bool TryOpen(long offset, [NotNullWhen(true)] out Stream? stream) 43 | { 44 | if (this.cache.TryGetValue(offset, out GitPackMemoryCacheStream? cacheStream)) 45 | { 46 | stream = new GitPackMemoryCacheViewStream(cacheStream!); 47 | return true; 48 | } 49 | 50 | stream = null; 51 | return false; 52 | } 53 | 54 | /// 55 | public override void GetCacheStatistics(StringBuilder builder) 56 | { 57 | builder.AppendLine($"{this.cache.Count} items in cache"); 58 | } 59 | 60 | /// 61 | protected override void Dispose(bool disposing) 62 | { 63 | if (disposing) 64 | { 65 | while (this.cache.Count > 0) 66 | { 67 | var key = this.cache.Keys.First(); 68 | var stream = this.cache[key]; 69 | stream.Dispose(); 70 | this.cache.Remove(key); 71 | } 72 | } 73 | 74 | base.Dispose(disposing); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /ManagedGitLib/GitPackMemoryCacheStream.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Buffers; 5 | using System.IO; 6 | 7 | namespace ManagedGitLib 8 | { 9 | internal class GitPackMemoryCacheStream : Stream 10 | { 11 | private Stream stream; 12 | private readonly MemoryStream cacheStream = new MemoryStream(); 13 | private long length; 14 | 15 | public GitPackMemoryCacheStream(Stream stream) 16 | { 17 | this.stream = stream ?? throw new ArgumentNullException(nameof(stream)); 18 | this.length = this.stream.Length; 19 | } 20 | 21 | public override bool CanRead => true; 22 | 23 | public override bool CanSeek => true; 24 | 25 | public override bool CanWrite => false; 26 | 27 | public override long Length => this.length; 28 | 29 | public override long Position 30 | { 31 | get => this.cacheStream.Position; 32 | set => throw new NotSupportedException(); 33 | } 34 | 35 | public override void Flush() 36 | { 37 | throw new NotSupportedException(); 38 | } 39 | 40 | #if NETSTANDARD2_0 41 | public int Read(Span buffer) 42 | #else 43 | /// 44 | public override int Read(Span buffer) 45 | #endif 46 | { 47 | if (this.cacheStream.Length < this.length 48 | && this.cacheStream.Position + buffer.Length > this.cacheStream.Length) 49 | { 50 | var currentPosition = this.cacheStream.Position; 51 | var toRead = (int)(buffer.Length - this.cacheStream.Length + this.cacheStream.Position); 52 | int actualRead = this.stream.Read(buffer.Slice(0, toRead)); 53 | this.cacheStream.Seek(0, SeekOrigin.End); 54 | this.cacheStream.Write(buffer.Slice(0, actualRead)); 55 | this.cacheStream.Seek(currentPosition, SeekOrigin.Begin); 56 | this.DisposeStreamIfRead(); 57 | } 58 | 59 | return this.cacheStream.Read(buffer); 60 | } 61 | 62 | public override int Read(byte[] buffer, int offset, int count) 63 | { 64 | return this.Read(buffer.AsSpan(offset, count)); 65 | } 66 | 67 | public override long Seek(long offset, SeekOrigin origin) 68 | { 69 | if (origin != SeekOrigin.Begin) 70 | { 71 | throw new NotSupportedException(); 72 | } 73 | 74 | if (offset > this.cacheStream.Length) 75 | { 76 | var toRead = (int)(offset - this.cacheStream.Length); 77 | byte[] buffer = ArrayPool.Shared.Rent(toRead); 78 | int read = this.stream.Read(buffer, 0, toRead); 79 | this.cacheStream.Seek(0, SeekOrigin.End); 80 | this.cacheStream.Write(buffer, 0, read); 81 | ArrayPool.Shared.Return(buffer); 82 | 83 | this.DisposeStreamIfRead(); 84 | return this.cacheStream.Position; 85 | } 86 | else 87 | { 88 | return this.cacheStream.Seek(offset, origin); 89 | } 90 | } 91 | 92 | public override void SetLength(long value) 93 | { 94 | throw new NotSupportedException(); 95 | } 96 | 97 | public override void Write(byte[] buffer, int offset, int count) 98 | { 99 | throw new NotSupportedException(); 100 | } 101 | 102 | protected override void Dispose(bool disposing) 103 | { 104 | if (disposing) 105 | { 106 | this.stream.Dispose(); 107 | this.cacheStream.Dispose(); 108 | } 109 | 110 | base.Dispose(disposing); 111 | } 112 | 113 | private void DisposeStreamIfRead() 114 | { 115 | if (this.cacheStream.Length == this.stream.Length) 116 | { 117 | this.stream.Dispose(); 118 | } 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /ManagedGitLib/GitPackMemoryCacheViewStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | 6 | namespace ManagedGitLib 7 | { 8 | internal class GitPackMemoryCacheViewStream : Stream 9 | { 10 | private readonly GitPackMemoryCacheStream baseStream; 11 | 12 | public GitPackMemoryCacheViewStream(GitPackMemoryCacheStream baseStream) 13 | { 14 | this.baseStream = baseStream ?? throw new ArgumentNullException(nameof(baseStream)); 15 | } 16 | 17 | public override bool CanRead => true; 18 | 19 | public override bool CanSeek => true; 20 | 21 | public override bool CanWrite => false; 22 | 23 | public override long Length => this.baseStream.Length; 24 | 25 | private long position; 26 | 27 | public override long Position 28 | { 29 | get => this.position; 30 | set => throw new NotSupportedException(); 31 | } 32 | 33 | public override void Flush() => throw new NotImplementedException(); 34 | 35 | public override int Read(byte[] buffer, int offset, int count) 36 | { 37 | return this.Read(buffer.AsSpan(offset, count)); 38 | } 39 | 40 | #if NETSTANDARD2_0 41 | public int Read(Span buffer) 42 | #else 43 | /// 44 | public override int Read(Span buffer) 45 | #endif 46 | { 47 | int read = 0; 48 | 49 | lock (this.baseStream) 50 | { 51 | if (this.baseStream.Position != this.position) 52 | { 53 | this.baseStream.Seek(this.position, SeekOrigin.Begin); 54 | } 55 | 56 | read = this.baseStream.Read(buffer); 57 | } 58 | 59 | this.position += read; 60 | return read; 61 | } 62 | 63 | public override long Seek(long offset, SeekOrigin origin) 64 | { 65 | if (origin != SeekOrigin.Begin) 66 | { 67 | throw new NotSupportedException(); 68 | } 69 | 70 | this.position = Math.Min(offset, this.Length); 71 | return this.position; 72 | } 73 | 74 | public override void SetLength(long value) => throw new NotSupportedException(); 75 | public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /ManagedGitLib/GitPackNullCache.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.IO; 5 | using System.Text; 6 | 7 | namespace ManagedGitLib 8 | { 9 | /// 10 | /// A no-op implementation of the class. 11 | /// 12 | public class GitPackNullCache : GitPackCache 13 | { 14 | /// 15 | /// Gets the default instance of the class. 16 | /// 17 | public static GitPackNullCache Instance { get; } = new GitPackNullCache(); 18 | 19 | /// 20 | public override Stream Add(long offset, Stream stream) 21 | { 22 | return stream; 23 | } 24 | 25 | /// 26 | public override bool TryOpen(long offset, [NotNullWhen(true)] out Stream? stream) 27 | { 28 | stream = null; 29 | return false; 30 | } 31 | 32 | /// 33 | public override void GetCacheStatistics(StringBuilder builder) 34 | { 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /ManagedGitLib/GitPackObjectType.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | namespace ManagedGitLib 4 | { 5 | internal enum GitPackObjectType 6 | { 7 | Invalid = 0, 8 | OBJ_COMMIT = 1, 9 | OBJ_TREE = 2, 10 | OBJ_BLOB = 3, 11 | OBJ_TAG = 4, 12 | OBJ_OFS_DELTA = 6, 13 | OBJ_REF_DELTA = 7, 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ManagedGitLib/GitPackPooledStream.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Diagnostics; 6 | using System.IO; 7 | 8 | namespace ManagedGitLib 9 | { 10 | /// 11 | /// A pooled , which wraps around a 12 | /// which will be returned to a pool 13 | /// instead of actually being closed when is called. 14 | /// 15 | public class GitPackPooledStream : Stream 16 | { 17 | private readonly Stream stream; 18 | private readonly Queue pool; 19 | 20 | /// 21 | /// Initializes a new instance of the class. 22 | /// 23 | /// 24 | /// The which is being pooled. 25 | /// 26 | /// 27 | /// A to which the stream will be returned. 28 | /// 29 | public GitPackPooledStream(Stream stream, Queue pool) 30 | { 31 | this.stream = stream ?? throw new ArgumentNullException(nameof(stream)); 32 | this.pool = pool ?? throw new ArgumentNullException(nameof(pool)); 33 | } 34 | 35 | /// 36 | /// Gets the underlying for this . 37 | /// 38 | public Stream BaseStream => this.stream; 39 | 40 | /// 41 | public override bool CanRead => this.stream.CanRead; 42 | 43 | /// 44 | public override bool CanSeek => this.stream.CanSeek; 45 | 46 | /// 47 | public override bool CanWrite => this.stream.CanWrite; 48 | 49 | /// 50 | public override long Length => this.stream.Length; 51 | 52 | /// 53 | public override long Position 54 | { 55 | get => this.stream.Position; 56 | set => this.stream.Position = value; 57 | } 58 | 59 | /// 60 | public override void Flush() 61 | { 62 | this.stream.Flush(); 63 | } 64 | 65 | #if !NETSTANDARD2_0 66 | /// 67 | public override int Read(Span buffer) 68 | { 69 | return this.stream.Read(buffer); 70 | } 71 | #endif 72 | 73 | /// 74 | public override int Read(byte[] buffer, int offset, int count) 75 | { 76 | return this.stream.Read(buffer, offset, count); 77 | } 78 | 79 | /// 80 | public override long Seek(long offset, SeekOrigin origin) 81 | { 82 | return this.stream.Seek(offset, origin); 83 | } 84 | 85 | /// 86 | public override void SetLength(long value) 87 | { 88 | this.stream.SetLength(value); 89 | } 90 | 91 | /// 92 | public override void Write(byte[] buffer, int offset, int count) 93 | { 94 | throw new NotSupportedException(); 95 | } 96 | 97 | /// 98 | protected override void Dispose(bool disposing) 99 | { 100 | this.pool.Enqueue(this); 101 | Debug.WriteLine("Returning stream to pool"); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /ManagedGitLib/GitPackReader.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Buffers.Binary; 5 | using System.Diagnostics; 6 | using System.IO; 7 | 8 | namespace ManagedGitLib 9 | { 10 | internal static class GitPackReader 11 | { 12 | private static readonly byte[] Signature = GitRepository.Encoding.GetBytes("PACK"); 13 | 14 | public static Stream GetObject(GitPack pack, Stream stream, long offset, string objectType, GitPackObjectType packObjectType) 15 | { 16 | if (pack is null) 17 | { 18 | throw new ArgumentNullException(nameof(pack)); 19 | } 20 | 21 | if (stream is null) 22 | { 23 | throw new ArgumentNullException(nameof(stream)); 24 | } 25 | 26 | // Read the signature 27 | #if DEBUG 28 | stream.Seek(0, SeekOrigin.Begin); 29 | Span buffer = stackalloc byte[12]; 30 | stream.ReadAll(buffer); 31 | 32 | Debug.Assert(buffer.Slice(0, 4).SequenceEqual(Signature)); 33 | 34 | var versionNumber = BinaryPrimitives.ReadInt32BigEndian(buffer.Slice(4, 4)); 35 | Debug.Assert(versionNumber == 2); 36 | 37 | var numberOfObjects = BinaryPrimitives.ReadInt32BigEndian(buffer.Slice(8, 4)); 38 | #endif 39 | 40 | stream.Seek(offset, SeekOrigin.Begin); 41 | 42 | var (type, decompressedSize) = ReadObjectHeader(stream); 43 | 44 | if (type == GitPackObjectType.OBJ_OFS_DELTA) 45 | { 46 | var baseObjectRelativeOffset = ReadVariableLengthInteger(stream); 47 | long baseObjectOffset = offset - baseObjectRelativeOffset; 48 | 49 | var deltaStream = new ZLibStream(stream, decompressedSize); 50 | var baseObjectStream = pack.GetObject(baseObjectOffset, objectType); 51 | 52 | return new GitPackDeltafiedStream(baseObjectStream, deltaStream); 53 | } 54 | else if (type == GitPackObjectType.OBJ_REF_DELTA) 55 | { 56 | Span baseObjectId = stackalloc byte[20]; 57 | stream.ReadAll(baseObjectId); 58 | 59 | Stream baseObject = pack.GetObjectFromRepository(GitObjectId.Parse(baseObjectId), objectType)!; 60 | var seekableBaseObject = new GitPackMemoryCacheStream(baseObject); 61 | 62 | var deltaStream = new ZLibStream(stream, decompressedSize); 63 | 64 | return new GitPackDeltafiedStream(seekableBaseObject, deltaStream); 65 | } 66 | 67 | // Tips for handling deltas: https://github.com/choffmeister/gitnet/blob/4d907623d5ce2d79a8875aee82e718c12a8aad0b/src/GitNet/GitPack.cs 68 | if (type != packObjectType) 69 | { 70 | throw new GitException($"An object of type {objectType} could not be located at offset {offset}.") { ErrorCode = GitException.ErrorCodes.ObjectNotFound }; 71 | } 72 | 73 | return new ZLibStream(stream, decompressedSize); 74 | } 75 | 76 | private static (GitPackObjectType, long) ReadObjectHeader(Stream stream) 77 | { 78 | Span value = stackalloc byte[1]; 79 | stream.Read(value); 80 | 81 | var type = (GitPackObjectType)((value[0] & 0b0111_0000) >> 4); 82 | long length = value[0] & 0b_1111; 83 | 84 | if ((value[0] & 0b1000_0000) == 0) 85 | { 86 | return (type, length); 87 | } 88 | 89 | int shift = 4; 90 | 91 | do 92 | { 93 | stream.Read(value); 94 | length = length | ((value[0] & (long)0b0111_1111) << shift); 95 | shift += 7; 96 | } while ((value[0] & 0b1000_0000) != 0); 97 | 98 | return (type, length); 99 | } 100 | 101 | private static long ReadVariableLengthInteger(Stream stream) 102 | { 103 | long offset = -1; 104 | int b; 105 | 106 | do 107 | { 108 | offset++; 109 | b = stream.ReadByte(); 110 | offset = (offset << 7) + (b & 127); 111 | } 112 | while ((b & (byte)128) != 0); 113 | 114 | return offset; 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /ManagedGitLib/GitReferenceReader.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.IO; 5 | 6 | namespace ManagedGitLib 7 | { 8 | internal class GitReferenceReader 9 | { 10 | private readonly static byte[] RefPrefix = GitRepository.Encoding.GetBytes("ref: "); 11 | 12 | public static object ReadReference(Stream stream) 13 | { 14 | Span reference = stackalloc byte[(int)stream.Length]; 15 | stream.ReadAll(reference); 16 | 17 | return ReadReference(reference); 18 | } 19 | 20 | public static object ReadReference(Span value) 21 | { 22 | if (value.Length == 41 && !value.StartsWith(RefPrefix)) 23 | { 24 | // Skip the trailing \n 25 | return GitObjectId.ParseHex(value.Slice(0, 40)); 26 | } 27 | else 28 | { 29 | if (!value.StartsWith(RefPrefix)) 30 | { 31 | throw new GitException(); 32 | } 33 | 34 | // Skip the terminating \n character 35 | return GitRepository.GetString(value.Slice(RefPrefix.Length, value.Length - RefPrefix.Length - 1)); 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /ManagedGitLib/GitSignature.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | 5 | namespace ManagedGitLib 6 | { 7 | /// 8 | /// Represents the signature of a Git committer or author. 9 | /// 10 | public struct GitSignature 11 | { 12 | /// 13 | /// Gets or sets the name of the committer or author. 14 | /// 15 | public string Name { get; set; } 16 | 17 | /// 18 | /// Gets or sets the e-mail address of the commiter or author. 19 | /// 20 | public string Email { get; set; } 21 | 22 | /// 23 | /// Gets or sets the date and time at which the commit was made. 24 | /// 25 | public DateTimeOffset Date { get; set; } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /ManagedGitLib/GitTag.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace ManagedGitLib 8 | { 9 | /// 10 | /// Represents a Git tag, both lightweight and annotated. 11 | /// 12 | public struct GitTag 13 | { 14 | /// 15 | /// Gets or sets the name of the tag 16 | /// 17 | public string Name { get; set; } 18 | 19 | /// 20 | /// Gets or sets ot the tag's target. 21 | /// 22 | public GitObjectId Target { get; set; } 23 | 24 | /// 25 | /// Gets or sets whether the tag is annotated 26 | /// 27 | public bool IsAnnotated { get; set; } 28 | 29 | /// 30 | /// Gets or sets a of the annotated tag. 31 | /// 32 | public GitObjectId Sha { get; set; } 33 | 34 | /// 35 | /// Gets or sets the target object type of the annotated tag. 36 | /// 37 | public string? TargetType { get; set; } 38 | 39 | /// 40 | /// Gets or sets the tagger of the annotated tag. 41 | /// 42 | public GitSignature? Tagger { get; set; } 43 | 44 | /// 45 | /// Gets otr sets the message of the annotated tag. 46 | /// 47 | public string? Message { get; set; } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /ManagedGitLib/GitTree.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System.Collections.Generic; 4 | 5 | namespace ManagedGitLib 6 | { 7 | /// 8 | /// Represents a git tree. 9 | /// 10 | public class GitTree 11 | { 12 | /// 13 | /// Gets an empty . 14 | /// 15 | public static GitTree Empty { get; } = new GitTree(); 16 | 17 | /// 18 | /// The Git object Id of this . 19 | /// 20 | public GitObjectId Sha { get; set; } 21 | 22 | /// 23 | /// Gets a dictionary which contains all entries in the current tree, accessible by name. 24 | /// 25 | public Dictionary Children { get; } = new Dictionary(); 26 | 27 | /// 28 | public override string ToString() 29 | { 30 | return $"Git tree: {this.Sha}"; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /ManagedGitLib/GitTreeEntry.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | namespace ManagedGitLib 4 | { 5 | /// 6 | /// Represents an individual entry in the Git tree. 7 | /// 8 | public class GitTreeEntry 9 | { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// 13 | /// 14 | /// The name of the entry. 15 | /// 16 | /// 17 | /// A value indicating whether the current entry is a file. 18 | /// 19 | /// 20 | /// The Git object Id of the blob or tree of the current entry. 21 | /// 22 | public GitTreeEntry(string name, bool isFile, GitObjectId sha) 23 | { 24 | this.Name = name; 25 | this.IsFile = isFile; 26 | this.Sha = sha; 27 | } 28 | 29 | /// 30 | /// Gets the name of the entry. 31 | /// 32 | public string Name { get; } 33 | 34 | /// 35 | /// Gets a value indicating whether the current entry is a file. 36 | /// 37 | public bool IsFile { get; } 38 | 39 | /// 40 | /// Gets the Git object Id of the blob or tree of the current entry. 41 | /// 42 | public GitObjectId Sha { get; } 43 | 44 | /// 45 | public override string ToString() 46 | { 47 | return this.Name; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /ManagedGitLib/GitTreeReader.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Buffers; 5 | using System.IO; 6 | 7 | namespace ManagedGitLib 8 | { 9 | internal static class GitTreeReader 10 | { 11 | public static GitTree Read(Stream stream, GitObjectId objectId) 12 | { 13 | byte[] buffer = ArrayPool.Shared.Rent((int)stream.Length); 14 | #if DEBUG 15 | Array.Clear(buffer, 0, buffer.Length); 16 | #endif 17 | 18 | GitTree value = new GitTree() 19 | { 20 | Sha = objectId, 21 | }; 22 | 23 | try 24 | { 25 | Span contents = buffer.AsSpan(0, (int)stream.Length); 26 | stream.ReadAll(contents); 27 | 28 | while (contents.Length > 0) 29 | { 30 | // Format: [mode] [file/ folder name]\0[SHA - 1 of referencing blob or tree] 31 | // Mode is either 6-bytes long (directory) or 7-bytes long (file). 32 | // If the entry is a file, the first byte is '1' 33 | var fileNameEnds = contents.IndexOf((byte)0); 34 | bool isFile = contents[0] == (byte)'1'; 35 | var modeLength = isFile ? 7 : 6; 36 | 37 | var currentName = contents.Slice(modeLength, fileNameEnds - modeLength); 38 | var currentObjectId = GitObjectId.Parse(contents.Slice(fileNameEnds + 1, 20)); 39 | 40 | var name = GitRepository.GetString(currentName); 41 | 42 | value.Children.Add( 43 | name, 44 | new GitTreeEntry(name, isFile, currentObjectId)); 45 | 46 | contents = contents.Slice(fileNameEnds + 1 + 20); 47 | } 48 | } 49 | finally 50 | { 51 | ArrayPool.Shared.Return(buffer); 52 | } 53 | 54 | return value; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /ManagedGitLib/GitTreeStreamingReader.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Buffers; 5 | using System.IO; 6 | 7 | namespace ManagedGitLib 8 | { 9 | /// 10 | /// Reads git tree objects. 11 | /// 12 | public class GitTreeStreamingReader 13 | { 14 | /// 15 | /// Finds a specific node in a git tree. 16 | /// 17 | /// 18 | /// A which represents the git tree. 19 | /// 20 | /// 21 | /// The name of the node to find, in it UTF-8 representation. 22 | /// 23 | /// 24 | /// The of the requested node. 25 | /// 26 | public static GitObjectId FindNode(Stream stream, ReadOnlySpan name) 27 | { 28 | byte[] buffer = ArrayPool.Shared.Rent((int)stream.Length); 29 | Span contents = new Span(buffer, 0, (int)stream.Length); 30 | 31 | stream.ReadAll(contents); 32 | 33 | GitObjectId value = GitObjectId.Empty; 34 | 35 | while (contents.Length > 0) 36 | { 37 | // Format: [mode] [file/ folder name]\0[SHA - 1 of referencing blob or tree] 38 | // Mode is either 6-bytes long (directory) or 7-bytes long (file). 39 | // If the entry is a file, the first byte is '1' 40 | var fileNameEnds = contents.IndexOf((byte)0); 41 | bool isFile = contents[0] == (byte)'1'; 42 | var modeLength = isFile ? 7 : 6; 43 | 44 | var currentName = contents.Slice(modeLength, fileNameEnds - modeLength); 45 | 46 | if (currentName.SequenceEqual(name)) 47 | { 48 | value = GitObjectId.Parse(contents.Slice(fileNameEnds + 1, 20)); 49 | break; 50 | } 51 | else 52 | { 53 | contents = contents.Slice(fileNameEnds + 1 + 20); 54 | } 55 | } 56 | 57 | ArrayPool.Shared.Return(buffer); 58 | 59 | return value; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /ManagedGitLib/ManagedGitLib.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0;netcoreapp3.1;net6.0 5 | enable 6 | 9.0 7 | true 8 | 9 | 10 | 11 | MIT 12 | A lightweight read-only git library written entirely in .NET 13 | https://github.com/GlebChili/ManagedGitLib 14 | Git 15 | git 16 | https://github.com/GlebChili/ManagedGitLib 17 | Copyright (c) Gleb Krasilich 18 | true 19 | README.md 20 | logo.png 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | all 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | true 43 | 44 | 45 | 46 | true 47 | Portable 48 | bin\$(Configuration)\$(TargetFramework)\GmodNET.API.xml 49 | 50 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /ManagedGitLib/MemoryMappedStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.IO.MemoryMappedFiles; 4 | 5 | namespace ManagedGitLib 6 | { 7 | /// 8 | /// Provides read-only, seekable access to a . 9 | /// 10 | public unsafe class MemoryMappedStream : Stream 11 | { 12 | private readonly MemoryMappedViewAccessor accessor; 13 | private readonly long length; 14 | private long position; 15 | private byte* ptr; 16 | private bool disposed; 17 | 18 | /// 19 | /// Initializes a new instance of the class. 20 | /// 21 | /// 22 | /// The accessor to the memory mapped stream. 23 | /// 24 | public MemoryMappedStream(MemoryMappedViewAccessor accessor) 25 | { 26 | this.accessor = accessor ?? throw new ArgumentNullException(nameof(accessor)); 27 | this.accessor.SafeMemoryMappedViewHandle.AcquirePointer(ref this.ptr); 28 | this.length = this.accessor.Capacity; 29 | } 30 | 31 | /// 32 | public override bool CanRead => true; 33 | 34 | /// 35 | public override bool CanSeek => true; 36 | 37 | /// 38 | public override bool CanWrite => false; 39 | 40 | /// 41 | public override long Length => this.length; 42 | 43 | /// 44 | public override long Position 45 | { 46 | get => this.position; 47 | set 48 | { 49 | this.position = (int)value; 50 | } 51 | } 52 | 53 | /// 54 | public override void Flush() 55 | { 56 | } 57 | 58 | /// 59 | public override int Read(byte[] buffer, int offset, int count) 60 | { 61 | if (this.disposed) 62 | { 63 | throw new ObjectDisposedException(nameof(MemoryMappedStream)); 64 | } 65 | 66 | int read = (int)Math.Min(count, this.length - this.position); 67 | 68 | new Span(this.ptr + this.position, read) 69 | .CopyTo(buffer.AsSpan(offset, count)); 70 | 71 | this.position += read; 72 | return read; 73 | } 74 | 75 | #if !NETSTANDARD2_0 76 | /// 77 | public override int Read(Span buffer) 78 | { 79 | if (this.disposed) 80 | { 81 | throw new ObjectDisposedException(nameof(MemoryMappedStream)); 82 | } 83 | 84 | int read = (int)Math.Min(buffer.Length, this.length - this.position); 85 | 86 | new Span(this.ptr + this.position, read) 87 | .CopyTo(buffer); 88 | 89 | this.position += read; 90 | return read; 91 | } 92 | #endif 93 | 94 | /// 95 | public override long Seek(long offset, SeekOrigin origin) 96 | { 97 | if (this.disposed) 98 | { 99 | throw new ObjectDisposedException(nameof(MemoryMappedStream)); 100 | } 101 | 102 | long newPosition = this.position; 103 | 104 | switch (origin) 105 | { 106 | case SeekOrigin.Begin: 107 | newPosition = offset; 108 | break; 109 | 110 | case SeekOrigin.Current: 111 | newPosition += offset; 112 | break; 113 | 114 | case SeekOrigin.End: 115 | throw new NotSupportedException(); 116 | } 117 | 118 | if (newPosition > this.length) 119 | { 120 | newPosition = this.length; 121 | } 122 | 123 | if (newPosition < 0) 124 | { 125 | throw new IOException("Attempted to seek before the start or beyond the end of the stream."); 126 | } 127 | 128 | this.position = newPosition; 129 | return this.position; 130 | } 131 | 132 | /// 133 | public override void SetLength(long value) 134 | { 135 | throw new NotSupportedException(); 136 | } 137 | 138 | /// 139 | public override void Write(byte[] buffer, int offset, int count) 140 | { 141 | throw new NotSupportedException(); 142 | } 143 | 144 | /// 145 | protected override void Dispose(bool disposing) 146 | { 147 | if (disposing) 148 | { 149 | this.accessor.SafeMemoryMappedViewHandle.ReleasePointer(); 150 | this.disposed = true; 151 | } 152 | 153 | base.Dispose(disposing); 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /ManagedGitLib/NativeMethods.txt: -------------------------------------------------------------------------------- 1 | CreateFile 2 | INVALID_HANDLE_VALUE 3 | -------------------------------------------------------------------------------- /ManagedGitLib/StreamExtensions.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Buffers; 5 | using System.IO; 6 | 7 | namespace ManagedGitLib 8 | { 9 | /// 10 | /// Provides extension methods for the class. 11 | /// 12 | public static class StreamExtensions 13 | { 14 | /// 15 | /// Reads data from a to fill a given buffer. 16 | /// 17 | /// 18 | /// The from which to read data. 19 | /// 20 | /// 21 | /// A buffer into which to store the data read. 22 | /// 23 | /// Thrown when the stream runs out of data before could be filled. 24 | public static void ReadAll(this Stream stream, Span buffer) 25 | { 26 | if (buffer.Length == 0) 27 | { 28 | return; 29 | } 30 | 31 | int totalBytesRead = 0; 32 | while (totalBytesRead < buffer.Length) 33 | { 34 | int bytesRead = stream.Read(buffer.Slice(totalBytesRead)); 35 | if (bytesRead == 0) 36 | { 37 | throw new EndOfStreamException(); 38 | } 39 | 40 | totalBytesRead += bytesRead; 41 | } 42 | } 43 | 44 | /// 45 | /// Reads an variable-length integer off a . 46 | /// 47 | /// 48 | /// The stream off which to read the variable-length integer. 49 | /// 50 | /// 51 | /// The requested value. 52 | /// 53 | /// Thrown when the stream runs out of data before the integer could be read. 54 | public static int ReadMbsInt(this Stream stream) 55 | { 56 | int value = 0; 57 | int currentBit = 0; 58 | int read; 59 | 60 | while (true) 61 | { 62 | read = stream.ReadByte(); 63 | if (read == -1) 64 | { 65 | throw new EndOfStreamException(); 66 | } 67 | 68 | value |= (read & 0b_0111_1111) << currentBit; 69 | currentBit += 7; 70 | 71 | if (read < 128) 72 | { 73 | break; 74 | } 75 | } 76 | 77 | return value; 78 | } 79 | 80 | #if NETSTANDARD2_0 81 | /// 82 | /// Reads a sequence of bytes from the current stream and advances the position within the stream by 83 | /// the number of bytes read. 84 | /// 85 | /// 86 | /// The from which to read the data. 87 | /// 88 | /// 89 | /// A region of memory. When this method returns, the contents of this region are replaced by the bytes 90 | /// read from the current source. 91 | /// 92 | /// 93 | /// The total number of bytes read into the buffer. This can be less than the number of bytes allocated 94 | /// in the buffer if that many bytes are not currently available, or zero (0) if the end of the stream 95 | /// has been reached. 96 | /// 97 | public static int Read(this Stream stream, Span span) 98 | { 99 | byte[]? buffer = null; 100 | 101 | try 102 | { 103 | buffer = ArrayPool.Shared.Rent(span.Length); 104 | int read = stream.Read(buffer, 0, span.Length); 105 | 106 | buffer.AsSpan(0, read).CopyTo(span); 107 | return read; 108 | } 109 | finally 110 | { 111 | ArrayPool.Shared.Return(buffer); 112 | } 113 | } 114 | 115 | /// 116 | /// Writes a sequence of bytes to the current stream and advances the current position within this stream 117 | /// by the number of bytes written. 118 | /// 119 | /// 120 | /// The to which to write the data. 121 | /// 122 | /// 123 | /// A region of memory. This method copies the contents of this region to the current stream. 124 | /// 125 | public static void Write(this Stream stream, Span span) 126 | { 127 | byte[]? buffer = null; 128 | 129 | try 130 | { 131 | buffer = ArrayPool.Shared.Rent(span.Length); 132 | span.CopyTo(buffer.AsSpan(0, span.Length)); 133 | 134 | stream.Write(buffer, 0, span.Length); 135 | } 136 | finally 137 | { 138 | ArrayPool.Shared.Return(buffer); 139 | } 140 | } 141 | 142 | internal static bool TryAdd(this System.Collections.Generic.IDictionary dictionary, TKey key, TValue value) 143 | { 144 | if (dictionary.ContainsKey(key)) 145 | { 146 | return false; 147 | } 148 | 149 | dictionary.Add(key, value); 150 | return true; 151 | } 152 | #endif 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /Metadata/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GlebChili/ManagedGitLib/d4ad0d2fe8ce137dcb311551c5b7df8bda67d702/Metadata/logo.png -------------------------------------------------------------------------------- /Metadata/version.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "0.2.0-alpha.1" 3 | } 4 | -------------------------------------------------------------------------------- /NerdBank.GitVersioning/AssemblyVersionOptionsConverter.cs: -------------------------------------------------------------------------------- 1 | namespace Nerdbank.GitVersioning 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Reflection; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using Newtonsoft.Json; 10 | using Newtonsoft.Json.Linq; 11 | 12 | internal class AssemblyVersionOptionsConverter : JsonConverter 13 | { 14 | private readonly bool includeDefaults; 15 | 16 | internal AssemblyVersionOptionsConverter(bool includeDefaults) 17 | { 18 | this.includeDefaults = includeDefaults; 19 | } 20 | 21 | public override bool CanConvert(Type objectType) 22 | { 23 | return typeof(VersionOptions.AssemblyVersionOptions).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo()); 24 | } 25 | 26 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 27 | { 28 | if (objectType.Equals(typeof(VersionOptions.AssemblyVersionOptions))) 29 | { 30 | if (reader.Value is string) 31 | { 32 | Version value; 33 | if (Version.TryParse((string)reader.Value, out value)) 34 | { 35 | return new VersionOptions.AssemblyVersionOptions(value); 36 | } 37 | } 38 | else if (reader.TokenType == JsonToken.StartObject) 39 | { 40 | // Temporarily remove ourselves from the serializer so we don't recurse infinitely. 41 | serializer.Converters.Remove(this); 42 | var result = serializer.Deserialize(reader); 43 | serializer.Converters.Add(this); 44 | return result; 45 | } 46 | } 47 | 48 | throw new NotSupportedException(); 49 | } 50 | 51 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 52 | { 53 | var data = value as VersionOptions.AssemblyVersionOptions; 54 | if (data is not null) 55 | { 56 | if (data.PrecisionOrDefault == VersionOptions.DefaultVersionPrecision && !this.includeDefaults) 57 | { 58 | serializer.Serialize(writer, data.Version); 59 | return; 60 | } 61 | else 62 | { 63 | // Temporarily remove ourselves from the serializer so we don't recurse infinitely. 64 | serializer.Converters.Remove(this); 65 | serializer.Serialize(writer, data); 66 | serializer.Converters.Add(this); 67 | return; 68 | } 69 | } 70 | 71 | throw new NotSupportedException(); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /NerdBank.GitVersioning/CloudBuild.cs: -------------------------------------------------------------------------------- 1 | namespace Nerdbank.GitVersioning 2 | { 3 | using System; 4 | using System.Linq; 5 | using CloudBuildServices; 6 | 7 | /// 8 | /// Provides access to cloud build providers. 9 | /// 10 | public static class CloudBuild 11 | { 12 | /// 13 | /// An array of cloud build systems we support. 14 | /// 15 | public static readonly ICloudBuild[] SupportedCloudBuilds = new ICloudBuild[] { 16 | new AppVeyor(), 17 | new VisualStudioTeamServices(), 18 | new GitHubActions(), 19 | new TeamCity(), 20 | new AtlassianBamboo(), 21 | new Jenkins(), 22 | new GitLab(), 23 | new Travis(), 24 | new SpaceAutomation(), 25 | }; 26 | 27 | /// 28 | /// Gets the cloud build provider that applies to this build, if any. 29 | /// 30 | public static ICloudBuild Active => SupportedCloudBuilds.FirstOrDefault(cb => cb.IsApplicable); 31 | 32 | /// 33 | /// Gets the specified string, prefixing it with some value if it is non-empty and lacks the prefix. 34 | /// 35 | /// The prefix that should be included in the returned value. 36 | /// The value to prefix. 37 | /// The provided, with prepended 38 | /// if the value doesn't already start with that string and the value is non-empty. 39 | internal static string ShouldStartWith(string value, string prefix) 40 | { 41 | return 42 | string.IsNullOrEmpty(value) ? value : 43 | value.StartsWith(prefix, StringComparison.Ordinal) ? value : 44 | prefix + value; 45 | } 46 | 47 | /// 48 | /// Gets the specified string if it starts with a given prefix; otherwise null. 49 | /// 50 | /// The value to return. 51 | /// The prefix to check for. 52 | /// if it starts with ; otherwise null. 53 | internal static string IfStartsWith(string value, string prefix) => value is object && value.StartsWith(prefix, StringComparison.Ordinal) ? value : null; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /NerdBank.GitVersioning/CloudBuildServices/AppVeyor.cs: -------------------------------------------------------------------------------- 1 | namespace Nerdbank.GitVersioning.CloudBuildServices 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.ComponentModel; 6 | using System.Diagnostics; 7 | using System.IO; 8 | 9 | /// 10 | /// 11 | /// 12 | /// 13 | /// The AppVeyor-specific properties referenced here are documented here: 14 | /// http://www.appveyor.com/docs/environment-variables 15 | /// 16 | internal class AppVeyor : ICloudBuild 17 | { 18 | /// 19 | /// 20 | /// 21 | /// 22 | /// AppVeyor's branch variable is the target branch of a PR, which is *NOT* to be misinterpreted 23 | /// as building the target branch itself. So only set the branch built property if it's not a PR. 24 | /// 25 | public string BuildingBranch => !this.IsPullRequest && !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("APPVEYOR_REPO_BRANCH")) 26 | ? $"refs/heads/{Environment.GetEnvironmentVariable("APPVEYOR_REPO_BRANCH")}" 27 | : null; 28 | 29 | public string BuildingRef => null; 30 | 31 | public string BuildingTag => string.Equals("true", Environment.GetEnvironmentVariable("APPVEYOR_REPO_TAG"), StringComparison.OrdinalIgnoreCase) 32 | ? $"refs/tags/{Environment.GetEnvironmentVariable("APPVEYOR_REPO_TAG_NAME")}" 33 | : null; 34 | 35 | public string GitCommitId => null; 36 | 37 | public bool IsApplicable => !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("APPVEYOR")); 38 | 39 | public bool IsPullRequest => !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("APPVEYOR_PULL_REQUEST_NUMBER")); 40 | 41 | public IReadOnlyDictionary SetCloudBuildNumber(string buildNumber, TextWriter stdout, TextWriter stderr) 42 | { 43 | // We ignore exit code so as to not fail the build when the cloud build number is not unique. 44 | RunAppveyor($"UpdateBuild -Version \"{buildNumber}\"", stdout, stderr); 45 | return new Dictionary(); 46 | } 47 | 48 | public IReadOnlyDictionary SetCloudBuildVariable(string name, string value, TextWriter stdout, TextWriter stderr) 49 | { 50 | RunAppveyor($"SetVariable -Name {name} -Value \"{value}\"", stdout, stderr); 51 | return new Dictionary(); 52 | } 53 | 54 | private static void RunAppveyor(string args, TextWriter stdout, TextWriter stderr) 55 | { 56 | try 57 | { 58 | // Skip this if this build is running in our own unit tests, since that can 59 | // mess with AppVeyor's actual build information. 60 | if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("_NBGV_UnitTest"))) 61 | { 62 | Process.Start("appveyor", args) 63 | .WaitForExit(); 64 | } 65 | } 66 | catch (Win32Exception ex) when ((uint)ex.HResult == 0x80004005) 67 | { 68 | (stderr ?? Console.Error).WriteLine("Could not find appveyor tool to set cloud build variable."); 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /NerdBank.GitVersioning/CloudBuildServices/AtlassianBamboo.cs: -------------------------------------------------------------------------------- 1 | namespace Nerdbank.GitVersioning.CloudBuildServices 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | 7 | /// 8 | /// 9 | /// 10 | /// 11 | /// The Bamboo-specific properties referenced here are documented here: 12 | /// https://confluence.atlassian.com/bamboo/bamboo-variables-289277087.html 13 | /// 14 | internal class AtlassianBamboo : ICloudBuild 15 | { 16 | public bool IsPullRequest => false; 17 | 18 | public string BuildingTag => null; 19 | 20 | public string BuildingBranch => CloudBuild.ShouldStartWith(Environment.GetEnvironmentVariable("bamboo.planRepository.branch"), "refs/heads/"); 21 | 22 | public string BuildingRef => this.BuildingBranch; 23 | 24 | public string GitCommitId => Environment.GetEnvironmentVariable("bamboo.planRepository.revision"); 25 | 26 | public bool IsApplicable => !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("bamboo.buildKey")); 27 | 28 | public IReadOnlyDictionary SetCloudBuildNumber(string buildNumber, TextWriter stdout, TextWriter stderr) 29 | { 30 | return new Dictionary(); 31 | } 32 | 33 | public IReadOnlyDictionary SetCloudBuildVariable(string name, string value, TextWriter stdout, TextWriter stderr) 34 | { 35 | return new Dictionary(); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /NerdBank.GitVersioning/CloudBuildServices/GitHubActions.cs: -------------------------------------------------------------------------------- 1 | namespace Nerdbank.GitVersioning.CloudBuildServices 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using Nerdbank.GitVersioning; 7 | 8 | internal class GitHubActions : ICloudBuild 9 | { 10 | public bool IsApplicable => Environment.GetEnvironmentVariable("GITHUB_ACTIONS") == "true"; 11 | 12 | public bool IsPullRequest => Environment.GetEnvironmentVariable("GITHUB_EVENT_NAME") == "PullRequestEvent"; 13 | 14 | public string BuildingBranch => (BuildingRef?.StartsWith("refs/heads/") ?? false) ? BuildingRef : null; 15 | 16 | public string BuildingTag => (BuildingRef?.StartsWith("refs/tags/") ?? false) ? BuildingRef : null; 17 | 18 | public string GitCommitId => Environment.GetEnvironmentVariable("GITHUB_SHA"); 19 | 20 | private static string BuildingRef => Environment.GetEnvironmentVariable("GITHUB_REF"); 21 | 22 | private static string EnvironmentFile => Environment.GetEnvironmentVariable("GITHUB_ENV"); 23 | 24 | public IReadOnlyDictionary SetCloudBuildNumber(string buildNumber, TextWriter stdout, TextWriter stderr) 25 | { 26 | return new Dictionary(); 27 | } 28 | 29 | public IReadOnlyDictionary SetCloudBuildVariable(string name, string value, TextWriter stdout, TextWriter stderr) 30 | { 31 | Utilities.FileOperationWithRetry(() => File.AppendAllText(EnvironmentFile, $"{Environment.NewLine}{name}={value}{Environment.NewLine}")); 32 | return GetDictionaryFor(name, value); 33 | } 34 | 35 | private static IReadOnlyDictionary GetDictionaryFor(string variableName, string value) 36 | { 37 | return new Dictionary(StringComparer.OrdinalIgnoreCase) 38 | { 39 | { GetEnvironmentVariableNameForVariable(variableName), value }, 40 | }; 41 | } 42 | 43 | private static string GetEnvironmentVariableNameForVariable(string name) => name.ToUpperInvariant().Replace('.', '_'); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /NerdBank.GitVersioning/CloudBuildServices/GitLab.cs: -------------------------------------------------------------------------------- 1 | namespace Nerdbank.GitVersioning.CloudBuildServices 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | 7 | /// 8 | /// 9 | /// 10 | /// 11 | /// The GitLab-specific properties referenced here are documented here: 12 | /// https://docs.gitlab.com/ce/ci/variables/README.html 13 | /// 14 | internal class GitLab : ICloudBuild 15 | { 16 | public string BuildingBranch => 17 | Environment.GetEnvironmentVariable("CI_COMMIT_TAG") is null ? 18 | $"refs/heads/{Environment.GetEnvironmentVariable("CI_COMMIT_REF_NAME")}" : null; 19 | 20 | public string BuildingRef => this.BuildingBranch ?? this.BuildingTag; 21 | 22 | public string BuildingTag => 23 | Environment.GetEnvironmentVariable("CI_COMMIT_TAG") is not null ? 24 | $"refs/tags/{Environment.GetEnvironmentVariable("CI_COMMIT_TAG")}" : null; 25 | 26 | public string GitCommitId => Environment.GetEnvironmentVariable("CI_COMMIT_SHA"); 27 | 28 | public bool IsApplicable => !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("GITLAB_CI")); 29 | 30 | public bool IsPullRequest => false; 31 | 32 | public IReadOnlyDictionary SetCloudBuildNumber(string buildNumber, TextWriter stdout, TextWriter stderr) 33 | { 34 | return new Dictionary(); 35 | } 36 | 37 | public IReadOnlyDictionary SetCloudBuildVariable(string name, string value, TextWriter stdout, TextWriter stderr) 38 | { 39 | return new Dictionary(); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /NerdBank.GitVersioning/CloudBuildServices/Jenkins.cs: -------------------------------------------------------------------------------- 1 | namespace Nerdbank.GitVersioning.CloudBuildServices 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Text; 7 | 8 | /// 9 | /// The Jenkins-specific properties referenced here are documented here: 10 | /// https://wiki.jenkins-ci.org/display/JENKINS/Git+Plugin#GitPlugin-Environmentvariables 11 | /// 12 | internal class Jenkins : ICloudBuild 13 | { 14 | private static readonly Encoding UTF8NoBOM = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); 15 | 16 | public string BuildingTag => null; 17 | 18 | public bool IsPullRequest => false; 19 | 20 | public string BuildingBranch => CloudBuild.ShouldStartWith(Branch, "refs/heads/"); 21 | 22 | public string GitCommitId => Environment.GetEnvironmentVariable("GIT_COMMIT"); 23 | 24 | public bool IsApplicable => !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("JENKINS_URL")); 25 | 26 | private static string Branch => 27 | Environment.GetEnvironmentVariable("GIT_LOCAL_BRANCH") 28 | ?? Environment.GetEnvironmentVariable("GIT_BRANCH"); 29 | 30 | public IReadOnlyDictionary SetCloudBuildNumber(string buildNumber, TextWriter stdout, TextWriter stderr) 31 | { 32 | WriteVersionFile(buildNumber); 33 | 34 | stdout.WriteLine($"## GIT_VERSION: {buildNumber}"); 35 | 36 | return new Dictionary 37 | { 38 | { "GIT_VERSION", buildNumber } 39 | }; 40 | } 41 | 42 | public IReadOnlyDictionary SetCloudBuildVariable(string name, string value, TextWriter stdout, TextWriter stderr) 43 | { 44 | return new Dictionary 45 | { 46 | { name, value } 47 | }; 48 | } 49 | 50 | private static void WriteVersionFile(string buildNumber) 51 | { 52 | var workspacePath = Environment.GetEnvironmentVariable("WORKSPACE"); 53 | 54 | if (string.IsNullOrEmpty(workspacePath)) 55 | { 56 | return; 57 | } 58 | 59 | var versionFilePath = Path.Combine(workspacePath, "jenkins_build_number.txt"); 60 | 61 | Utilities.FileOperationWithRetry(() => File.WriteAllText(versionFilePath, buildNumber, UTF8NoBOM)); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /NerdBank.GitVersioning/CloudBuildServices/SpaceAutomation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | 5 | namespace Nerdbank.GitVersioning.CloudBuildServices 6 | { 7 | /// 8 | /// SpaceAutomation CI build support. 9 | /// 10 | /// 11 | /// The SpaceAutomation-specific properties referenced here are documented here: 12 | /// https://www.jetbrains.com/help/space/automation-environment-variables.html 13 | /// 14 | internal class SpaceAutomation : ICloudBuild 15 | { 16 | public string BuildingBranch => CloudBuild.IfStartsWith(BuildingRef, "refs/heads/"); 17 | 18 | public string BuildingTag => CloudBuild.IfStartsWith(BuildingRef, "refs/tags/"); 19 | 20 | public string GitCommitId => Environment.GetEnvironmentVariable("JB_SPACE_GIT_REVISION"); 21 | 22 | public bool IsApplicable => this.GitCommitId is not null; 23 | 24 | public bool IsPullRequest => false; 25 | 26 | private static string BuildingRef => Environment.GetEnvironmentVariable("JB_SPACE_GIT_BRANCH"); 27 | 28 | public IReadOnlyDictionary SetCloudBuildNumber(string buildNumber, TextWriter stdout, TextWriter stderr) 29 | { 30 | return new Dictionary(); 31 | } 32 | 33 | public IReadOnlyDictionary SetCloudBuildVariable(string name, string value, TextWriter stdout, TextWriter stderr) 34 | { 35 | return new Dictionary(); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /NerdBank.GitVersioning/CloudBuildServices/TeamCity.cs: -------------------------------------------------------------------------------- 1 | namespace Nerdbank.GitVersioning.CloudBuildServices 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | 7 | /// 8 | /// TeamCity CI build support. 9 | /// 10 | /// 11 | /// The TeamCity-specific properties referenced here are documented here: 12 | /// https://www.jetbrains.com/help/teamcity/predefined-build-parameters.html 13 | /// 14 | internal class TeamCity : ICloudBuild 15 | { 16 | public string BuildingBranch => CloudBuild.IfStartsWith(BuildingRef, "refs/heads/"); 17 | 18 | public string BuildingTag => CloudBuild.IfStartsWith(BuildingRef, "refs/tags/"); 19 | 20 | public string GitCommitId => Environment.GetEnvironmentVariable("BUILD_VCS_NUMBER"); 21 | 22 | public bool IsApplicable => this.GitCommitId is not null; 23 | 24 | public bool IsPullRequest => false; 25 | 26 | private static string BuildingRef => Environment.GetEnvironmentVariable("BUILD_GIT_BRANCH"); 27 | 28 | public IReadOnlyDictionary SetCloudBuildNumber(string buildNumber, TextWriter stdout, TextWriter stderr) 29 | { 30 | (stdout ?? Console.Out).WriteLine($"##teamcity[buildNumber '{buildNumber}']"); 31 | return new Dictionary(); 32 | } 33 | 34 | public IReadOnlyDictionary SetCloudBuildVariable(string name, string value, TextWriter stdout, TextWriter stderr) 35 | { 36 | (stdout ?? Console.Out).WriteLine($"##teamcity[setParameter name='{name}' value='{value}']"); 37 | (stdout ?? Console.Out).WriteLine($"##teamcity[setParameter name='system.{name}' value='{value}']"); 38 | 39 | return new Dictionary(); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /NerdBank.GitVersioning/CloudBuildServices/Travis.cs: -------------------------------------------------------------------------------- 1 | namespace Nerdbank.GitVersioning.CloudBuildServices 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | 7 | /// 8 | /// Travis CI build support. 9 | /// 10 | /// 11 | /// The Travis CI environment variables referenced here are documented here: 12 | /// https://docs.travis-ci.com/user/environment-variables/#default-environment-variables 13 | /// 14 | internal class Travis: ICloudBuild 15 | { 16 | // TRAVIS_BRANCH can reference a branch or a tag, so make sure it starts with refs/heads 17 | public string BuildingBranch => CloudBuild.ShouldStartWith(Environment.GetEnvironmentVariable("TRAVIS_BRANCH"), "refs/heads/"); 18 | 19 | public string BuildingTag => Environment.GetEnvironmentVariable("TRAVIS_TAG"); 20 | 21 | public string GitCommitId => Environment.GetEnvironmentVariable("TRAVIS_COMMIT"); 22 | 23 | public bool IsApplicable => !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("TRAVIS")); 24 | 25 | public bool IsPullRequest => !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("TRAVIS_PULL_REQUEST_BRANCH")); 26 | 27 | public IReadOnlyDictionary SetCloudBuildNumber(string buildNumber, TextWriter stdout, TextWriter stderr) 28 | { 29 | return new Dictionary(); 30 | } 31 | 32 | public IReadOnlyDictionary SetCloudBuildVariable(string name, string value, TextWriter stdout, TextWriter stderr) 33 | { 34 | return new Dictionary(); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /NerdBank.GitVersioning/CloudBuildServices/VisualStudioTeamServices.cs: -------------------------------------------------------------------------------- 1 | namespace Nerdbank.GitVersioning.CloudBuildServices 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | 7 | /// 8 | /// 9 | /// 10 | /// 11 | /// The VSTS-specific properties referenced here are documented here: 12 | /// https://msdn.microsoft.com/en-us/Library/vs/alm/Build/scripts/variables 13 | /// 14 | internal class VisualStudioTeamServices : ICloudBuild 15 | { 16 | public bool IsPullRequest => BuildingRef?.StartsWith("refs/pull/") ?? false; 17 | 18 | public string BuildingTag => CloudBuild.IfStartsWith(BuildingRef, "refs/tags/"); 19 | 20 | public string BuildingBranch => CloudBuild.IfStartsWith(BuildingRef, "refs/heads/"); 21 | 22 | public string GitCommitId => null; 23 | 24 | public bool IsApplicable => !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("SYSTEM_TEAMPROJECTID")); 25 | 26 | private static string BuildingRef => Environment.GetEnvironmentVariable("BUILD_SOURCEBRANCH"); 27 | 28 | public IReadOnlyDictionary SetCloudBuildNumber(string buildNumber, TextWriter stdout, TextWriter stderr) 29 | { 30 | (stdout ?? Console.Out).WriteLine($"##vso[build.updatebuildnumber]{buildNumber}"); 31 | return GetDictionaryFor("Build.BuildNumber", buildNumber); 32 | } 33 | 34 | public IReadOnlyDictionary SetCloudBuildVariable(string name, string value, TextWriter stdout, TextWriter stderr) 35 | { 36 | Utilities.FileOperationWithRetry(() => 37 | (stdout ?? Console.Out).WriteLine($"##vso[task.setvariable variable={name};]{value}")); 38 | return GetDictionaryFor(name, value); 39 | } 40 | 41 | private static IReadOnlyDictionary GetDictionaryFor(string variableName, string value) 42 | { 43 | return new Dictionary(StringComparer.OrdinalIgnoreCase) 44 | { 45 | { GetEnvironmentVariableNameForVariable(variableName), value }, 46 | }; 47 | } 48 | 49 | private static string GetEnvironmentVariableNameForVariable(string name) => name.ToUpperInvariant().Replace('.', '_'); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /NerdBank.GitVersioning/FilterPathJsonConverter.cs: -------------------------------------------------------------------------------- 1 | namespace Nerdbank.GitVersioning 2 | { 3 | using System; 4 | using System.Reflection; 5 | using Newtonsoft.Json; 6 | 7 | internal class FilterPathJsonConverter : JsonConverter 8 | { 9 | private readonly string repoRelativeBaseDirectory; 10 | 11 | public FilterPathJsonConverter(string repoRelativeBaseDirectory) 12 | { 13 | this.repoRelativeBaseDirectory = repoRelativeBaseDirectory; 14 | } 15 | 16 | public override bool CanConvert(Type objectType) => typeof(FilterPath).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo()); 17 | 18 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 19 | { 20 | if (objectType != typeof(FilterPath) || !(reader.Value is string value)) 21 | { 22 | throw new NotSupportedException(); 23 | } 24 | 25 | if (this.repoRelativeBaseDirectory is null) 26 | { 27 | throw new ArgumentNullException(nameof(this.repoRelativeBaseDirectory), $"Base directory must not be null to be able to deserialize filter paths. Ensure that one was passed to {nameof(VersionOptions.GetJsonSettings)}, and that the version.json file is being written to a Git repository."); 28 | } 29 | 30 | return new FilterPath(value, this.repoRelativeBaseDirectory); 31 | } 32 | 33 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 34 | { 35 | if (!(value is FilterPath filterPath)) 36 | { 37 | throw new NotSupportedException(); 38 | } 39 | 40 | if (this.repoRelativeBaseDirectory is null) 41 | { 42 | throw new ArgumentNullException(nameof(this.repoRelativeBaseDirectory), $"Base directory must not be null to be able to serialize filter paths. Ensure that one was passed to {nameof(VersionOptions.GetJsonSettings)}, and that the version.json file is being written to a Git repository."); 43 | } 44 | 45 | writer.WriteValue(filterPath.ToPathSpec(this.repoRelativeBaseDirectory)); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /NerdBank.GitVersioning/GitException.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Runtime.Serialization; 5 | 6 | namespace Nerdbank.GitVersioning 7 | { 8 | /// 9 | /// The exception which is thrown by the managed Git layer. 10 | /// 11 | [Serializable] 12 | public class GitException : Exception 13 | { 14 | /// 15 | /// Initializes a new instance of the class. 16 | /// 17 | public GitException() 18 | { 19 | } 20 | 21 | /// 22 | /// Initializes a new instance of the class. 23 | /// 24 | /// 25 | public GitException(string message) 26 | : base(message) 27 | { 28 | } 29 | 30 | /// 31 | /// Initializes a new instance of the with an 32 | /// error message and an inner message. 33 | /// 34 | /// 35 | /// A message which describes the error. 36 | /// 37 | /// 38 | /// The which caused this exception to be thrown. 39 | /// 40 | public GitException(string message, Exception innerException) 41 | : base(message, innerException) 42 | { 43 | } 44 | 45 | /// 46 | /// Initializes a new instance of the class. 47 | /// 48 | protected GitException(SerializationInfo info, StreamingContext context) 49 | : base(info, context) 50 | { 51 | this.ErrorCode = (ErrorCodes)info.GetUInt32(nameof(this.ErrorCode)); 52 | this.iSShallowClone = info.GetBoolean(nameof(this.iSShallowClone)); 53 | } 54 | 55 | /// 56 | /// Gets the error code for this exception. 57 | /// 58 | public ErrorCodes ErrorCode { get; set; } 59 | 60 | /// 61 | /// Gets a value indicating whether the exception was thrown from a shallow clone. 62 | /// 63 | public bool iSShallowClone { get; set; } 64 | 65 | /// 66 | public override void GetObjectData(SerializationInfo info, StreamingContext context) 67 | { 68 | base.GetObjectData(info, context); 69 | info.AddValue(nameof(this.ErrorCode), (int)this.ErrorCode); 70 | info.AddValue(nameof(this.iSShallowClone), this.iSShallowClone); 71 | } 72 | 73 | /// 74 | /// Describes specific error conditions that may warrant branching code paths. 75 | /// 76 | public enum ErrorCodes 77 | { 78 | /// 79 | /// No error code was specified. 80 | /// 81 | Unspecified = 0, 82 | 83 | /// 84 | /// An object could not be found. 85 | /// 86 | ObjectNotFound, 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /NerdBank.GitVersioning/ICloudBuild.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | namespace Nerdbank.GitVersioning 4 | { 5 | using System.Collections.Generic; 6 | using System.IO; 7 | 8 | /// 9 | /// Defines cloud build provider functionality. 10 | /// 11 | public interface ICloudBuild 12 | { 13 | /// 14 | /// Gets a value indicating whether the active cloud build matches what this instance supports. 15 | /// 16 | bool IsApplicable { get; } 17 | 18 | /// 19 | /// Gets a value indicating whether a cloud build is validating a pull request. 20 | /// 21 | bool IsPullRequest { get; } 22 | 23 | /// 24 | /// Gets the branch being built by a cloud build, if applicable. 25 | /// 26 | string? BuildingBranch { get; } 27 | 28 | /// 29 | /// Gets the tag being built by a cloud build, if applicable. 30 | /// 31 | string? BuildingTag { get; } 32 | 33 | /// 34 | /// Gets the git commit ID being built by a cloud build, if applicable. 35 | /// 36 | string? GitCommitId { get; } 37 | 38 | /// 39 | /// Sets the build number for the cloud build, if supported. 40 | /// 41 | /// The build number to set. 42 | /// An optional redirection for what should be written to the standard out stream. 43 | /// An optional redirection for what should be written to the standard error stream. 44 | /// A dictionary of environment/build variables that the caller should set to update the environment to match the new settings. 45 | IReadOnlyDictionary SetCloudBuildNumber(string buildNumber, TextWriter? stdout, TextWriter? stderr); 46 | 47 | /// 48 | /// Sets a cloud build variable, if supported. 49 | /// 50 | /// The name of the variable. 51 | /// The value for the variable. 52 | /// An optional redirection for what should be written to the standard out stream. 53 | /// An optional redirection for what should be written to the standard error stream. 54 | /// A dictionary of environment/build variables that the caller should set to update the environment to match the new settings. 55 | IReadOnlyDictionary SetCloudBuildVariable(string name, string value, TextWriter? stdout, TextWriter? stderr); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /NerdBank.GitVersioning/ManagedGit/DeltaInstruction.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | namespace Nerdbank.GitVersioning.ManagedGit 4 | { 5 | /// 6 | /// Represents an instruction in a deltified stream. 7 | /// 8 | /// 9 | public struct DeltaInstruction 10 | { 11 | /// 12 | /// Gets or sets the type of the current instruction. 13 | /// 14 | public DeltaInstructionType InstructionType; 15 | 16 | /// 17 | /// If the is , 18 | /// the offset of the base stream to start copying from. 19 | /// 20 | public int Offset; 21 | 22 | /// 23 | /// The number of bytes to copy or insert. 24 | /// 25 | public int Size; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /NerdBank.GitVersioning/ManagedGit/DeltaInstructionType.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | namespace Nerdbank.GitVersioning.ManagedGit 4 | { 5 | /// 6 | /// Enumerates the various instruction types which can be found in a deltafied stream. 7 | /// 8 | /// 9 | public enum DeltaInstructionType 10 | { 11 | /// 12 | /// Instructs the caller to insert a new byte range into the object. 13 | /// 14 | Insert = 0, 15 | 16 | /// 17 | /// Instructs the caller to copy a byte range from the source object. 18 | /// 19 | Copy = 1, 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /NerdBank.GitVersioning/ManagedGit/FileHelpers.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Diagnostics.CodeAnalysis; 5 | using System.IO; 6 | using System.Runtime.InteropServices; 7 | using Microsoft.Win32.SafeHandles; 8 | using Windows.Win32; 9 | using Windows.Win32.Storage.FileSystem; 10 | using Windows.Win32.System.SystemServices; 11 | 12 | namespace Nerdbank.GitVersioning.ManagedGit 13 | { 14 | internal static class FileHelpers 15 | { 16 | private static readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); 17 | 18 | /// 19 | /// Opens the file with a given path, if it exists. 20 | /// 21 | /// The path to the file. 22 | /// The stream to open to, if the file exists. 23 | /// if the file exists; otherwise . 24 | internal static bool TryOpen(string path, out FileStream? stream) 25 | { 26 | if (IsWindows) 27 | { 28 | var handle = PInvoke.CreateFile(path, FILE_ACCESS_FLAGS.FILE_GENERIC_READ, FILE_SHARE_MODE.FILE_SHARE_READ, lpSecurityAttributes: null, FILE_CREATION_DISPOSITION.OPEN_EXISTING, FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_NORMAL, null); 29 | 30 | if (!handle.IsInvalid) 31 | { 32 | var fileHandle = new SafeFileHandle(handle.DangerousGetHandle(), ownsHandle: true); 33 | handle.SetHandleAsInvalid(); 34 | stream = new FileStream(fileHandle, System.IO.FileAccess.Read); 35 | return true; 36 | } 37 | else 38 | { 39 | stream = null; 40 | return false; 41 | } 42 | } 43 | else 44 | { 45 | if (!File.Exists(path)) 46 | { 47 | stream = null; 48 | return false; 49 | } 50 | 51 | stream = File.OpenRead(path); 52 | return true; 53 | } 54 | } 55 | 56 | /// 57 | /// Opens the file with a given path, if it exists. 58 | /// 59 | /// The path to the file, as a null-terminated UTF-16 character array. 60 | /// The stream to open to, if the file exists. 61 | /// if the file exists; otherwise . 62 | internal static unsafe bool TryOpen(ReadOnlySpan path, [NotNullWhen(true)] out FileStream? stream) 63 | { 64 | if (IsWindows) 65 | { 66 | HANDLE handle; 67 | fixed (char* pPath = &path[0]) 68 | { 69 | handle = PInvoke.CreateFile(pPath, FILE_ACCESS_FLAGS.FILE_GENERIC_READ, FILE_SHARE_MODE.FILE_SHARE_READ, null, FILE_CREATION_DISPOSITION.OPEN_EXISTING, FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_NORMAL, default); 70 | } 71 | 72 | if (!handle.Equals(Constants.INVALID_HANDLE_VALUE)) 73 | { 74 | var fileHandle = new SafeFileHandle(handle, ownsHandle: true); 75 | stream = new FileStream(fileHandle, System.IO.FileAccess.Read); 76 | return true; 77 | } 78 | else 79 | { 80 | stream = null; 81 | return false; 82 | } 83 | } 84 | else 85 | { 86 | // Make sure to trim the trailing \0 87 | string fullPath = GetUtf16String(path.Slice(0, path.Length - 1)); 88 | 89 | if (!File.Exists(fullPath)) 90 | { 91 | stream = null; 92 | return false; 93 | } 94 | 95 | stream = File.OpenRead(fullPath); 96 | return true; 97 | } 98 | } 99 | 100 | private static unsafe string GetUtf16String(ReadOnlySpan chars) 101 | { 102 | fixed (char* pChars = chars) 103 | { 104 | return new string(pChars, 0, chars.Length); 105 | } 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /NerdBank.GitVersioning/ManagedGit/GitObjectStream.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.IO; 5 | 6 | namespace Nerdbank.GitVersioning.ManagedGit 7 | { 8 | /// 9 | /// A which reads data stored in the Git object store. The data is stored 10 | /// as a gz-compressed stream, and is prefixed with the object type and data length. 11 | /// 12 | public class GitObjectStream : ZLibStream 13 | { 14 | /// 15 | /// Initializes a new instance of the class. 16 | /// 17 | /// 18 | /// The from which to read data. 19 | /// 20 | /// 21 | /// The expected object type of the git object. 22 | /// 23 | #pragma warning disable CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable. ObjectType is assigned in ReadObjectTypeAndLength. 24 | public GitObjectStream(Stream stream, string objectType) 25 | #pragma warning restore CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable. 26 | : base(stream, -1) 27 | { 28 | this.ReadObjectTypeAndLength(objectType); 29 | } 30 | 31 | /// 32 | /// Gets the object type of this Git object. 33 | /// 34 | public string ObjectType { get; private set; } 35 | 36 | /// 37 | public override bool CanRead => true; 38 | 39 | /// 40 | public override bool CanSeek => true; 41 | 42 | /// 43 | public override bool CanWrite => false; 44 | 45 | private void ReadObjectTypeAndLength(string objectType) 46 | { 47 | Span buffer = stackalloc byte[128]; 48 | this.Read(buffer.Slice(0, objectType.Length + 1)); 49 | 50 | var actualObjectType = GitRepository.GetString(buffer.Slice(0, objectType.Length)); 51 | this.ObjectType = actualObjectType; 52 | 53 | int headerLength = 0; 54 | long length = 0; 55 | 56 | while (headerLength < buffer.Length) 57 | { 58 | this.Read(buffer.Slice(headerLength, 1)); 59 | 60 | if (buffer[headerLength] == 0) 61 | { 62 | break; 63 | } 64 | 65 | // Direct conversion from ASCII to int 66 | length = (10 * length) + (buffer[headerLength] - (byte)'0'); 67 | 68 | headerLength += 1; 69 | } 70 | 71 | this.Initialize(length); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /NerdBank.GitVersioning/ManagedGit/GitPackCache.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Diagnostics.CodeAnalysis; 5 | using System.IO; 6 | using System.Text; 7 | 8 | namespace Nerdbank.GitVersioning.ManagedGit 9 | { 10 | /// 11 | /// Represents a cache in which objects retrieved from a 12 | /// are cached. Caching these objects can be of interest, because retrieving 13 | /// data from a can be potentially expensive: the data is 14 | /// compressed and can be deltified. 15 | /// 16 | public abstract class GitPackCache : IDisposable 17 | { 18 | /// 19 | /// Attempts to retrieve a Git object from cache. 20 | /// 21 | /// 22 | /// The offset of the Git object in the Git pack. 23 | /// 24 | /// 25 | /// A which will be set to the cached Git object. 26 | /// 27 | /// 28 | /// if the object was found in cache; otherwise, 29 | /// . 30 | /// 31 | public abstract bool TryOpen(long offset, [NotNullWhen(true)] out Stream? stream); 32 | 33 | /// 34 | /// Gets statistics about the cache usage. 35 | /// 36 | /// 37 | /// A to which to write the statistics. 38 | /// 39 | public abstract void GetCacheStatistics(StringBuilder builder); 40 | 41 | /// 42 | /// Adds a Git object to this cache. 43 | /// 44 | /// 45 | /// The offset of the Git object in the Git pack. 46 | /// 47 | /// 48 | /// A which represents the object to add. This stream 49 | /// will be copied to the cache. 50 | /// 51 | /// 52 | /// A which represents the cached entry. 53 | /// 54 | public abstract Stream Add(long offset, Stream stream); 55 | 56 | /// 57 | public void Dispose() 58 | { 59 | this.Dispose(true); 60 | GC.SuppressFinalize(this); 61 | } 62 | 63 | /// 64 | /// Disposes of native and managed resources associated by this object. 65 | /// 66 | /// to dispose managed and native resources; to only dispose of native resources. 67 | protected virtual void Dispose(bool disposing) 68 | { 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /NerdBank.GitVersioning/ManagedGit/GitPackIndexReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Nerdbank.GitVersioning.ManagedGit 4 | { 5 | /// 6 | /// Base class for classes which support reading data stored in a Git Pack file. 7 | /// 8 | /// 9 | public abstract class GitPackIndexReader : IDisposable 10 | { 11 | /// 12 | /// The header of the index file. 13 | /// 14 | protected static readonly byte[] Header = new byte[] { 0xff, 0x74, 0x4f, 0x63 }; 15 | 16 | /// 17 | /// Gets the offset of a Git object in the index file. 18 | /// 19 | /// 20 | /// The Git object Id of the Git object for which to get the offset. 21 | /// 22 | /// 23 | /// If found, the offset of the Git object in the index file; otherwise, 24 | /// . 25 | /// 26 | public long? GetOffset(GitObjectId objectId) 27 | { 28 | Span name = stackalloc byte[20]; 29 | objectId.CopyTo(name); 30 | (var offset, var _) = this.GetOffset(name); 31 | return offset; 32 | } 33 | 34 | /// 35 | /// Gets the offset of a Git object in the index file. 36 | /// 37 | /// 38 | /// A partial or full Git object id, in its binary representation. 39 | /// 40 | /// if ends with a byte whose last 4 bits are all zeros and not intended for inclusion in the search; otherwise. 41 | /// 42 | /// If found, the offset of the Git object in the index file; otherwise, 43 | /// . 44 | /// 45 | public abstract (long?, GitObjectId?) GetOffset(Span objectId, bool endsWithHalfByte = false); 46 | 47 | /// 48 | public abstract void Dispose(); 49 | } 50 | } -------------------------------------------------------------------------------- /NerdBank.GitVersioning/ManagedGit/GitPackMemoryCache.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System.Collections.Generic; 4 | using System.Diagnostics.CodeAnalysis; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Text; 8 | 9 | namespace Nerdbank.GitVersioning.ManagedGit 10 | { 11 | /// 12 | /// 13 | /// The implements the abstract class. 14 | /// When a is added to the , it is wrapped in a 15 | /// . This stream allows for just-in-time, random, read-only 16 | /// access to the underlying data (which may deltafied and/or compressed). 17 | /// 18 | /// 19 | /// Whenever data is read from a , the call is forwarded to the 20 | /// underlying and cached in a . If the same data is read 21 | /// twice, it is read from the , rather than the underlying . 22 | /// 23 | /// 24 | /// and return 25 | /// objects which may operate on the same underlying , but independently maintain 26 | /// their state. 27 | /// 28 | /// 29 | public class GitPackMemoryCache : GitPackCache 30 | { 31 | private readonly Dictionary cache = new Dictionary(); 32 | 33 | /// 34 | public override Stream Add(long offset, Stream stream) 35 | { 36 | var cacheStream = new GitPackMemoryCacheStream(stream); 37 | this.cache.Add(offset, cacheStream); 38 | return new GitPackMemoryCacheViewStream(cacheStream); 39 | } 40 | 41 | /// 42 | public override bool TryOpen(long offset, [NotNullWhen(true)] out Stream? stream) 43 | { 44 | if (this.cache.TryGetValue(offset, out GitPackMemoryCacheStream? cacheStream)) 45 | { 46 | stream = new GitPackMemoryCacheViewStream(cacheStream!); 47 | return true; 48 | } 49 | 50 | stream = null; 51 | return false; 52 | } 53 | 54 | /// 55 | public override void GetCacheStatistics(StringBuilder builder) 56 | { 57 | builder.AppendLine($"{this.cache.Count} items in cache"); 58 | } 59 | 60 | /// 61 | protected override void Dispose(bool disposing) 62 | { 63 | if (disposing) 64 | { 65 | while (this.cache.Count > 0) 66 | { 67 | var key = this.cache.Keys.First(); 68 | var stream = this.cache[key]; 69 | stream.Dispose(); 70 | this.cache.Remove(key); 71 | } 72 | } 73 | 74 | base.Dispose(disposing); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /NerdBank.GitVersioning/ManagedGit/GitPackMemoryCacheStream.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Buffers; 5 | using System.IO; 6 | 7 | namespace Nerdbank.GitVersioning.ManagedGit 8 | { 9 | internal class GitPackMemoryCacheStream : Stream 10 | { 11 | private Stream stream; 12 | private readonly MemoryStream cacheStream = new MemoryStream(); 13 | private long length; 14 | 15 | public GitPackMemoryCacheStream(Stream stream) 16 | { 17 | this.stream = stream ?? throw new ArgumentNullException(nameof(stream)); 18 | this.length = this.stream.Length; 19 | } 20 | 21 | public override bool CanRead => true; 22 | 23 | public override bool CanSeek => true; 24 | 25 | public override bool CanWrite => false; 26 | 27 | public override long Length => this.length; 28 | 29 | public override long Position 30 | { 31 | get => this.cacheStream.Position; 32 | set => throw new NotSupportedException(); 33 | } 34 | 35 | public override void Flush() 36 | { 37 | throw new NotSupportedException(); 38 | } 39 | 40 | #if NETSTANDARD2_0 41 | public int Read(Span buffer) 42 | #else 43 | /// 44 | public override int Read(Span buffer) 45 | #endif 46 | { 47 | if (this.cacheStream.Length < this.length 48 | && this.cacheStream.Position + buffer.Length > this.cacheStream.Length) 49 | { 50 | var currentPosition = this.cacheStream.Position; 51 | var toRead = (int)(buffer.Length - this.cacheStream.Length + this.cacheStream.Position); 52 | int actualRead = this.stream.Read(buffer.Slice(0, toRead)); 53 | this.cacheStream.Seek(0, SeekOrigin.End); 54 | this.cacheStream.Write(buffer.Slice(0, actualRead)); 55 | this.cacheStream.Seek(currentPosition, SeekOrigin.Begin); 56 | this.DisposeStreamIfRead(); 57 | } 58 | 59 | return this.cacheStream.Read(buffer); 60 | } 61 | 62 | public override int Read(byte[] buffer, int offset, int count) 63 | { 64 | return this.Read(buffer.AsSpan(offset, count)); 65 | } 66 | 67 | public override long Seek(long offset, SeekOrigin origin) 68 | { 69 | if (origin != SeekOrigin.Begin) 70 | { 71 | throw new NotSupportedException(); 72 | } 73 | 74 | if (offset > this.cacheStream.Length) 75 | { 76 | var toRead = (int)(offset - this.cacheStream.Length); 77 | byte[] buffer = ArrayPool.Shared.Rent(toRead); 78 | int read = this.stream.Read(buffer, 0, toRead); 79 | this.cacheStream.Seek(0, SeekOrigin.End); 80 | this.cacheStream.Write(buffer, 0, read); 81 | ArrayPool.Shared.Return(buffer); 82 | 83 | this.DisposeStreamIfRead(); 84 | return this.cacheStream.Position; 85 | } 86 | else 87 | { 88 | return this.cacheStream.Seek(offset, origin); 89 | } 90 | } 91 | 92 | public override void SetLength(long value) 93 | { 94 | throw new NotSupportedException(); 95 | } 96 | 97 | public override void Write(byte[] buffer, int offset, int count) 98 | { 99 | throw new NotSupportedException(); 100 | } 101 | 102 | protected override void Dispose(bool disposing) 103 | { 104 | if (disposing) 105 | { 106 | this.stream.Dispose(); 107 | this.cacheStream.Dispose(); 108 | } 109 | 110 | base.Dispose(disposing); 111 | } 112 | 113 | private void DisposeStreamIfRead() 114 | { 115 | if (this.cacheStream.Length == this.stream.Length) 116 | { 117 | this.stream.Dispose(); 118 | } 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /NerdBank.GitVersioning/ManagedGit/GitPackMemoryCacheViewStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | 6 | namespace Nerdbank.GitVersioning.ManagedGit 7 | { 8 | internal class GitPackMemoryCacheViewStream : Stream 9 | { 10 | private readonly GitPackMemoryCacheStream baseStream; 11 | 12 | public GitPackMemoryCacheViewStream(GitPackMemoryCacheStream baseStream) 13 | { 14 | this.baseStream = baseStream ?? throw new ArgumentNullException(nameof(baseStream)); 15 | } 16 | 17 | public override bool CanRead => true; 18 | 19 | public override bool CanSeek => true; 20 | 21 | public override bool CanWrite => false; 22 | 23 | public override long Length => this.baseStream.Length; 24 | 25 | private long position; 26 | 27 | public override long Position 28 | { 29 | get => this.position; 30 | set => throw new NotSupportedException(); 31 | } 32 | 33 | public override void Flush() => throw new NotImplementedException(); 34 | 35 | public override int Read(byte[] buffer, int offset, int count) 36 | { 37 | return this.Read(buffer.AsSpan(offset, count)); 38 | } 39 | 40 | #if NETSTANDARD2_0 41 | public int Read(Span buffer) 42 | #else 43 | /// 44 | public override int Read(Span buffer) 45 | #endif 46 | { 47 | int read = 0; 48 | 49 | lock (this.baseStream) 50 | { 51 | if (this.baseStream.Position != this.position) 52 | { 53 | this.baseStream.Seek(this.position, SeekOrigin.Begin); 54 | } 55 | 56 | read = this.baseStream.Read(buffer); 57 | } 58 | 59 | this.position += read; 60 | return read; 61 | } 62 | 63 | public override long Seek(long offset, SeekOrigin origin) 64 | { 65 | if (origin != SeekOrigin.Begin) 66 | { 67 | throw new NotSupportedException(); 68 | } 69 | 70 | this.position = Math.Min(offset, this.Length); 71 | return this.position; 72 | } 73 | 74 | public override void SetLength(long value) => throw new NotSupportedException(); 75 | public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /NerdBank.GitVersioning/ManagedGit/GitPackNullCache.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.IO; 5 | using System.Text; 6 | 7 | namespace Nerdbank.GitVersioning.ManagedGit 8 | { 9 | /// 10 | /// A no-op implementation of the class. 11 | /// 12 | public class GitPackNullCache : GitPackCache 13 | { 14 | /// 15 | /// Gets the default instance of the class. 16 | /// 17 | public static GitPackNullCache Instance { get; } = new GitPackNullCache(); 18 | 19 | /// 20 | public override Stream Add(long offset, Stream stream) 21 | { 22 | return stream; 23 | } 24 | 25 | /// 26 | public override bool TryOpen(long offset, [NotNullWhen(true)] out Stream? stream) 27 | { 28 | stream = null; 29 | return false; 30 | } 31 | 32 | /// 33 | public override void GetCacheStatistics(StringBuilder builder) 34 | { 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /NerdBank.GitVersioning/ManagedGit/GitPackObjectType.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | namespace Nerdbank.GitVersioning.ManagedGit 4 | { 5 | internal enum GitPackObjectType 6 | { 7 | Invalid = 0, 8 | OBJ_COMMIT = 1, 9 | OBJ_TREE = 2, 10 | OBJ_BLOB = 3, 11 | OBJ_TAG = 4, 12 | OBJ_OFS_DELTA = 6, 13 | OBJ_REF_DELTA = 7, 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /NerdBank.GitVersioning/ManagedGit/GitPackPooledStream.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Diagnostics; 6 | using System.IO; 7 | 8 | namespace Nerdbank.GitVersioning.ManagedGit 9 | { 10 | /// 11 | /// A pooled , which wraps around a 12 | /// which will be returned to a pool 13 | /// instead of actually being closed when is called. 14 | /// 15 | public class GitPackPooledStream : Stream 16 | { 17 | private readonly Stream stream; 18 | private readonly Queue pool; 19 | 20 | /// 21 | /// Initializes a new instance of the class. 22 | /// 23 | /// 24 | /// The which is being pooled. 25 | /// 26 | /// 27 | /// A to which the stream will be returned. 28 | /// 29 | public GitPackPooledStream(Stream stream, Queue pool) 30 | { 31 | this.stream = stream ?? throw new ArgumentNullException(nameof(stream)); 32 | this.pool = pool ?? throw new ArgumentNullException(nameof(pool)); 33 | } 34 | 35 | /// 36 | /// Gets the underlying for this . 37 | /// 38 | public Stream BaseStream => this.stream; 39 | 40 | /// 41 | public override bool CanRead => this.stream.CanRead; 42 | 43 | /// 44 | public override bool CanSeek => this.stream.CanSeek; 45 | 46 | /// 47 | public override bool CanWrite => this.stream.CanWrite; 48 | 49 | /// 50 | public override long Length => this.stream.Length; 51 | 52 | /// 53 | public override long Position 54 | { 55 | get => this.stream.Position; 56 | set => this.stream.Position = value; 57 | } 58 | 59 | /// 60 | public override void Flush() 61 | { 62 | this.stream.Flush(); 63 | } 64 | 65 | #if !NETSTANDARD2_0 66 | /// 67 | public override int Read(Span buffer) 68 | { 69 | return this.stream.Read(buffer); 70 | } 71 | #endif 72 | 73 | /// 74 | public override int Read(byte[] buffer, int offset, int count) 75 | { 76 | return this.stream.Read(buffer, offset, count); 77 | } 78 | 79 | /// 80 | public override long Seek(long offset, SeekOrigin origin) 81 | { 82 | return this.stream.Seek(offset, origin); 83 | } 84 | 85 | /// 86 | public override void SetLength(long value) 87 | { 88 | this.stream.SetLength(value); 89 | } 90 | 91 | /// 92 | public override void Write(byte[] buffer, int offset, int count) 93 | { 94 | throw new NotSupportedException(); 95 | } 96 | 97 | /// 98 | protected override void Dispose(bool disposing) 99 | { 100 | this.pool.Enqueue(this); 101 | Debug.WriteLine("Returning stream to pool"); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /NerdBank.GitVersioning/ManagedGit/GitPackReader.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Buffers.Binary; 5 | using System.Diagnostics; 6 | using System.IO; 7 | 8 | namespace Nerdbank.GitVersioning.ManagedGit 9 | { 10 | internal static class GitPackReader 11 | { 12 | private static readonly byte[] Signature = GitRepository.Encoding.GetBytes("PACK"); 13 | 14 | public static Stream GetObject(GitPack pack, Stream stream, long offset, string objectType, GitPackObjectType packObjectType) 15 | { 16 | if (pack is null) 17 | { 18 | throw new ArgumentNullException(nameof(pack)); 19 | } 20 | 21 | if (stream is null) 22 | { 23 | throw new ArgumentNullException(nameof(stream)); 24 | } 25 | 26 | // Read the signature 27 | #if DEBUG 28 | stream.Seek(0, SeekOrigin.Begin); 29 | Span buffer = stackalloc byte[12]; 30 | stream.ReadAll(buffer); 31 | 32 | Debug.Assert(buffer.Slice(0, 4).SequenceEqual(Signature)); 33 | 34 | var versionNumber = BinaryPrimitives.ReadInt32BigEndian(buffer.Slice(4, 4)); 35 | Debug.Assert(versionNumber == 2); 36 | 37 | var numberOfObjects = BinaryPrimitives.ReadInt32BigEndian(buffer.Slice(8, 4)); 38 | #endif 39 | 40 | stream.Seek(offset, SeekOrigin.Begin); 41 | 42 | var (type, decompressedSize) = ReadObjectHeader(stream); 43 | 44 | if (type == GitPackObjectType.OBJ_OFS_DELTA) 45 | { 46 | var baseObjectRelativeOffset = ReadVariableLengthInteger(stream); 47 | long baseObjectOffset = offset - baseObjectRelativeOffset; 48 | 49 | var deltaStream = new ZLibStream(stream, decompressedSize); 50 | var baseObjectStream = pack.GetObject(baseObjectOffset, objectType); 51 | 52 | return new GitPackDeltafiedStream(baseObjectStream, deltaStream); 53 | } 54 | else if (type == GitPackObjectType.OBJ_REF_DELTA) 55 | { 56 | Span baseObjectId = stackalloc byte[20]; 57 | stream.ReadAll(baseObjectId); 58 | 59 | Stream baseObject = pack.GetObjectFromRepository(GitObjectId.Parse(baseObjectId), objectType)!; 60 | var seekableBaseObject = new GitPackMemoryCacheStream(baseObject); 61 | 62 | var deltaStream = new ZLibStream(stream, decompressedSize); 63 | 64 | return new GitPackDeltafiedStream(seekableBaseObject, deltaStream); 65 | } 66 | 67 | // Tips for handling deltas: https://github.com/choffmeister/gitnet/blob/4d907623d5ce2d79a8875aee82e718c12a8aad0b/src/GitNet/GitPack.cs 68 | if (type != packObjectType) 69 | { 70 | throw new GitException($"An object of type {objectType} could not be located at offset {offset}.") { ErrorCode = GitException.ErrorCodes.ObjectNotFound }; 71 | } 72 | 73 | return new ZLibStream(stream, decompressedSize); 74 | } 75 | 76 | private static (GitPackObjectType, long) ReadObjectHeader(Stream stream) 77 | { 78 | Span value = stackalloc byte[1]; 79 | stream.Read(value); 80 | 81 | var type = (GitPackObjectType)((value[0] & 0b0111_0000) >> 4); 82 | long length = value[0] & 0b_1111; 83 | 84 | if ((value[0] & 0b1000_0000) == 0) 85 | { 86 | return (type, length); 87 | } 88 | 89 | int shift = 4; 90 | 91 | do 92 | { 93 | stream.Read(value); 94 | length = length | ((value[0] & (long)0b0111_1111) << shift); 95 | shift += 7; 96 | } while ((value[0] & 0b1000_0000) != 0); 97 | 98 | return (type, length); 99 | } 100 | 101 | private static long ReadVariableLengthInteger(Stream stream) 102 | { 103 | long offset = -1; 104 | int b; 105 | 106 | do 107 | { 108 | offset++; 109 | b = stream.ReadByte(); 110 | offset = (offset << 7) + (b & 127); 111 | } 112 | while ((b & (byte)128) != 0); 113 | 114 | return offset; 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /NerdBank.GitVersioning/ManagedGit/GitReferenceReader.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.IO; 5 | 6 | namespace Nerdbank.GitVersioning.ManagedGit 7 | { 8 | internal class GitReferenceReader 9 | { 10 | private readonly static byte[] RefPrefix = GitRepository.Encoding.GetBytes("ref: "); 11 | 12 | public static object ReadReference(Stream stream) 13 | { 14 | Span reference = stackalloc byte[(int)stream.Length]; 15 | stream.ReadAll(reference); 16 | 17 | return ReadReference(reference); 18 | } 19 | 20 | public static object ReadReference(Span value) 21 | { 22 | if (value.Length == 41 && !value.StartsWith(RefPrefix)) 23 | { 24 | // Skip the trailing \n 25 | return GitObjectId.ParseHex(value.Slice(0, 40)); 26 | } 27 | else 28 | { 29 | if (!value.StartsWith(RefPrefix)) 30 | { 31 | throw new GitException(); 32 | } 33 | 34 | // Skip the terminating \n character 35 | return GitRepository.GetString(value.Slice(RefPrefix.Length, value.Length - RefPrefix.Length - 1)); 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /NerdBank.GitVersioning/ManagedGit/GitSignature.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | 5 | namespace Nerdbank.GitVersioning.ManagedGit 6 | { 7 | /// 8 | /// Represents the signature of a Git committer or author. 9 | /// 10 | public struct GitSignature 11 | { 12 | /// 13 | /// Gets or sets the name of the committer or author. 14 | /// 15 | public string Name { get; set; } 16 | 17 | /// 18 | /// Gets or sets the e-mail address of the commiter or author. 19 | /// 20 | public string Email { get; set; } 21 | 22 | /// 23 | /// Gets or sets the date and time at which the commit was made. 24 | /// 25 | public DateTimeOffset Date { get; set; } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /NerdBank.GitVersioning/ManagedGit/GitTree.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System.Collections.Generic; 4 | 5 | namespace Nerdbank.GitVersioning.ManagedGit 6 | { 7 | /// 8 | /// Represents a git tree. 9 | /// 10 | public class GitTree 11 | { 12 | /// 13 | /// Gets an empty . 14 | /// 15 | public static GitTree Empty { get; } = new GitTree(); 16 | 17 | /// 18 | /// The Git object Id of this . 19 | /// 20 | public GitObjectId Sha { get; set; } 21 | 22 | /// 23 | /// Gets a dictionary which contains all entries in the current tree, accessible by name. 24 | /// 25 | public Dictionary Children { get; } = new Dictionary(); 26 | 27 | /// 28 | public override string ToString() 29 | { 30 | return $"Git tree: {this.Sha}"; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /NerdBank.GitVersioning/ManagedGit/GitTreeEntry.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | namespace Nerdbank.GitVersioning.ManagedGit 4 | { 5 | /// 6 | /// Represents an individual entry in the Git tree. 7 | /// 8 | public class GitTreeEntry 9 | { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// 13 | /// 14 | /// The name of the entry. 15 | /// 16 | /// 17 | /// A vaolue indicating whether the current entry is a file. 18 | /// 19 | /// 20 | /// The Git object Id of the blob or tree of the current entry. 21 | /// 22 | public GitTreeEntry(string name, bool isFile, GitObjectId sha) 23 | { 24 | this.Name = name; 25 | this.IsFile = isFile; 26 | this.Sha = sha; 27 | } 28 | 29 | /// 30 | /// Gets the name of the entry. 31 | /// 32 | public string Name { get; } 33 | 34 | /// 35 | /// Gets a value indicating whether the current entry is a file. 36 | /// 37 | public bool IsFile { get; } 38 | 39 | /// 40 | /// Gets the Git object Id of the blob or tree of the current entry. 41 | /// 42 | public GitObjectId Sha { get; } 43 | 44 | /// 45 | public override string ToString() 46 | { 47 | return this.Name; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /NerdBank.GitVersioning/ManagedGit/GitTreeReader.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Buffers; 5 | using System.IO; 6 | 7 | namespace Nerdbank.GitVersioning.ManagedGit 8 | { 9 | internal static class GitTreeReader 10 | { 11 | public static GitTree Read(Stream stream, GitObjectId objectId) 12 | { 13 | byte[] buffer = ArrayPool.Shared.Rent((int)stream.Length); 14 | #if DEBUG 15 | Array.Clear(buffer, 0, buffer.Length); 16 | #endif 17 | 18 | GitTree value = new GitTree() 19 | { 20 | Sha = objectId, 21 | }; 22 | 23 | try 24 | { 25 | Span contents = buffer.AsSpan(0, (int)stream.Length); 26 | stream.ReadAll(contents); 27 | 28 | while (contents.Length > 0) 29 | { 30 | // Format: [mode] [file/ folder name]\0[SHA - 1 of referencing blob or tree] 31 | // Mode is either 6-bytes long (directory) or 7-bytes long (file). 32 | // If the entry is a file, the first byte is '1' 33 | var fileNameEnds = contents.IndexOf((byte)0); 34 | bool isFile = contents[0] == (byte)'1'; 35 | var modeLength = isFile ? 7 : 6; 36 | 37 | var currentName = contents.Slice(modeLength, fileNameEnds - modeLength); 38 | var currentObjectId = GitObjectId.Parse(contents.Slice(fileNameEnds + 1, 20)); 39 | 40 | var name = GitRepository.GetString(currentName); 41 | 42 | value.Children.Add( 43 | name, 44 | new GitTreeEntry(name, isFile, currentObjectId)); 45 | 46 | contents = contents.Slice(fileNameEnds + 1 + 20); 47 | } 48 | } 49 | finally 50 | { 51 | ArrayPool.Shared.Return(buffer); 52 | } 53 | 54 | return value; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /NerdBank.GitVersioning/ManagedGit/GitTreeStreamingReader.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Buffers; 5 | using System.IO; 6 | 7 | namespace Nerdbank.GitVersioning.ManagedGit 8 | { 9 | /// 10 | /// Reads git tree objects. 11 | /// 12 | public class GitTreeStreamingReader 13 | { 14 | /// 15 | /// Finds a specific node in a git tree. 16 | /// 17 | /// 18 | /// A which represents the git tree. 19 | /// 20 | /// 21 | /// The name of the node to find, in it UTF-8 representation. 22 | /// 23 | /// 24 | /// The of the requested node. 25 | /// 26 | public static GitObjectId FindNode(Stream stream, ReadOnlySpan name) 27 | { 28 | byte[] buffer = ArrayPool.Shared.Rent((int)stream.Length); 29 | Span contents = new Span(buffer, 0, (int)stream.Length); 30 | 31 | stream.ReadAll(contents); 32 | 33 | GitObjectId value = GitObjectId.Empty; 34 | 35 | while (contents.Length > 0) 36 | { 37 | // Format: [mode] [file/ folder name]\0[SHA - 1 of referencing blob or tree] 38 | // Mode is either 6-bytes long (directory) or 7-bytes long (file). 39 | // If the entry is a file, the first byte is '1' 40 | var fileNameEnds = contents.IndexOf((byte)0); 41 | bool isFile = contents[0] == (byte)'1'; 42 | var modeLength = isFile ? 7 : 6; 43 | 44 | var currentName = contents.Slice(modeLength, fileNameEnds - modeLength); 45 | 46 | if (currentName.SequenceEqual(name)) 47 | { 48 | value = GitObjectId.Parse(contents.Slice(fileNameEnds + 1, 20)); 49 | break; 50 | } 51 | else 52 | { 53 | contents = contents.Slice(fileNameEnds + 1 + 20); 54 | } 55 | } 56 | 57 | ArrayPool.Shared.Return(buffer); 58 | 59 | return value; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /NerdBank.GitVersioning/ManagedGit/MemoryMappedStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.IO.MemoryMappedFiles; 4 | 5 | namespace Nerdbank.GitVersioning.ManagedGit 6 | { 7 | /// 8 | /// Provides read-only, seekable access to a . 9 | /// 10 | public unsafe class MemoryMappedStream : Stream 11 | { 12 | private readonly MemoryMappedViewAccessor accessor; 13 | private readonly long length; 14 | private long position; 15 | private byte* ptr; 16 | private bool disposed; 17 | 18 | /// 19 | /// Initializes a new instance of the class. 20 | /// 21 | /// 22 | /// The accessor to the memory mapped stream. 23 | /// 24 | public MemoryMappedStream(MemoryMappedViewAccessor accessor) 25 | { 26 | this.accessor = accessor ?? throw new ArgumentNullException(nameof(accessor)); 27 | this.accessor.SafeMemoryMappedViewHandle.AcquirePointer(ref this.ptr); 28 | this.length = this.accessor.Capacity; 29 | } 30 | 31 | /// 32 | public override bool CanRead => true; 33 | 34 | /// 35 | public override bool CanSeek => true; 36 | 37 | /// 38 | public override bool CanWrite => false; 39 | 40 | /// 41 | public override long Length => this.length; 42 | 43 | /// 44 | public override long Position 45 | { 46 | get => this.position; 47 | set 48 | { 49 | this.position = (int)value; 50 | } 51 | } 52 | 53 | /// 54 | public override void Flush() 55 | { 56 | } 57 | 58 | /// 59 | public override int Read(byte[] buffer, int offset, int count) 60 | { 61 | if (this.disposed) 62 | { 63 | throw new ObjectDisposedException(nameof(MemoryMappedStream)); 64 | } 65 | 66 | int read = (int)Math.Min(count, this.length - this.position); 67 | 68 | new Span(this.ptr + this.position, read) 69 | .CopyTo(buffer.AsSpan(offset, count)); 70 | 71 | this.position += read; 72 | return read; 73 | } 74 | 75 | #if !NETSTANDARD2_0 76 | /// 77 | public override int Read(Span buffer) 78 | { 79 | if (this.disposed) 80 | { 81 | throw new ObjectDisposedException(nameof(MemoryMappedStream)); 82 | } 83 | 84 | int read = (int)Math.Min(buffer.Length, this.length - this.position); 85 | 86 | new Span(this.ptr + this.position, read) 87 | .CopyTo(buffer); 88 | 89 | this.position += read; 90 | return read; 91 | } 92 | #endif 93 | 94 | /// 95 | public override long Seek(long offset, SeekOrigin origin) 96 | { 97 | if (this.disposed) 98 | { 99 | throw new ObjectDisposedException(nameof(MemoryMappedStream)); 100 | } 101 | 102 | long newPosition = this.position; 103 | 104 | switch (origin) 105 | { 106 | case SeekOrigin.Begin: 107 | newPosition = offset; 108 | break; 109 | 110 | case SeekOrigin.Current: 111 | newPosition += offset; 112 | break; 113 | 114 | case SeekOrigin.End: 115 | throw new NotSupportedException(); 116 | } 117 | 118 | if (newPosition > this.length) 119 | { 120 | newPosition = this.length; 121 | } 122 | 123 | if (newPosition < 0) 124 | { 125 | throw new IOException("Attempted to seek before the start or beyond the end of the stream."); 126 | } 127 | 128 | this.position = newPosition; 129 | return this.position; 130 | } 131 | 132 | /// 133 | public override void SetLength(long value) 134 | { 135 | throw new NotSupportedException(); 136 | } 137 | 138 | /// 139 | public override void Write(byte[] buffer, int offset, int count) 140 | { 141 | throw new NotSupportedException(); 142 | } 143 | 144 | /// 145 | protected override void Dispose(bool disposing) 146 | { 147 | if (disposing) 148 | { 149 | this.accessor.SafeMemoryMappedViewHandle.ReleasePointer(); 150 | this.disposed = true; 151 | } 152 | 153 | base.Dispose(disposing); 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /NerdBank.GitVersioning/NativeMethods.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://aka.ms/CsWin32.schema.json" 3 | } -------------------------------------------------------------------------------- /NerdBank.GitVersioning/NativeMethods.txt: -------------------------------------------------------------------------------- 1 | CreateFile 2 | INVALID_HANDLE_VALUE 3 | -------------------------------------------------------------------------------- /NerdBank.GitVersioning/NerdBank.GitVersioning.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netstandard2.0;netcoreapp3.1 4 | true 5 | Full 6 | false 7 | Nerdbank.GitVersioning.Core 8 | true 9 | Nerdbank.GitVersioning 10 | 9.0 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /NerdBank.GitVersioning/NoGit/NoGitContext.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Diagnostics; 5 | 6 | namespace Nerdbank.GitVersioning 7 | { 8 | [DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")] 9 | internal class NoGitContext : GitContext 10 | { 11 | private const string NotAGitRepoMessage = "Not a git repo"; 12 | 13 | public NoGitContext(string workingTreePath) 14 | : base(workingTreePath, null) 15 | { 16 | this.VersionFile = new NoGitVersionFile(this); 17 | } 18 | 19 | public override VersionFile VersionFile { get; } 20 | 21 | public override string? GitCommitId => null; 22 | 23 | public override bool IsHead => false; 24 | 25 | public override DateTimeOffset? GitCommitDate => null; 26 | 27 | public override string? HeadCanonicalName => null; 28 | 29 | private string DebuggerDisplay => $"\"{this.WorkingTreePath}\" (no-git)"; 30 | 31 | public override void ApplyTag(string name) => throw new InvalidOperationException(NotAGitRepoMessage); 32 | public override void Stage(string path) => throw new InvalidOperationException(NotAGitRepoMessage); 33 | public override string GetShortUniqueCommitId(int minLength) => throw new InvalidOperationException(NotAGitRepoMessage); 34 | public override bool TrySelectCommit(string committish) => throw new InvalidOperationException(NotAGitRepoMessage); 35 | internal override int CalculateVersionHeight(VersionOptions? committedVersion, VersionOptions? workingVersion) => 0; 36 | internal override Version GetIdAsVersion(VersionOptions? committedVersion, VersionOptions? workingVersion, int versionHeight) => throw new NotImplementedException(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /NerdBank.GitVersioning/NoGit/NoGitVersionFile.cs: -------------------------------------------------------------------------------- 1 | namespace Nerdbank.GitVersioning 2 | { 3 | using Validation; 4 | 5 | internal class NoGitVersionFile : VersionFile 6 | { 7 | public NoGitVersionFile(GitContext context) 8 | : base(context) 9 | { 10 | } 11 | 12 | protected override VersionOptions GetVersionCore(out string actualDirectory) => throw Assumes.NotReachable(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /NerdBank.GitVersioning/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | [assembly: AssemblyCopyright("Copyright (c) .NET Foundation and Contributors")] 5 | [assembly: AssemblyTrademark("")] 6 | [assembly: AssemblyCulture("")] 7 | [assembly: ComVisible(false)] 8 | -------------------------------------------------------------------------------- /NerdBank.GitVersioning/SemanticVersionJsonConverter.cs: -------------------------------------------------------------------------------- 1 | namespace Nerdbank.GitVersioning 2 | { 3 | using System; 4 | using System.Reflection; 5 | using Newtonsoft.Json; 6 | 7 | internal class SemanticVersionJsonConverter : JsonConverter 8 | { 9 | public override bool CanConvert(Type objectType) 10 | { 11 | return typeof(SemanticVersion).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo()); 12 | } 13 | 14 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 15 | { 16 | if (objectType.Equals(typeof(SemanticVersion)) && reader.Value is string) 17 | { 18 | SemanticVersion value; 19 | if (SemanticVersion.TryParse((string)reader.Value, out value)) 20 | { 21 | return value; 22 | } 23 | else 24 | { 25 | throw new FormatException($"The value \"{reader.Value}\" is not a valid semantic version."); 26 | } 27 | } 28 | 29 | throw new NotSupportedException(); 30 | } 31 | 32 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 33 | { 34 | var version = value as SemanticVersion; 35 | if (version is not null) 36 | { 37 | writer.WriteValue(version.ToString()); 38 | return; 39 | } 40 | 41 | throw new NotSupportedException(); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /NerdBank.GitVersioning/Utilities.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Validation; 6 | 7 | namespace Nerdbank.GitVersioning 8 | { 9 | internal static class Utilities 10 | { 11 | private const int ProcessCannotAccessFileHR = unchecked((int)0x80070020); 12 | 13 | internal static void FileOperationWithRetry(Action operation) 14 | { 15 | Requires.NotNull(operation, nameof(operation)); 16 | 17 | for (int retriesLeft = 6; retriesLeft > 0; retriesLeft--) 18 | { 19 | try 20 | { 21 | operation(); 22 | } 23 | catch (IOException ex) when (ex.HResult == ProcessCannotAccessFileHR && retriesLeft > 0) 24 | { 25 | Task.Delay(100).Wait(); 26 | continue; 27 | } 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /NerdBank.GitVersioning/VersionExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Nerdbank.GitVersioning 2 | { 3 | using System; 4 | using Validation; 5 | 6 | /// 7 | /// Extension methods for the class. 8 | /// 9 | public static class VersionExtensions 10 | { 11 | /// 12 | /// Returns a instance where the specified number of components 13 | /// are guaranteed to be non-negative. Any applicable negative components are converted to zeros. 14 | /// 15 | /// The version to use as a template for the returned value. 16 | /// The number of version components to ensure are non-negative. 17 | /// 18 | /// The same as except with any applicable negative values 19 | /// translated to zeros. 20 | /// 21 | public static Version EnsureNonNegativeComponents(this Version version, int fieldCount = 4) 22 | { 23 | Requires.NotNull(version, nameof(version)); 24 | Requires.Range(fieldCount >= 0 && fieldCount <= 4, nameof(fieldCount)); 25 | 26 | int maj = fieldCount >= 1 ? Math.Max(0, version.Major) : version.Major; 27 | int min = fieldCount >= 2 ? Math.Max(0, version.Minor) : version.Minor; 28 | int bld = fieldCount >= 3 ? Math.Max(0, version.Build) : version.Build; 29 | int rev = fieldCount >= 4 ? Math.Max(0, version.Revision) : version.Revision; 30 | 31 | if (version.Major == maj && 32 | version.Minor == min && 33 | version.Build == bld && 34 | version.Revision == rev) 35 | { 36 | return version; 37 | } 38 | 39 | if (rev >= 0) 40 | { 41 | return new Version(maj, min, bld, rev); 42 | } 43 | else if (bld >= 0) 44 | { 45 | return new Version(maj, min, bld); 46 | } 47 | else 48 | { 49 | throw Assumes.NotReachable(); 50 | } 51 | } 52 | 53 | /// 54 | /// Converts the value of the current System.Version object to its equivalent System.String 55 | /// representation. A specified count indicates the number of components to return. 56 | /// 57 | /// The instance to serialize as a string. 58 | /// The number of components to return. The fieldCount ranges from 0 to 4. 59 | /// 60 | /// The System.String representation of the values of the major, minor, build, and 61 | /// revision components of the current System.Version object, each separated by a 62 | /// period character ('.'). The fieldCount parameter determines how many components 63 | /// are returned.fieldCount Return Value 0 An empty string (""). 1 major 2 major.minor 64 | /// 3 major.minor.build 4 major.minor.build.revision For example, if you create System.Version 65 | /// object using the constructor Version(1,3,5), ToString(2) returns "1.3" and ToString(4) 66 | /// returns "1.3.5.0". 67 | /// 68 | public static string ToStringSafe(this Version version, int fieldCount) 69 | { 70 | return version.EnsureNonNegativeComponents(fieldCount).ToString(fieldCount); 71 | } 72 | 73 | /// 74 | /// Initializes a new instance of the class, 75 | /// allowing for the last two integers to possibly be -1. 76 | /// 77 | /// The major version. 78 | /// The minor version. 79 | /// The build version. 80 | /// The revision. 81 | /// 82 | internal static Version Create(int major, int minor, int build, int revision) 83 | { 84 | return 85 | build == -1 ? new Version(major, minor) : 86 | revision == -1 ? new Version(major, minor, build) : 87 | new Version(major, minor, build, revision); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ManagedGitLib 2 | [![NuGet link](https://img.shields.io/nuget/v/ManagedGitLib?logo=NuGet&style=for-the-badge)](https://www.nuget.org/packages/ManagedGitLib/) 3 | 4 | A lightweight read-only git library written entirely in .NET. 5 | ManagedGitLib targets .NET Standard 2.0 (among other frameworks) and, unlike [Libgit2Sharp](https://github.com/libgit2/libgit2sharp), does not depend on any native library. 6 | Therefore ManagedGitLib can be used in scenarios where taking dependency on native libraries is imposible or problematic, 7 | like MSBuild Tasks and C# Source Generators. 8 | 9 | ManagedGitLib is a read-only library and designed to be used for reading git metadata, like commit information, listing tags, and files change history. 10 | It can't be used for cloning, managing, and modifying git repositories. 11 | For a full-blown git experience check out [Libgit2Sharp](https://github.com/libgit2/libgit2sharp). 12 | 13 | ManagedGitLib is derived from [Nerdbank.GitVersioning](https://github.com/dotnet/Nerdbank.GitVersioning) `ManagedGit` implementation. Special thanks to people, who made original `ManagedGit` possible: 14 | 1. [Frederik Carlier](https://github.com/qmfrederik) 15 | 2. [Filip Navara](https://github.com/filipnavara) 16 | 17 | ## Nightly builds 18 | 19 | You can find the latest experemental builds of ManagedGitLib at my [nightly packages feed](https://dev.azure.com/glebchili-personal/glebchili-packages/_packaging?_a=feed&feed=glebchili-personal-public%40Local). 20 | -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "6.0.100" 4 | } 5 | } -------------------------------------------------------------------------------- /nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | --------------------------------------------------------------------------------