├── .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 | [](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 |
--------------------------------------------------------------------------------