diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 54837ac35..8a197b428 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,102 +1,140 @@ name: CI on: - push: - branches: [master, release-*] - tags: - - '[0-9]+.[0-9]+.[0-9]+' - - '[0-9]+.[0-9]+.[0-9]+-*' - pull_request: - workflow_dispatch: + push: + branches: [master, release-*] + tags: + - "[0-9]+.[0-9]+.[0-9]+" + - "[0-9]+.[0-9]+.[0-9]+-*" + pull_request: + workflow_dispatch: env: - DOTNET_NOLOGO: true + DOTNET_NOLOGO: true jobs: - build: - name: Build - runs-on: ubuntu-22.04 - steps: - - name: Checkout - uses: actions/checkout@v4.1.2 - with: - fetch-depth: 0 - - name: Install .NET SDK - uses: actions/setup-dotnet@v4.0.0 - with: - dotnet-version: 9.0.x - - name: Build - run: dotnet build LibGit2Sharp.sln --configuration Release - - name: Upload packages - uses: actions/upload-artifact@v4.3.1 - with: - name: NuGet packages - path: artifacts/package/ - retention-days: 7 - - name: Verify trimming compatibility - run: dotnet publish TrimmingTestApp - test: - name: Test / ${{ matrix.os }} / ${{ matrix.arch }} / ${{ matrix.tfm }} - runs-on: ${{ matrix.os }} - strategy: - matrix: - arch: [ x64 ] - os: [ windows-2019, windows-2022, macos-13 ] - tfm: [ net472, net8.0, net9.0 ] - exclude: - - os: macos-13 - tfm: net472 - include: - - arch: arm64 - os: macos-14 - tfm: net8.0 - - arch: arm64 - os: macos-14 - tfm: net9.0 - fail-fast: false - steps: - - name: Checkout - uses: actions/checkout@v4.1.2 - with: - fetch-depth: 0 - - name: Install .NET SDK - uses: actions/setup-dotnet@v4.0.0 - with: - dotnet-version: | - 9.0.x - 8.0.x - - name: Run ${{ matrix.tfm }} tests - run: dotnet test LibGit2Sharp.sln --configuration Release --framework ${{ matrix.tfm }} --logger "GitHubActions" /p:ExtraDefine=LEAKS_IDENTIFYING - test-linux: - name: Test / ${{ matrix.distro }} / ${{ matrix.arch }} / ${{ matrix.tfm }} - runs-on: ${{ matrix.runnerImage }} - strategy: - matrix: - arch: [ amd64, arm64 ] - distro: [ alpine.3.17, alpine.3.18, alpine.3.19, alpine.3.20, centos.stream.9, debian.12, fedora.40, ubuntu.20.04, ubuntu.22.04, ubuntu.24.04 ] - sdk: [ '8.0', '9.0' ] - exclude: - - distro: alpine.3.17 - sdk: '9.0' - - distro: alpine.3.18 - sdk: '9.0' - - distro: alpine.3.19 - sdk: '9.0' - include: - - sdk: '8.0' - tfm: net8.0 - - sdk: '9.0' - tfm: net9.0 - - arch: amd64 - runnerImage: ubuntu-22.04 - - arch: arm64 - runnerImage: ubuntu-22.04-arm - fail-fast: false - steps: - - name: Checkout - uses: actions/checkout@v4.1.2 - with: - fetch-depth: 0 - - name: Run ${{ matrix.tfm }} tests - run: | - git_command="git config --global --add safe.directory /app" - test_command="dotnet test LibGit2Sharp.sln --configuration Release -p:TargetFrameworks=${{ matrix.tfm }} --logger "GitHubActions" -p:ExtraDefine=LEAKS_IDENTIFYING" - docker run -t --rm --platform linux/${{ matrix.arch }} -v "$PWD:/app" -e OPENSSL_ENABLE_SHA1_SIGNATURES=1 gittools/build-images:${{ matrix.distro }}-sdk-${{ matrix.sdk }} sh -c "$git_command && $test_command" + build: + name: Build + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + - name: Install .NET SDK + uses: actions/setup-dotnet@v5 + with: + dotnet-version: 9.0.x + - name: Compute version override for branch builds + if: ${{ !startsWith(github.ref, 'refs/tags/') }} + id: version + run: | + # Latest release tag matching the convention -octopus. + LATEST=$(git tag --list --sort=-v:refname | grep -E '^[0-9]+\.[0-9]+\.[0-9]+-octopus\.[0-9]+$' | head -n 1) + if [ -z "$LATEST" ]; then + echo "::error::No release tag matching -octopus. found" + exit 1 + fi + # Sanitize branch name: lowercase, replace non-alphanumeric with hyphen, trim to 20 chars + BRANCH="${GITHUB_HEAD_REF:-${GITHUB_REF_NAME}}" + SAFE_BRANCH=$(echo "$BRANCH" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//' | sed 's/-$//' | cut -c1-20) + # Join with '.' (not '-') so branch/run land as separate prerelease IDs, keeping ordering correct vs the next octopus. + echo "override=${LATEST}.${SAFE_BRANCH}.${{ github.run_number }}" >> "$GITHUB_OUTPUT" + - name: Build + run: dotnet build LibGit2Sharp.sln --configuration Release ${{ steps.version.outputs.override && format('/p:MinVerVersionOverride={0}', steps.version.outputs.override) || '' }} + - name: Upload packages + uses: actions/upload-artifact@v7 + with: + name: NuGet packages + path: artifacts/package/ + retention-days: 7 + - name: Verify trimming compatibility + run: dotnet publish TrimmingTestApp + test: + name: Test / ${{ matrix.os }} / ${{ matrix.arch }} / ${{ matrix.tfm }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + arch: [x64] + os: [windows-2022, macos-14] + tfm: [net472, net8.0, net9.0] + exclude: + - os: macos-14 + tfm: net472 + include: + - arch: arm64 + os: macos-14 + tfm: net8.0 + - arch: arm64 + os: macos-14 + tfm: net9.0 + fail-fast: false + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + - name: Install .NET SDK + uses: actions/setup-dotnet@v5 + with: + dotnet-version: | + 9.0.x + 8.0.x + - name: Run ${{ matrix.tfm }} tests + run: dotnet test LibGit2Sharp.sln --configuration Release --framework ${{ matrix.tfm }} --logger "GitHubActions" /p:ExtraDefine=LEAKS_IDENTIFYING + test-linux: + name: Test / ${{ matrix.distro }} / ${{ matrix.arch }} / ${{ matrix.tfm }} + runs-on: ${{ matrix.runnerImage }} + strategy: + matrix: + arch: [amd64, arm64] + distro: + [ + alpine.3.20, + alpine.3.21, + alpine.3.22, + centos.stream.9, + debian.12, + fedora.41, + fedora.42, + ubuntu.22.04, + ubuntu.24.04, + ] + sdk: ["8.0", "9.0"] + include: + - sdk: "8.0" + tfm: net8.0 + - sdk: "9.0" + tfm: net9.0 + - arch: amd64 + runnerImage: ubuntu-22.04 + - arch: arm64 + runnerImage: ubuntu-22.04-arm + fail-fast: false + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + - name: Run ${{ matrix.tfm }} tests + run: | + git_command="git config --global --add safe.directory /app" + test_command="dotnet test LibGit2Sharp.sln --configuration Release -p:TargetFrameworks=${{ matrix.tfm }} --logger "GitHubActions" -p:ExtraDefine=LEAKS_IDENTIFYING" + docker run -t --rm --platform linux/${{ matrix.arch }} -v "$PWD:/app" -w /app -e OPENSSL_ENABLE_SHA1_SIGNATURES=1 gittools/build-images:${{ matrix.distro }}-sdk-${{ matrix.sdk }} sh -c "$git_command && $test_command" + + nuget-push: + name: Octopus NuGet Push + needs: [build, test, test-linux] + # && github.ref == 'refs/heads/octopus/master' + if: github.actor != 'dependabot[bot]' && github.actor != 'dependabot-preview[bot]' && github.event_name != 'schedule' + runs-on: ubuntu-22.04 + steps: + - uses: actions/download-artifact@v8 + + with: + path: staging + - name: Push package to feed 🐙 + id: push-feed + shell: bash + env: + FEED_API_KEY: ${{ secrets.FEED_API_KEY }} + FEED_SOURCE: ${{ secrets.FEED_SOURCE }} + run: dotnet nuget push staging/**/*.nupkg --api-key "$FEED_API_KEY" --source "$FEED_SOURCE" --skip-duplicate diff --git a/.gitignore b/.gitignore index 32e17b4d0..ee0a07b78 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,7 @@ _ReSharper*/ *.swp *.DotSettings +.idea/ _NCrunch_LibGit2Sharp/ artifacts/ worktree.playlist diff --git a/.idea/.idea.LibGit2Sharp/.idea/.gitignore b/.idea/.idea.LibGit2Sharp/.idea/.gitignore new file mode 100644 index 000000000..b3c268b2e --- /dev/null +++ b/.idea/.idea.LibGit2Sharp/.idea/.gitignore @@ -0,0 +1,13 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/contentModel.xml +/projectSettingsUpdater.xml +/modules.xml +/.idea.LibGit2Sharp.iml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/.idea.LibGit2Sharp/.idea/.name b/.idea/.idea.LibGit2Sharp/.idea/.name new file mode 100644 index 000000000..1bd867fe1 --- /dev/null +++ b/.idea/.idea.LibGit2Sharp/.idea/.name @@ -0,0 +1 @@ +LibGit2Sharp \ No newline at end of file diff --git a/.idea/.idea.LibGit2Sharp/.idea/encodings.xml b/.idea/.idea.LibGit2Sharp/.idea/encodings.xml new file mode 100644 index 000000000..df87cf951 --- /dev/null +++ b/.idea/.idea.LibGit2Sharp/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/.idea.LibGit2Sharp/.idea/indexLayout.xml b/.idea/.idea.LibGit2Sharp/.idea/indexLayout.xml new file mode 100644 index 000000000..323a9b706 --- /dev/null +++ b/.idea/.idea.LibGit2Sharp/.idea/indexLayout.xml @@ -0,0 +1,10 @@ + + + + + . + + + + + \ No newline at end of file diff --git a/.idea/.idea.LibGit2Sharp/.idea/vcs.xml b/.idea/.idea.LibGit2Sharp/.idea/vcs.xml new file mode 100644 index 000000000..35eb1ddfb --- /dev/null +++ b/.idea/.idea.LibGit2Sharp/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/LibGit2Sharp.Tests/GlobalSettingsFixture.cs b/LibGit2Sharp.Tests/GlobalSettingsFixture.cs index 925efc3d0..41ace8e58 100644 --- a/LibGit2Sharp.Tests/GlobalSettingsFixture.cs +++ b/LibGit2Sharp.Tests/GlobalSettingsFixture.cs @@ -61,7 +61,6 @@ public void LoadFromSpecifiedPath(string architecture) { Skip.IfNot(Platform.IsRunningOnNetFramework(), ".NET Framework only test."); - var nativeDllFileName = NativeDllName.Name + ".dll"; var testDir = Path.GetDirectoryName(typeof(GlobalSettingsFixture).Assembly.Location); var testAppExe = Path.Combine(testDir, $"NativeLibraryLoadTestApp.{architecture}.exe"); var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); @@ -71,7 +70,10 @@ public void LoadFromSpecifiedPath(string architecture) try { Directory.CreateDirectory(platformDir); - File.Copy(Path.Combine(libraryPath, nativeDllFileName), Path.Combine(platformDir, nativeDllFileName)); + foreach (var file in Directory.GetFiles(libraryPath, "*.dll")) + { + File.Copy(file, Path.Combine(platformDir, Path.GetFileName(file))); + } var (output, exitCode) = ProcessHelper.RunProcess(testAppExe, arguments: $@"{NativeDllName.Name} ""{platformDir}""", workingDirectory: tempDir); @@ -90,17 +92,17 @@ public void SetExtensions() var extensions = GlobalSettings.GetExtensions(); // Assert that "noop" is supported by default - Assert.Equal(new[] { "noop", "objectformat", "worktreeconfig" }, extensions); + Assert.Equal(new[] { "noop", "objectformat", "preciousobjects", "worktreeconfig" }, extensions); // Disable "noop" extensions GlobalSettings.SetExtensions("!noop"); extensions = GlobalSettings.GetExtensions(); - Assert.Equal(new[] { "objectformat", "worktreeconfig" }, extensions); + Assert.Equal(new[] { "objectformat", "preciousobjects", "worktreeconfig" }, extensions); // Enable two new extensions (it will reset the configuration and "noop" will be enabled) GlobalSettings.SetExtensions("partialclone", "newext"); extensions = GlobalSettings.GetExtensions(); - Assert.Equal(new[] { "newext", "noop", "objectformat", "partialclone", "worktreeconfig" }, extensions); + Assert.Equal(new[] { "newext", "noop", "objectformat", "partialclone", "preciousobjects", "worktreeconfig" }, extensions); } [Fact] diff --git a/LibGit2Sharp.Tests/NetworkFixture.cs b/LibGit2Sharp.Tests/NetworkFixture.cs index f4ad922f6..9f21fa90e 100644 --- a/LibGit2Sharp.Tests/NetworkFixture.cs +++ b/LibGit2Sharp.Tests/NetworkFixture.cs @@ -22,7 +22,6 @@ public void CanListRemoteReferences(string url) Remote remote = repo.Network.Remotes.Add(remoteName, url); IList references = repo.Network.ListReferences(remote).ToList(); - foreach (var reference in references) { // None of those references point to an existing @@ -136,6 +135,133 @@ public void CanListRemoteReferencesWithCredentials() } } + [Theory] + [InlineData("http://github.com/libgit2/TestGitRepository")] + [InlineData("https://github.com/libgit2/TestGitRepository")] + public void CanListRemoteReferencesWithListRemoteOptions(string url) + { + string remoteName = "testRemote"; + + string repoPath = InitNewRepository(); + + using (var repo = new Repository(repoPath)) + { + Remote remote = repo.Network.Remotes.Add(remoteName, url); + var options = new ListRemoteOptions + { + ProxyOptions = new ProxyOptions() + }; + + IList references = repo.Network.ListReferences(remote, options).ToList(); + + foreach (var reference in references) + { + Assert.Null(reference.ResolveToDirectReference().Target); + } + + List> actualRefs = references. + Select(directRef => new Tuple(directRef.CanonicalName, directRef.ResolveToDirectReference() + .TargetIdentifier)).ToList(); + + Assert.Equal(TestRemoteRefs.ExpectedRemoteRefs.Count, actualRefs.Count); + Assert.True(references.Single(reference => reference.CanonicalName == "HEAD") is SymbolicReference); + for (int i = 0; i < TestRemoteRefs.ExpectedRemoteRefs.Count; i++) + { + Assert.Equal(TestRemoteRefs.ExpectedRemoteRefs[i].Item2, actualRefs[i].Item2); + Assert.Equal(TestRemoteRefs.ExpectedRemoteRefs[i].Item1, actualRefs[i].Item1); + } + } + } + + [Theory] + [InlineData("http://github.com/libgit2/TestGitRepository")] + [InlineData("https://github.com/libgit2/TestGitRepository")] + public void CanListRemoteReferencesFromUrlWithListRemoteOptions(string url) + { + string repoPath = InitNewRepository(); + + using (var repo = new Repository(repoPath)) + { + var options = new ListRemoteOptions + { + ProxyOptions = new ProxyOptions() + }; + + IList references = repo.Network.ListReferences(url, options).ToList(); + + foreach (var reference in references) + { + Assert.Null(reference.ResolveToDirectReference().Target); + } + + List> actualRefs = references. + Select(directRef => new Tuple(directRef.CanonicalName, directRef.ResolveToDirectReference() + .TargetIdentifier)).ToList(); + + Assert.Equal(TestRemoteRefs.ExpectedRemoteRefs.Count, actualRefs.Count); + Assert.True(references.Single(reference => reference.CanonicalName == "HEAD") is SymbolicReference); + for (int i = 0; i < TestRemoteRefs.ExpectedRemoteRefs.Count; i++) + { + Assert.Equal(TestRemoteRefs.ExpectedRemoteRefs[i].Item2, actualRefs[i].Item2); + Assert.Equal(TestRemoteRefs.ExpectedRemoteRefs[i].Item1, actualRefs[i].Item1); + } + } + } + + [Theory] + [InlineData("https://github.com/libgit2/TestGitRepository")] + public void CanListRemoteReferencesWithCertificateCheckCallback(string url) + { + string repoPath = InitNewRepository(); + + bool certificateCheckCalled = false; + + using (var repo = new Repository(repoPath)) + { + var options = new ListRemoteOptions + { + CertificateCheck = (cert, valid, host) => + { + certificateCheckCalled = true; + return true; + } + }; + + IList references = repo.Network.ListReferences(url, options).ToList(); + + Assert.True(certificateCheckCalled); + Assert.NotEmpty(references); + } + } + + [SkippableFact] + public void CanListRemoteReferencesWithCredentialsInListRemoteOptions() + { + InconclusiveIf(() => string.IsNullOrEmpty(Constants.PrivateRepoUrl), + "Populate Constants.PrivateRepo* to run this test"); + + string remoteName = "origin"; + + string repoPath = InitNewRepository(); + + using (var repo = new Repository(repoPath)) + { + Remote remote = repo.Network.Remotes.Add(remoteName, Constants.PrivateRepoUrl); + + var options = new ListRemoteOptions + { + CredentialsProvider = Constants.PrivateRepoCredentials + }; + + var references = repo.Network.ListReferences(remote, options); + + foreach (var reference in references) + { + Assert.NotNull(reference); + } + } + } + [Theory] [InlineData(FastForwardStrategy.Default)] [InlineData(FastForwardStrategy.NoFastForward)] diff --git a/LibGit2Sharp.Tests/PushFixture.cs b/LibGit2Sharp.Tests/PushFixture.cs index 824c1d8c0..29969ca59 100644 --- a/LibGit2Sharp.Tests/PushFixture.cs +++ b/LibGit2Sharp.Tests/PushFixture.cs @@ -135,6 +135,18 @@ public void CanInvokePrePushCallbackAndFail() Assert.True(prePushHandlerCalled); } + [Fact] + public void CanPushWithRemoteProgressCallback() + { + PushOptions options = new PushOptions() + { + OnPushStatusError = OnPushStatusError, + OnPushRemoteProgress = (progress) => { return true; }, + }; + + AssertPush(repo => repo.Network.Push(repo.Network.Remotes["origin"], "HEAD", @"refs/heads/master", options)); + } + [Fact] public void PushingABranchThatDoesNotTrackAnUpstreamBranchThrows() { diff --git a/LibGit2Sharp.Tests/RepositoryFixture.cs b/LibGit2Sharp.Tests/RepositoryFixture.cs index ef3e72f07..891737ee5 100644 --- a/LibGit2Sharp.Tests/RepositoryFixture.cs +++ b/LibGit2Sharp.Tests/RepositoryFixture.cs @@ -724,6 +724,30 @@ public void CanListRemoteReferences(string url) } } + [Theory] + [InlineData("http://github.com/libgit2/TestGitRepository")] + [InlineData("https://github.com/libgit2/TestGitRepository")] + public void CanListRemoteReferencesWithListRemoteOptions(string url) + { + var options = new ListRemoteOptions + { + ProxyOptions = new ProxyOptions() + }; + + IEnumerable references = Repository.ListRemoteReferences(url, options).ToList(); + + List> actualRefs = references. + Select(reference => new Tuple(reference.CanonicalName, reference.ResolveToDirectReference().TargetIdentifier)).ToList(); + + Assert.Equal(TestRemoteRefs.ExpectedRemoteRefs.Count, actualRefs.Count); + Assert.True(references.Single(reference => reference.CanonicalName == "HEAD") is SymbolicReference); + for (int i = 0; i < TestRemoteRefs.ExpectedRemoteRefs.Count; i++) + { + Assert.Equal(TestRemoteRefs.ExpectedRemoteRefs[i].Item2, actualRefs[i].Item2); + Assert.Equal(TestRemoteRefs.ExpectedRemoteRefs[i].Item1, actualRefs[i].Item1); + } + } + [Fact] public void CanListRemoteReferencesWithDetachedRemoteHead() { diff --git a/LibGit2Sharp/CertificateSsh.cs b/LibGit2Sharp/CertificateSsh.cs index 683c04402..37c3e3052 100644 --- a/LibGit2Sharp/CertificateSsh.cs +++ b/LibGit2Sharp/CertificateSsh.cs @@ -25,6 +25,11 @@ protected CertificateSsh() /// public readonly byte[] HashSHA1; + /// + /// The SHA256 hash of the host. Meaningful if is true + /// + public readonly byte[] HashSHA256; + /// /// True if we have the MD5 hostkey hash from the server /// @@ -35,11 +40,17 @@ protected CertificateSsh() /// public readonly bool HasSHA1; + /// + /// True if we have the SHA256 hostkey hash from the server + /// + public readonly bool HasSHA256; + internal unsafe CertificateSsh(git_certificate_ssh* cert) { HasMD5 = cert->type.HasFlag(GitCertificateSshType.MD5); HasSHA1 = cert->type.HasFlag(GitCertificateSshType.SHA1); + HasSHA256 = cert->type.HasFlag(GitCertificateSshType.SHA256); HashMD5 = new byte[16]; for (var i = 0; i < HashMD5.Length; i++) @@ -52,6 +63,12 @@ internal unsafe CertificateSsh(git_certificate_ssh* cert) { HashSHA1[i] = cert->HashSHA1[i]; } + + HashSHA256 = new byte[32]; + for (var i = 0; i < HashSHA256.Length; i++) + { + HashSHA256[i] = cert->HashSHA256[i]; + } } internal unsafe IntPtr ToPointer() @@ -65,6 +82,10 @@ internal unsafe IntPtr ToPointer() { sshCertType |= GitCertificateSshType.SHA1; } + if (HasSHA256) + { + sshCertType |= GitCertificateSshType.SHA256; + } var gitCert = new git_certificate_ssh() { @@ -88,6 +109,14 @@ internal unsafe IntPtr ToPointer() } } + fixed (byte* p = &HashSHA256[0]) + { + for (var i = 0; i < HashSHA256.Length; i++) + { + gitCert.HashSHA256[i] = p[i]; + } + } + var ptr = Marshal.AllocHGlobal(Marshal.SizeOf(gitCert)); Marshal.StructureToPtr(gitCert, ptr, false); diff --git a/LibGit2Sharp/Core/Ensure.cs b/LibGit2Sharp/Core/Ensure.cs index cd681e4ba..87129e0bd 100644 --- a/LibGit2Sharp/Core/Ensure.cs +++ b/LibGit2Sharp/Core/Ensure.cs @@ -148,7 +148,7 @@ private static unsafe void HandleError(int result) Func exceptionBuilder; if (!GitErrorsToLibGit2SharpExceptions.TryGetValue((GitErrorCode)result, out exceptionBuilder)) { - exceptionBuilder = (m, c) => new LibGit2SharpException(m, c); + exceptionBuilder = (m, c) => new LibGit2SharpException(m); } throw exceptionBuilder(errorMessage, errorCategory); diff --git a/LibGit2Sharp/Core/GitBlame.cs b/LibGit2Sharp/Core/GitBlame.cs index d484b0b4b..7bdeee82e 100644 --- a/LibGit2Sharp/Core/GitBlame.cs +++ b/LibGit2Sharp/Core/GitBlame.cs @@ -61,12 +61,15 @@ internal unsafe struct git_blame_hunk public git_oid final_commit_id; public UIntPtr final_start_line_number; public git_signature* final_signature; + public git_signature* final_committer; public git_oid orig_commit_id; public char* orig_path; public UIntPtr orig_start_line_number; public git_signature* orig_signature; + public git_signature* orig_committer; + public char* summary; public byte boundary; } diff --git a/LibGit2Sharp/Core/GitCertificateSsh.cs b/LibGit2Sharp/Core/GitCertificateSsh.cs index e3e7c4927..bc49bc9d0 100644 --- a/LibGit2Sharp/Core/GitCertificateSsh.cs +++ b/LibGit2Sharp/Core/GitCertificateSsh.cs @@ -1,4 +1,5 @@ -using System.Runtime.InteropServices; +using System; +using System.Runtime.InteropServices; namespace LibGit2Sharp.Core { @@ -17,5 +18,14 @@ internal unsafe struct git_certificate_ssh /// The SHA1 hash (if appropriate) /// public unsafe fixed byte HashSHA1[20]; + + /// + /// The SHA256 hash (if appropriate) + /// + public unsafe fixed byte HashSHA256[32]; + + public int raw_type; + public byte* hostkey; + public UIntPtr hostkey_len; } } diff --git a/LibGit2Sharp/Core/GitCertificateSshType.cs b/LibGit2Sharp/Core/GitCertificateSshType.cs index 4fc432e9a..6fde63fc3 100644 --- a/LibGit2Sharp/Core/GitCertificateSshType.cs +++ b/LibGit2Sharp/Core/GitCertificateSshType.cs @@ -7,5 +7,6 @@ internal enum GitCertificateSshType { MD5 = (1 << 0), SHA1 = (1 << 1), + SHA256 = (1 << 2), } } diff --git a/LibGit2Sharp/Core/GitCheckoutOpts.cs b/LibGit2Sharp/Core/GitCheckoutOpts.cs index 053258565..8d025530c 100644 --- a/LibGit2Sharp/Core/GitCheckoutOpts.cs +++ b/LibGit2Sharp/Core/GitCheckoutOpts.cs @@ -7,14 +7,9 @@ namespace LibGit2Sharp.Core internal enum CheckoutStrategy { /// - /// Default is a dry run, no actual updates. + /// Allow safe updates that cannot overwrite uncommitted data. /// - GIT_CHECKOUT_NONE = 0, - - /// - /// Allow safe updates that cannot overwrite uncommited data. - /// - GIT_CHECKOUT_SAFE = (1 << 0), + GIT_CHECKOUT_SAFE = 0, /// /// Allow update of entries in working dir that are modified from HEAD. @@ -105,6 +100,22 @@ internal enum CheckoutStrategy /// GIT_CHECKOUT_DONT_WRITE_INDEX = (1 << 23), + /// + /// Perform a dry run, reporting what would be done but without + /// actually making changes in the working directory or the index. + /// + GIT_CHECKOUT_DRY_RUN = (1 << 24), + + /// + /// Include common ancestor data in zdiff3 format for conflicts. + /// + GIT_CHECKOUT_CONFLICT_STYLE_ZDIFF3 = (1 << 25), + + /// + /// Do not do a checkout and do not fire callbacks. + /// + GIT_CHECKOUT_NONE = (1 << 30), + // THE FOLLOWING OPTIONS ARE NOT YET IMPLEMENTED /// diff --git a/LibGit2Sharp/Core/GitConfigEntry.cs b/LibGit2Sharp/Core/GitConfigEntry.cs index 7af657894..72f0c14c7 100644 --- a/LibGit2Sharp/Core/GitConfigEntry.cs +++ b/LibGit2Sharp/Core/GitConfigEntry.cs @@ -11,6 +11,5 @@ internal unsafe struct GitConfigEntry public char* origin_path; public uint include_depth; public uint level; - public void* freePtr; } } diff --git a/LibGit2Sharp/Core/GitDiff.cs b/LibGit2Sharp/Core/GitDiff.cs index 44679124d..1ce097758 100644 --- a/LibGit2Sharp/Core/GitDiff.cs +++ b/LibGit2Sharp/Core/GitDiff.cs @@ -230,6 +230,7 @@ internal class GitDiffOptions : IDisposable public uint ContextLines; public uint InterhunkLines; + public uint OidType; public ushort IdAbbrev; public long MaxSize; public IntPtr OldPrefixString; diff --git a/LibGit2Sharp/Core/GitRebaseOptions.cs b/LibGit2Sharp/Core/GitRebaseOptions.cs index 981bfe919..8f62bf530 100644 --- a/LibGit2Sharp/Core/GitRebaseOptions.cs +++ b/LibGit2Sharp/Core/GitRebaseOptions.cs @@ -21,5 +21,7 @@ internal class GitRebaseOptions private IntPtr padding; // TODO: add git_commit_create_cb public NativeMethods.commit_signing_callback signing_callback; + + public IntPtr payload; } } diff --git a/LibGit2Sharp/Core/GitRemoteCallbacks.cs b/LibGit2Sharp/Core/GitRemoteCallbacks.cs index 4900ad562..4ac006d0e 100644 --- a/LibGit2Sharp/Core/GitRemoteCallbacks.cs +++ b/LibGit2Sharp/Core/GitRemoteCallbacks.cs @@ -38,5 +38,7 @@ internal struct GitRemoteCallbacks internal IntPtr payload; internal NativeMethods.url_resolve_callback resolve_url; + + internal IntPtr update_refs; } } diff --git a/LibGit2Sharp/Core/GitStatusOptions.cs b/LibGit2Sharp/Core/GitStatusOptions.cs index d577cefe6..c5181bec5 100644 --- a/LibGit2Sharp/Core/GitStatusOptions.cs +++ b/LibGit2Sharp/Core/GitStatusOptions.cs @@ -15,6 +15,8 @@ internal class GitStatusOptions : IDisposable public IntPtr Baseline = IntPtr.Zero; + public ushort RenameThreshold; + public void Dispose() { PathSpec.Dispose(); diff --git a/LibGit2Sharp/Core/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs index cbb850b16..d78921d59 100644 --- a/LibGit2Sharp/Core/NativeMethods.cs +++ b/LibGit2Sharp/Core/NativeMethods.cs @@ -102,12 +102,37 @@ private static IntPtr ResolveDll(string libraryName, Assembly assembly, DllImpor // libc/OpenSSL libraries. Try them out. if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { - // The libraries are located at 'runtimes//native/lib{libraryName}.so' - // The ends with the processor architecture. e.g. fedora-x64. string assemblyDirectory = Path.GetDirectoryName(AppContext.BaseDirectory); string processorArchitecture = RuntimeInformation.ProcessArchitecture.ToString().ToLowerInvariant(); string runtimesDirectory = Path.Combine(assemblyDirectory, "runtimes"); + // The default libgit2 binary is linked against OpenSSL 3. On hosts that have only + // libcrypto.so.1.1 fall back to the OpenSSL-1.1 variant shipped alongside it. We probe + // both layouts: flat (self-contained publish copies natives next to the assembly) and + // 'runtimes//native/' (framework-dependent / build output). + if (!NativeLibrary.TryLoad("libcrypto.so.3", out _) && NativeLibrary.TryLoad("libcrypto.so.1.1", out _)) + { + string variantFile = $"lib{libraryName}-openssl1.1.so"; + + string flatVariantPath = Path.Combine(assemblyDirectory, variantFile); + if (NativeLibrary.TryLoad(flatVariantPath, out handle)) + { + return handle; + } + + if (Directory.Exists(runtimesDirectory)) + { + foreach (var runtimeFolder in Directory.GetDirectories(runtimesDirectory, $"*-{processorArchitecture}")) + { + string variantPath = Path.Combine(runtimeFolder, "native", variantFile); + if (NativeLibrary.TryLoad(variantPath, out handle)) + { + return handle; + } + } + } + } + if (Directory.Exists(runtimesDirectory)) { foreach (var runtimeFolder in Directory.GetDirectories(runtimesDirectory, $"*-{processorArchitecture}")) @@ -132,8 +157,26 @@ private static IntPtr ResolveDll(string libraryName, Assembly assembly, DllImpor [DllImport("libdl", EntryPoint = "dlopen")] private static extern IntPtr LoadUnixLibrary(string path, int flags); - [DllImport("kernel32", EntryPoint = "LoadLibrary")] - private static extern IntPtr LoadWindowsLibrary(string path); + [DllImport("kernel32", EntryPoint = "AddDllDirectory", CharSet = CharSet.Unicode)] + private static extern IntPtr AddDllDirectory(string path); + + [DllImport("kernel32", EntryPoint = "LoadLibraryExW", CharSet = CharSet.Unicode)] + private static extern IntPtr LoadWindowsLibraryEx(string path, IntPtr hFile, uint flags); + + private const uint LOAD_LIBRARY_SEARCH_DEFAULT_DIRS = 0x00001000; + + // Use AddDllDirectory + LoadLibraryEx so that transitive native dependencies + // (e.g. libssh2 -> libcrypto) in the same directory are resolved at load time. + private static IntPtr LoadWindowsLibrary(string path) + { + var directory = Path.GetDirectoryName(path); + if (directory != null) + { + AddDllDirectory(directory); + } + + return LoadWindowsLibraryEx(path, IntPtr.Zero, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS); + } // Avoid inlining this method because otherwise mono's JITter may try // to load the library _before_ we've configured the path. diff --git a/LibGit2Sharp/Core/SshExtensions.cs b/LibGit2Sharp/Core/SshExtensions.cs new file mode 100644 index 000000000..5c571707d --- /dev/null +++ b/LibGit2Sharp/Core/SshExtensions.cs @@ -0,0 +1,27 @@ +using LibGit2Sharp.Core; +using System; +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Ssh +{ + internal static class NativeMethods + { + private const string libgit2 = NativeDllName.Name; + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_cred_ssh_key_new( + out IntPtr cred, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string username, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string publickey, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string privatekey, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string passphrase); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_cred_ssh_key_memory_new( + out IntPtr cred, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string username, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string publickey, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string privatekey, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string passphrase); + } +} diff --git a/LibGit2Sharp/LibGit2Sharp.csproj b/LibGit2Sharp/LibGit2Sharp.csproj index 1c4abef7b..f64ef4086 100644 --- a/LibGit2Sharp/LibGit2Sharp.csproj +++ b/LibGit2Sharp/LibGit2Sharp.csproj @@ -19,8 +19,9 @@ App_Readme/README.md App_Readme/LICENSE.md true + Octopus.LibGit2Sharp $(ArtifactsPath)\package - preview.0 + octopus.0 libgit2-$(libgit2_hash.Substring(0,7)) @@ -29,7 +30,7 @@ - + diff --git a/LibGit2Sharp/ListRemoteOptions.cs b/LibGit2Sharp/ListRemoteOptions.cs new file mode 100644 index 000000000..d7ec62835 --- /dev/null +++ b/LibGit2Sharp/ListRemoteOptions.cs @@ -0,0 +1,26 @@ +using LibGit2Sharp.Handlers; + +namespace LibGit2Sharp; + +/// +/// Options controlling ListRemote behavior. +/// +public sealed class ListRemoteOptions +{ + /// + /// Handler to generate for authentication. + /// + public CredentialsHandler CredentialsProvider { get; set; } + + /// + /// This handler will be called to let the user make a decision on whether to allow + /// the connection to proceed based on the certificate presented by the server. + /// + public CertificateCheckHandler CertificateCheck { get; set; } + + + /// + /// Options for connecting through a proxy. + /// + public ProxyOptions ProxyOptions { get; set; } = new(); +} diff --git a/LibGit2Sharp/Network.cs b/LibGit2Sharp/Network.cs index ba0a33144..cddc770b0 100644 --- a/LibGit2Sharp/Network.cs +++ b/LibGit2Sharp/Network.cs @@ -52,7 +52,31 @@ public virtual IEnumerable ListReferences(Remote remote) { Ensure.ArgumentNotNull(remote, "remote"); - return ListReferencesInternal(remote.Url, null, new ProxyOptions()); + var options = new ListRemoteOptions() + { + ProxyOptions = new ProxyOptions() + }; + + return ListReferencesInternal(remote.Url, options); + } + + /// + /// List references in a repository. + /// + /// When the remote tips are ahead of the local ones, the retrieved + /// s may point to non existing + /// s in the local repository. In that + /// case, will return null. + /// + /// + /// The to list from. + /// The options for the remote request. + /// The references in the repository. + public virtual IEnumerable ListReferences(Remote remote, ListRemoteOptions options) + { + Ensure.ArgumentNotNull(remote, "remote"); + + return ListReferencesInternal(remote.Url, options); } /// @@ -71,7 +95,12 @@ public virtual IEnumerable ListReferences(Remote remote, ProxyOptions { Ensure.ArgumentNotNull(remote, "remote"); - return ListReferencesInternal(remote.Url, null, proxyOptions); + var options = new ListRemoteOptions() + { + ProxyOptions = proxyOptions + }; + + return ListReferencesInternal(remote.Url, options); } /// @@ -91,7 +120,13 @@ public virtual IEnumerable ListReferences(Remote remote, CredentialsH Ensure.ArgumentNotNull(remote, "remote"); Ensure.ArgumentNotNull(credentialsProvider, "credentialsProvider"); - return ListReferencesInternal(remote.Url, credentialsProvider, new ProxyOptions()); + var options = new ListRemoteOptions() + { + ProxyOptions = new ProxyOptions(), + CredentialsProvider = credentialsProvider + }; + + return ListReferencesInternal(remote.Url, options); } /// @@ -112,7 +147,32 @@ public virtual IEnumerable ListReferences(Remote remote, CredentialsH Ensure.ArgumentNotNull(remote, "remote"); Ensure.ArgumentNotNull(credentialsProvider, "credentialsProvider"); - return ListReferencesInternal(remote.Url, credentialsProvider, proxyOptions); + var options = new ListRemoteOptions() + { + ProxyOptions = proxyOptions, + CredentialsProvider = credentialsProvider + }; + + return ListReferencesInternal(remote.Url, options); + } + + /// + /// List references in a remote repository. + /// + /// When the remote tips are ahead of the local ones, the retrieved + /// s may point to non existing + /// s in the local repository. In that + /// case, will return null. + /// + /// + /// The url to list from. + /// The options for the remote request. + /// The references in the remote repository. + public virtual IEnumerable ListReferences(string url, ListRemoteOptions options) + { + Ensure.ArgumentNotNull(url, "url"); + + return ListReferencesInternal(url, options); } /// @@ -130,7 +190,12 @@ public virtual IEnumerable ListReferences(string url) { Ensure.ArgumentNotNull(url, "url"); - return ListReferencesInternal(url, null, new ProxyOptions()); + var options = new ListRemoteOptions + { + ProxyOptions = new ProxyOptions() + }; + + return ListReferencesInternal(url, options); } /// @@ -148,8 +213,12 @@ public virtual IEnumerable ListReferences(string url) public virtual IEnumerable ListReferences(string url, ProxyOptions proxyOptions) { Ensure.ArgumentNotNull(url, "url"); + var options = new ListRemoteOptions() + { + ProxyOptions = proxyOptions + }; - return ListReferencesInternal(url, null, proxyOptions); + return ListReferencesInternal(url, options); } /// @@ -169,7 +238,13 @@ public virtual IEnumerable ListReferences(string url, CredentialsHand Ensure.ArgumentNotNull(url, "url"); Ensure.ArgumentNotNull(credentialsProvider, "credentialsProvider"); - return ListReferencesInternal(url, credentialsProvider, new ProxyOptions()); + var options = new ListRemoteOptions() + { + CredentialsProvider = credentialsProvider, + ProxyOptions = new ProxyOptions() + }; + + return ListReferencesInternal(url, options); } /// @@ -190,21 +265,26 @@ public virtual IEnumerable ListReferences(string url, CredentialsHand Ensure.ArgumentNotNull(url, "url"); Ensure.ArgumentNotNull(credentialsProvider, "credentialsProvider"); - return ListReferencesInternal(url, credentialsProvider, new ProxyOptions()); + var options = new ListRemoteOptions() + { + CredentialsProvider = credentialsProvider, + ProxyOptions = new ProxyOptions() + }; + return ListReferencesInternal(url, options); } - private IEnumerable ListReferencesInternal(string url, CredentialsHandler credentialsProvider, ProxyOptions proxyOptions) + private IEnumerable ListReferencesInternal(string url, ListRemoteOptions options) { - proxyOptions ??= new(); + var proxyOptions = options?.ProxyOptions ?? new(); using RemoteHandle remoteHandle = BuildRemoteHandle(repository.Handle, url); using var proxyOptionsWrapper = new GitProxyOptionsWrapper(proxyOptions.CreateGitProxyOptions()); GitRemoteCallbacks gitCallbacks = new GitRemoteCallbacks { version = 1 }; - if (credentialsProvider != null) + if (options != null) { - var callbacks = new RemoteCallbacks(credentialsProvider); + var callbacks = new RemoteCallbacks(options); gitCallbacks = callbacks.GenerateCallbacks(); } diff --git a/LibGit2Sharp/PushOptions.cs b/LibGit2Sharp/PushOptions.cs index 829eb0d60..592f1c14c 100644 --- a/LibGit2Sharp/PushOptions.cs +++ b/LibGit2Sharp/PushOptions.cs @@ -52,6 +52,11 @@ public sealed class PushOptions /// public PrePushHandler OnNegotiationCompletedBeforePush { get; set; } + /// + /// Handler for receiving textual progress from the remote. + /// + public ProgressHandler OnPushRemoteProgress { get; set; } + /// /// Get/Set the custom headers. /// diff --git a/LibGit2Sharp/RemoteCallbacks.cs b/LibGit2Sharp/RemoteCallbacks.cs index 6061b10e1..45e85eed1 100644 --- a/LibGit2Sharp/RemoteCallbacks.cs +++ b/LibGit2Sharp/RemoteCallbacks.cs @@ -17,6 +17,17 @@ internal RemoteCallbacks(CredentialsHandler credentialsProvider) CredentialsProvider = credentialsProvider; } + internal RemoteCallbacks(ListRemoteOptions listRemoteOptions) + { + if (listRemoteOptions == null) + { + return; + } + + CertificateCheck = listRemoteOptions.CertificateCheck; + CredentialsProvider = listRemoteOptions.CredentialsProvider; + } + internal RemoteCallbacks(PushOptions pushOptions) { if (pushOptions == null) @@ -30,6 +41,7 @@ internal RemoteCallbacks(PushOptions pushOptions) CertificateCheck = pushOptions.CertificateCheck; PushStatusError = pushOptions.OnPushStatusError; PrePushCallback = pushOptions.OnNegotiationCompletedBeforePush; + Progress = pushOptions.OnPushRemoteProgress; } internal RemoteCallbacks(FetchOptionsBase fetchOptions) @@ -285,6 +297,16 @@ private int GitCredentialHandler( types |= SupportedCredentialTypes.Default; } + if (credTypes.HasFlag(GitCredentialType.SshKey)) + { + types |= SupportedCredentialTypes.SshKey; + } + + if (credTypes.HasFlag(GitCredentialType.SshMemory)) + { + types |= SupportedCredentialTypes.SshMemory; + } + ptr = IntPtr.Zero; try { diff --git a/LibGit2Sharp/Repository.cs b/LibGit2Sharp/Repository.cs index 9ac5e2424..67bbde997 100644 --- a/LibGit2Sharp/Repository.cs +++ b/LibGit2Sharp/Repository.cs @@ -656,7 +656,7 @@ internal Commit LookupCommit(string committish) /// The references in the remote repository. public static IEnumerable ListRemoteReferences(string url) { - return ListRemoteReferences(url, null, new ProxyOptions()); + return ListRemoteReferences(url, (ListRemoteOptions)null); } /// @@ -667,7 +667,7 @@ public static IEnumerable ListRemoteReferences(string url) /// The references in the remote repository. public static IEnumerable ListRemoteReferences(string url, ProxyOptions proxyOptions) { - return ListRemoteReferences(url, null, proxyOptions); + return ListRemoteReferences(url, new ListRemoteOptions { ProxyOptions = proxyOptions }); } /// @@ -683,7 +683,7 @@ public static IEnumerable ListRemoteReferences(string url, ProxyOptio /// The references in the remote repository. public static IEnumerable ListRemoteReferences(string url, CredentialsHandler credentialsProvider) { - return ListRemoteReferences(url, credentialsProvider, new ProxyOptions()); + return ListRemoteReferences(url, new ListRemoteOptions { CredentialsProvider = credentialsProvider }); } /// @@ -699,23 +699,33 @@ public static IEnumerable ListRemoteReferences(string url, Credential /// Options for connecting through a proxy. /// The references in the remote repository. public static IEnumerable ListRemoteReferences(string url, CredentialsHandler credentialsProvider, ProxyOptions proxyOptions) + { + return ListRemoteReferences(url, new ListRemoteOptions + { + CredentialsProvider = credentialsProvider, + ProxyOptions = proxyOptions, + }); + } + + /// + /// Lists the Remote Repository References. + /// + /// The url to list from. + /// Options for connecting to the remote repository. + /// The references in the remote repository. + public static IEnumerable ListRemoteReferences(string url, ListRemoteOptions listRemoteOptions) { Ensure.ArgumentNotNull(url, "url"); - proxyOptions ??= new(); + listRemoteOptions ??= new ListRemoteOptions(); + var proxyOptions = listRemoteOptions.ProxyOptions ?? new ProxyOptions(); using RepositoryHandle repositoryHandle = Proxy.git_repository_new(); using RemoteHandle remoteHandle = Proxy.git_remote_create_anonymous(repositoryHandle, url); using var proxyOptionsWrapper = new GitProxyOptionsWrapper(proxyOptions.CreateGitProxyOptions()); - var gitCallbacks = new GitRemoteCallbacks { version = 1 }; - - if (credentialsProvider != null) - { - var callbacks = new RemoteCallbacks(credentialsProvider); - gitCallbacks = callbacks.GenerateCallbacks(); - } - + var callbacks = new RemoteCallbacks(listRemoteOptions); + var gitCallbacks = callbacks.GenerateCallbacks(); var gitProxyOptions = proxyOptionsWrapper.Options; Proxy.git_remote_connect(remoteHandle, GitDirection.Fetch, ref gitCallbacks, ref gitProxyOptions); diff --git a/LibGit2Sharp/SmartSubtransportStream.cs b/LibGit2Sharp/SmartSubtransportStream.cs index 008d1fcd0..247e90f34 100644 --- a/LibGit2Sharp/SmartSubtransportStream.cs +++ b/LibGit2Sharp/SmartSubtransportStream.cs @@ -119,6 +119,10 @@ private static int SetError(SmartSubtransportStream stream, Exception caught) { errorCode = ((NativeException)ret).ErrorCode; } + else + { + Proxy.git_error_set_str(GitErrorCategory.Unknown, caught); + } return (int)errorCode; } diff --git a/LibGit2Sharp/SshKeyCredentials.cs b/LibGit2Sharp/SshKeyCredentials.cs new file mode 100644 index 000000000..eb61d2ad9 --- /dev/null +++ b/LibGit2Sharp/SshKeyCredentials.cs @@ -0,0 +1,51 @@ +using System; +using LibGit2Sharp.Ssh; + +namespace LibGit2Sharp +{ + /// + /// Class that holds SSH username with key credentials for remote repository access. + /// + public sealed class SshKeyCredentials : Credentials + { + /// + /// Callback to acquire a credential object. + /// + /// The newly created credential object. + /// 0 for success, < 0 to indicate an error, > 0 to indicate no credential was acquired. + protected internal override int GitCredentialHandler(out IntPtr cred) + { + if (Username == null) + { + throw new InvalidOperationException("SshUserKeyCredentials contains a null Username."); + } + + if (PrivateKey == null) + { + throw new InvalidOperationException("SshUserKeyCredentials contains a null PrivateKey."); + } + + return NativeMethods.git_cred_ssh_key_new(out cred, Username, PublicKey, PrivateKey, Passphrase); + } + + /// + /// Username for SSH authentication. + /// + public string Username { get; set; } + + /// + /// Public key file location for SSH authentication. + /// + public string PublicKey { get; set; } + + /// + /// Private key file location for SSH authentication. + /// + public string PrivateKey { get; set; } + + /// + /// Passphrase for SSH authentication. + /// + public string Passphrase { get; set; } + } +} diff --git a/LibGit2Sharp/SshKeyMemoryCredentials.cs b/LibGit2Sharp/SshKeyMemoryCredentials.cs new file mode 100644 index 000000000..38c2a50b6 --- /dev/null +++ b/LibGit2Sharp/SshKeyMemoryCredentials.cs @@ -0,0 +1,51 @@ +using System; +using LibGit2Sharp.Ssh; + +namespace LibGit2Sharp +{ + /// + /// Class that holds SSH username with in-memory key credentials for remote repository access. + /// + public sealed class SshKeyMemoryCredentials : Credentials + { + /// + /// Callback to acquire a credential object. + /// + /// The newly created credential object. + /// 0 for success, < 0 to indicate an error, > 0 to indicate no credential was acquired. + protected internal override int GitCredentialHandler(out IntPtr cred) + { + if (Username == null) + { + throw new InvalidOperationException("SshUserKeyMemoryCredentials contains a null Username."); + } + + if (PrivateKey == null) + { + throw new InvalidOperationException("SshUserKeyMemoryCredentials contains a null PrivateKey."); + } + + return NativeMethods.git_cred_ssh_key_memory_new(out cred, Username, PublicKey, PrivateKey, Passphrase); + } + + /// + /// Username for SSH authentication. + /// + public string Username { get; set; } + + /// + /// Public key for SSH authentication. + /// + public string PublicKey { get; set; } + + /// + /// Private key for SSH authentication. + /// + public string PrivateKey { get; set; } + + /// + /// Passphrase for SSH authentication. + /// + public string Passphrase { get; set; } + } +} diff --git a/LibGit2Sharp/SupportedCredentialTypes.cs b/LibGit2Sharp/SupportedCredentialTypes.cs index bc38a259e..429684ba2 100644 --- a/LibGit2Sharp/SupportedCredentialTypes.cs +++ b/LibGit2Sharp/SupportedCredentialTypes.cs @@ -18,5 +18,15 @@ public enum SupportedCredentialTypes /// Ask Windows to provide its default credentials for the current user (e.g. NTLM) /// Default = (1 << 1), + + /// + /// SSH key credentials sourced from files + /// + SshKey = (1 << 2), + + /// + /// SSH key credentials sourced from in-memory keys + /// + SshMemory = (1 << 3), } } diff --git a/README.md b/README.md index 3aafdceb1..1debc15cf 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # LibGit2Sharp -[![CI](https://github.com/libgit2/libgit2sharp/actions/workflows/ci.yml/badge.svg)](https://github.com/libgit2/libgit2sharp/actions/workflows/ci.yml) +[![CI](https://github.com/libgit2/libgit2sharp/actions/workflows/ci.yml/badge.svg)](https://github.com/libgit2/libgit2sharp/actions/workflows/ci.yml) [![NuGet version (LibGit2Sharp)](https://img.shields.io/nuget/v/LibGit2Sharp.svg)](https://www.nuget.org/packages/LibGit2Sharp/) **LibGit2Sharp brings all the might and speed of [libgit2](http://libgit2.github.com/), a native Git implementation, to the managed world of .NET** @@ -36,6 +36,25 @@ You can do a few things to optimize running unit tests on Windows: 3. Install a RAM disk like [IMDisk](http://www.ltr-data.se/opencode.html/#ImDisk) and set `LibGit2TestPath` to use it. * Use `imdisk.exe -a -s 512M -m X: -p "/fs:fat /q /v:ramdisk /y"` to create a RAM disk. This command requires elevated privileges and can be placed into a scheduled task or run manually before you begin unit-testing. +## Releasing + +Releases are triggered by pushing a git tag. The tag format is: + +``` +-octopus. +``` + +Where `` is the version from the upstream libgit2sharp repo (e.g., `0.31.0`) and `` is an incrementing number starting at 1. The incrementing number resets to 1 when the upstream version changes. + +For example, for upstream version `0.31.0`: + +``` +git tag 0.31.0-octopus.1 +git push origin 0.31.0-octopus.1 +``` + +This triggers CI, which builds the assemblies, runs the tests, packs the `Octopus.LibGit2Sharp` NuGet package with the tag as its version, and pushes it to the configured feed. + ## Authors - **Code:** The LibGit2Sharp [contributors](https://github.com/libgit2/libgit2sharp/contributors) diff --git a/nuget.config b/nuget.config index 35696f810..119fc702c 100644 --- a/nuget.config +++ b/nuget.config @@ -2,5 +2,6 @@ +