From 8e5504679775327fcdaa88328334a981c53332d1 Mon Sep 17 00:00:00 2001 From: RobE Date: Fri, 18 Jul 2025 21:07:04 +1000 Subject: [PATCH 01/13] Update build process for Octopus fork package push --- .github/workflows/ci.yml | 22 ++++++++++++++++++++-- LibGit2Sharp/LibGit2Sharp.csproj | 3 ++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 54837ac35..038eab0ec 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,13 +32,14 @@ jobs: 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 ] + os: [ windows-2022, macos-13 ] tfm: [ net472, net8.0, net9.0 ] exclude: - os: macos-13 @@ -99,4 +100,21 @@ jobs: 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" - + + 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@v4 + 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" \ No newline at end of file diff --git a/LibGit2Sharp/LibGit2Sharp.csproj b/LibGit2Sharp/LibGit2Sharp.csproj index 1c4abef7b..f3ae850d4 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)) From bdc9c40688a510f6d740152bca193e7a1be5269c Mon Sep 17 00:00:00 2001 From: Dylan Lerch Date: Wed, 2 Nov 2022 09:23:40 +1000 Subject: [PATCH 02/13] Do not use string formatting LibGit2SharpException constructor when building unknown exception types The exception constructors in GitErrorsToLibGit2SharpExceptions make use of the error category by passing it through to the NativeException(string message, GitErrorCategory category) constructor which adds ("libgit2.category", category) to Data on System.Exception. We were calling new LibGit2SharpException(m, c), but this was resolving to the string formatting constructor on LibGit2SharpException, because it does not have a constructor that takes a category. This runs a string format, so if the errorMessage contained any curly braces, that constructor would throw an System.FormatException. This has been changed to just use the message constructor (with no format arguments), and will drop the error code (which is always Unknown anyway). --- LibGit2Sharp/Core/Ensure.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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); From 1367830158e13d0c748ef5ab4c2850b6b401a1fa Mon Sep 17 00:00:00 2001 From: Dylan Lerch Date: Wed, 22 Sep 2021 15:17:49 +1000 Subject: [PATCH 03/13] Set the native libgit2 error message when an exception is thrown in the subtransport --- LibGit2Sharp/SmartSubtransportStream.cs | 4 ++++ 1 file changed, 4 insertions(+) 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; } From 65ca2c09f205b736e266598b31b8be08405910e8 Mon Sep 17 00:00:00 2001 From: RobE Date: Fri, 31 Oct 2025 16:38:15 +1000 Subject: [PATCH 04/13] Re-introduce .net6 while we still have Calamari running in .net6 --- .github/workflows/ci.yml | 5 ++++- .idea/.idea.LibGit2Sharp/.idea/.gitignore | 13 +++++++++++++ .idea/.idea.LibGit2Sharp/.idea/.name | 1 + .idea/.idea.LibGit2Sharp/.idea/encodings.xml | 4 ++++ .idea/.idea.LibGit2Sharp/.idea/indexLayout.xml | 8 ++++++++ .idea/.idea.LibGit2Sharp/.idea/vcs.xml | 6 ++++++ LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj | 2 +- LibGit2Sharp/LibGit2Sharp.csproj | 5 ++++- 8 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 .idea/.idea.LibGit2Sharp/.idea/.gitignore create mode 100644 .idea/.idea.LibGit2Sharp/.idea/.name create mode 100644 .idea/.idea.LibGit2Sharp/.idea/encodings.xml create mode 100644 .idea/.idea.LibGit2Sharp/.idea/indexLayout.xml create mode 100644 .idea/.idea.LibGit2Sharp/.idea/vcs.xml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 038eab0ec..5a83f6b58 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,7 +40,7 @@ jobs: matrix: arch: [ x64 ] os: [ windows-2022, macos-13 ] - tfm: [ net472, net8.0, net9.0 ] + tfm: [ net472, net6.0, net8.0, net9.0 ] exclude: - os: macos-13 tfm: net472 @@ -63,6 +63,7 @@ jobs: dotnet-version: | 9.0.x 8.0.x + 6.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: @@ -81,6 +82,8 @@ jobs: - distro: alpine.3.19 sdk: '9.0' include: + - sdk: '6.0' + tfm: net6.0 - sdk: '8.0' tfm: net8.0 - sdk: '9.0' 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..7b08163ce --- /dev/null +++ b/.idea/.idea.LibGit2Sharp/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ 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/LibGit2Sharp.Tests.csproj b/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj index fb81a76a3..e769ab3df 100644 --- a/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj +++ b/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj @@ -1,7 +1,7 @@  - net472;net8.0;net9.0 + net472;net6.0;net8.0;net9.0 diff --git a/LibGit2Sharp/LibGit2Sharp.csproj b/LibGit2Sharp/LibGit2Sharp.csproj index f3ae850d4..611798631 100644 --- a/LibGit2Sharp/LibGit2Sharp.csproj +++ b/LibGit2Sharp/LibGit2Sharp.csproj @@ -1,7 +1,7 @@  - net472;net8.0 + net472;net8.0;net6.0 12.0 true LibGit2Sharp brings all the might and speed of libgit2, a native Git implementation, to the managed world of .NET @@ -28,6 +28,9 @@ true + + true + From cf91cb21f5d9c8fc90c4bb479a81791ba76d3341 Mon Sep 17 00:00:00 2001 From: Steven Cleve <107827476+stevencl840@users.noreply.github.com> Date: Fri, 12 Dec 2025 13:44:10 +1100 Subject: [PATCH 05/13] Remove .net 6 --- .github/workflows/ci.yml | 5 +---- LibGit2Sharp/LibGit2Sharp.csproj | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5a83f6b58..038eab0ec 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,7 +40,7 @@ jobs: matrix: arch: [ x64 ] os: [ windows-2022, macos-13 ] - tfm: [ net472, net6.0, net8.0, net9.0 ] + tfm: [ net472, net8.0, net9.0 ] exclude: - os: macos-13 tfm: net472 @@ -63,7 +63,6 @@ jobs: dotnet-version: | 9.0.x 8.0.x - 6.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: @@ -82,8 +81,6 @@ jobs: - distro: alpine.3.19 sdk: '9.0' include: - - sdk: '6.0' - tfm: net6.0 - sdk: '8.0' tfm: net8.0 - sdk: '9.0' diff --git a/LibGit2Sharp/LibGit2Sharp.csproj b/LibGit2Sharp/LibGit2Sharp.csproj index 611798631..f3ae850d4 100644 --- a/LibGit2Sharp/LibGit2Sharp.csproj +++ b/LibGit2Sharp/LibGit2Sharp.csproj @@ -1,7 +1,7 @@  - net472;net8.0;net6.0 + net472;net8.0 12.0 true LibGit2Sharp brings all the might and speed of libgit2, a native Git implementation, to the managed world of .NET @@ -28,9 +28,6 @@ true - - true - From 8219c8ddaf5f41eea53295e95114dece75f25956 Mon Sep 17 00:00:00 2001 From: Steven Cleve <107827476+stevencl840@users.noreply.github.com> Date: Fri, 12 Dec 2025 13:57:36 +1100 Subject: [PATCH 06/13] Update to Macos-14 --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 038eab0ec..321c21385 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,10 +39,10 @@ jobs: strategy: matrix: arch: [ x64 ] - os: [ windows-2022, macos-13 ] + os: [ windows-2022, macos-14 ] tfm: [ net472, net8.0, net9.0 ] exclude: - - os: macos-13 + - os: macos-14 tfm: net472 include: - arch: arm64 From 3efac0b861009922a3d06b139b93f8363ce0c83d Mon Sep 17 00:00:00 2001 From: Steven Cleve <107827476+stevencl840@users.noreply.github.com> Date: Mon, 12 Jan 2026 07:15:36 +1100 Subject: [PATCH 07/13] WIP (#21) --- LibGit2Sharp/PushOptions.cs | 5 +++++ LibGit2Sharp/RemoteCallbacks.cs | 1 + 2 files changed, 6 insertions(+) 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..178a4077b 100644 --- a/LibGit2Sharp/RemoteCallbacks.cs +++ b/LibGit2Sharp/RemoteCallbacks.cs @@ -30,6 +30,7 @@ internal RemoteCallbacks(PushOptions pushOptions) CertificateCheck = pushOptions.CertificateCheck; PushStatusError = pushOptions.OnPushStatusError; PrePushCallback = pushOptions.OnNegotiationCompletedBeforePush; + Progress = pushOptions.OnPushRemoteProgress; } internal RemoteCallbacks(FetchOptionsBase fetchOptions) From 9b2bac1b402b067ce4f572e47f3f1ca56f75eb34 Mon Sep 17 00:00:00 2001 From: Eddy Moulton <8491021+eddymoulton@users.noreply.github.com> Date: Fri, 8 May 2026 14:15:47 +1000 Subject: [PATCH 08/13] Update libgit2 native binaries (#26) --- .github/workflows/ci.yml | 245 +++++++++--------- .gitignore | 1 + .../.idea.LibGit2Sharp/.idea/indexLayout.xml | 4 +- LibGit2Sharp.Tests/GlobalSettingsFixture.cs | 6 +- LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj | 2 +- LibGit2Sharp/Core/GitBlame.cs | 3 + LibGit2Sharp/Core/GitCertificateSsh.cs | 12 +- LibGit2Sharp/Core/GitCertificateSshType.cs | 1 + LibGit2Sharp/Core/GitCheckoutOpts.cs | 25 +- LibGit2Sharp/Core/GitConfigEntry.cs | 1 - LibGit2Sharp/Core/GitDiff.cs | 1 + LibGit2Sharp/Core/GitRebaseOptions.cs | 2 + LibGit2Sharp/Core/GitRemoteCallbacks.cs | 2 + LibGit2Sharp/Core/GitStatusOptions.cs | 2 + LibGit2Sharp/LibGit2Sharp.csproj | 2 +- nuget.config | 1 + 16 files changed, 179 insertions(+), 131 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 321c21385..64b8096db 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,120 +1,133 @@ 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-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@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" - - 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@v4 - 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" \ No newline at end of file + 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 suffix for branch builds + if: ${{ !startsWith(github.ref, 'refs/tags/') }} + id: version + run: | + # 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) + echo "override=${SAFE_BRANCH}.${{ github.run_number }}" >> "$GITHUB_OUTPUT" + - name: Build + run: dotnet build LibGit2Sharp.sln --configuration Release ${{ steps.version.outputs.override && format('/p:MinVerDefaultPreReleaseIdentifiers="{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/indexLayout.xml b/.idea/.idea.LibGit2Sharp/.idea/indexLayout.xml index 7b08163ce..323a9b706 100644 --- a/.idea/.idea.LibGit2Sharp/.idea/indexLayout.xml +++ b/.idea/.idea.LibGit2Sharp/.idea/indexLayout.xml @@ -1,7 +1,9 @@ - + + . + diff --git a/LibGit2Sharp.Tests/GlobalSettingsFixture.cs b/LibGit2Sharp.Tests/GlobalSettingsFixture.cs index 925efc3d0..b4d1f24ab 100644 --- a/LibGit2Sharp.Tests/GlobalSettingsFixture.cs +++ b/LibGit2Sharp.Tests/GlobalSettingsFixture.cs @@ -90,17 +90,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/LibGit2Sharp.Tests.csproj b/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj index e769ab3df..fb81a76a3 100644 --- a/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj +++ b/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj @@ -1,7 +1,7 @@  - net472;net6.0;net8.0;net9.0 + net472;net8.0;net9.0 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/LibGit2Sharp.csproj b/LibGit2Sharp/LibGit2Sharp.csproj index f3ae850d4..90d3eb193 100644 --- a/LibGit2Sharp/LibGit2Sharp.csproj +++ b/LibGit2Sharp/LibGit2Sharp.csproj @@ -30,7 +30,7 @@ - + diff --git a/nuget.config b/nuget.config index 35696f810..119fc702c 100644 --- a/nuget.config +++ b/nuget.config @@ -2,5 +2,6 @@ + From 3ee95e7c23cf565aa6c441e157ea24c3588ddc4a Mon Sep 17 00:00:00 2001 From: Eddy Moulton <8491021+eddymoulton@users.noreply.github.com> Date: Fri, 15 May 2026 15:23:05 +1000 Subject: [PATCH 09/13] Add SSH key support with libssh2 (#27) --- LibGit2Sharp.Tests/GlobalSettingsFixture.cs | 6 +- LibGit2Sharp.Tests/NetworkFixture.cs | 128 +++++++++++++++++++- LibGit2Sharp.Tests/PushFixture.cs | 12 ++ LibGit2Sharp.Tests/RepositoryFixture.cs | 24 ++++ LibGit2Sharp/CertificateSsh.cs | 29 +++++ LibGit2Sharp/Core/NativeMethods.cs | 51 +++++++- LibGit2Sharp/Core/SshExtensions.cs | 27 +++++ LibGit2Sharp/LibGit2Sharp.csproj | 2 +- LibGit2Sharp/ListRemoteOptions.cs | 26 ++++ LibGit2Sharp/Network.cs | 104 ++++++++++++++-- LibGit2Sharp/RemoteCallbacks.cs | 21 ++++ LibGit2Sharp/Repository.cs | 34 ++++-- LibGit2Sharp/SshKeyCredentials.cs | 51 ++++++++ LibGit2Sharp/SshKeyMemoryCredentials.cs | 51 ++++++++ LibGit2Sharp/SupportedCredentialTypes.cs | 10 ++ 15 files changed, 544 insertions(+), 32 deletions(-) create mode 100644 LibGit2Sharp/Core/SshExtensions.cs create mode 100644 LibGit2Sharp/ListRemoteOptions.cs create mode 100644 LibGit2Sharp/SshKeyCredentials.cs create mode 100644 LibGit2Sharp/SshKeyMemoryCredentials.cs diff --git a/LibGit2Sharp.Tests/GlobalSettingsFixture.cs b/LibGit2Sharp.Tests/GlobalSettingsFixture.cs index b4d1f24ab..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); 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/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 90d3eb193..26c99bd24 100644 --- a/LibGit2Sharp/LibGit2Sharp.csproj +++ b/LibGit2Sharp/LibGit2Sharp.csproj @@ -30,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/RemoteCallbacks.cs b/LibGit2Sharp/RemoteCallbacks.cs index 178a4077b..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) @@ -286,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/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), } } From ea58de2681413eb2e2be82cae1c4c1c25c23191a Mon Sep 17 00:00:00 2001 From: Eddy Moulton <8491021+eddymoulton@users.noreply.github.com> Date: Mon, 18 May 2026 10:24:20 +1000 Subject: [PATCH 10/13] Update readme for Octopus releases (#29) --- README.md | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) 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) From 0035b38e00a677682610b33097acffdbb38fa6d6 Mon Sep 17 00:00:00 2001 From: Eddy Moulton <8491021+eddymoulton@users.noreply.github.com> Date: Tue, 19 May 2026 17:01:11 +1000 Subject: [PATCH 11/13] Update native binaries to 2.0.323-octopus.4 (#31) --- LibGit2Sharp/LibGit2Sharp.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LibGit2Sharp/LibGit2Sharp.csproj b/LibGit2Sharp/LibGit2Sharp.csproj index 26c99bd24..2ad629cc2 100644 --- a/LibGit2Sharp/LibGit2Sharp.csproj +++ b/LibGit2Sharp/LibGit2Sharp.csproj @@ -30,7 +30,7 @@ - + From ba014294ab440465e6094a64d32c4a4495057733 Mon Sep 17 00:00:00 2001 From: Eddy Moulton <8491021+eddymoulton@users.noreply.github.com> Date: Fri, 5 Jun 2026 04:06:57 +0000 Subject: [PATCH 12/13] Include branch in branch build versions (#33) --- .github/workflows/ci.yml | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 64b8096db..8a197b428 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,16 +22,23 @@ jobs: uses: actions/setup-dotnet@v5 with: dotnet-version: 9.0.x - - name: Compute version suffix for branch builds + - 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) - echo "override=${SAFE_BRANCH}.${{ github.run_number }}" >> "$GITHUB_OUTPUT" + # 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:MinVerDefaultPreReleaseIdentifiers="{0}"', steps.version.outputs.override) || '' }} + 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: From c5bd8b2d25a4b0bdf89ccc376b3ed1aec95ae2ae Mon Sep 17 00:00:00 2001 From: Eddy Moulton <8491021+eddymoulton@users.noreply.github.com> Date: Fri, 5 Jun 2026 06:02:34 +0000 Subject: [PATCH 13/13] Include static windows binaries (#32) --- LibGit2Sharp/LibGit2Sharp.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LibGit2Sharp/LibGit2Sharp.csproj b/LibGit2Sharp/LibGit2Sharp.csproj index 2ad629cc2..f64ef4086 100644 --- a/LibGit2Sharp/LibGit2Sharp.csproj +++ b/LibGit2Sharp/LibGit2Sharp.csproj @@ -30,7 +30,7 @@ - +