From e75902e9916021c1337e06db5b90f9b9f2a0a7ad Mon Sep 17 00:00:00 2001 From: Glen Date: Tue, 2 Jun 2026 09:55:36 -0700 Subject: [PATCH 01/11] Update .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index e1fb4cd..f471f06 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ artifacts/ .vscode/ +.idea From 39495e41467393f650a40616837cfb134f15d7b4 Mon Sep 17 00:00:00 2001 From: Glen Date: Tue, 2 Jun 2026 10:36:25 -0700 Subject: [PATCH 02/11] modernize github action build pipeline --- .github/workflows/build.yml | 80 ++++++++++--------------------------- 1 file changed, 20 insertions(+), 60 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 21e70af..3a438a3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,17 +5,14 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 with: - ref: ${{ github.ref }} - - - name: Prepare repository - run: git checkout "${GITHUB_REF:11}" + fetch-depth: 0 - name: Install Go - uses: actions/setup-go@v1 + uses: actions/setup-go@v5 with: - go-version: 1.13.1 + go-version: 1.26.3 - name: test run: make test @@ -24,68 +21,31 @@ jobs: runs-on: ubuntu-latest needs: test if: github.ref == 'refs/heads/master' + permissions: + contents: write steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 with: - ref: ${{ github.ref }} - - - name: Prepare repository - run: git checkout "${GITHUB_REF:11}" + fetch-depth: 0 - - uses: syncromatics/gogitver-action@v0.0.2 + - uses: syncromatics/gogitver-action@v0.0.7 id: gogitver - - name: Create Release - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ steps.gogitver.outputs.version }} - release_name: Release ${{ steps.gogitver.outputs.version }} - draft: false - prerelease: false - - name: Install Go - uses: actions/setup-go@v1 + uses: actions/setup-go@v5 with: - go-version: 1.13.1 + go-version: 1.26.3 - name: build run: make package - - uses: actions/upload-release-asset@v1.0.1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./artifacts/linux.tar.gz - asset_name: linux.tar.gz - asset_content_type: application/gzip - - - uses: actions/upload-release-asset@v1.0.1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./artifacts/windows.zip - asset_name: windows.zip - asset_content_type: application/zip - - - uses: actions/upload-release-asset@v1.0.1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./artifacts/arm.tar.gz - asset_name: arm.tar.gz - asset_content_type: application/gzip - - - uses: actions/upload-release-asset@v1.0.1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Create Release + uses: softprops/action-gh-release@v2 with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./artifacts/darwin.tar.gz - asset_name: darwin.tar.gz - asset_content_type: application/gzip + tag_name: ${{ steps.gogitver.outputs.version }} + name: Release ${{ steps.gogitver.outputs.version }} + files: | + ./artifacts/linux.tar.gz + ./artifacts/windows.zip + ./artifacts/arm.tar.gz + ./artifacts/darwin.tar.gz From ad6e1f6a7ba8b958b675956833f689ad49118423 Mon Sep 17 00:00:00 2001 From: Glen Date: Tue, 2 Jun 2026 10:37:42 -0700 Subject: [PATCH 03/11] only lf line endings --- .gitattributes | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..6b04541 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +* text=auto eol=lf + +*.exe binary +*.zip binary +*.tar.gz binary +*.snap binary From ed96556b4b4809ba6510920173cdbef809a8d794 Mon Sep 17 00:00:00 2001 From: Glen Date: Tue, 2 Jun 2026 10:38:04 -0700 Subject: [PATCH 04/11] update go to 1.26 from 1.13 --- go.mod | 44 ++++++++++++---- go.sum | 158 +++++++++++++++++++++++++++++++++++++-------------------- 2 files changed, 136 insertions(+), 66 deletions(-) diff --git a/go.mod b/go.mod index 6839ee1..4ac20ae 100644 --- a/go.mod +++ b/go.mod @@ -1,15 +1,39 @@ module github.com/syncromatics/gogitver -go 1.13 +go 1.26.3 require ( - github.com/coreos/go-semver v0.2.0 - github.com/inconshreveable/mousetrap v1.0.0 // indirect - github.com/pkg/errors v0.8.0 - github.com/spf13/cobra v0.0.3 - github.com/spf13/pflag v1.0.1 // indirect - github.com/stretchr/testify v1.2.2 - gopkg.in/src-d/go-billy.v4 v4.2.1 - gopkg.in/src-d/go-git.v4 v4.7.1 - gopkg.in/yaml.v2 v2.2.1 + github.com/coreos/go-semver v0.3.1 + github.com/go-git/go-billy/v5 v5.9.0 + github.com/go-git/go-git/v5 v5.19.1 + github.com/pkg/errors v0.9.1 + github.com/spf13/cobra v1.10.2 + github.com/stretchr/testify v1.11.1 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + dario.cat/mergo v1.0.0 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/ProtonMail/go-crypto v1.1.6 // indirect + github.com/cloudflare/circl v1.6.3 // indirect + github.com/cyphar/filepath-securejoin v0.6.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/emirpasic/gods v1.18.1 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/kevinburke/ssh_config v1.2.0 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/pjbgf/sha1cd v0.6.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect + github.com/skeema/knownhosts v1.3.1 // indirect + github.com/spf13/pflag v1.0.9 // indirect + github.com/xanzy/ssh-agent v0.3.3 // indirect + golang.org/x/crypto v0.50.0 // indirect + golang.org/x/net v0.53.0 // indirect + golang.org/x/sys v0.43.0 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect ) diff --git a/go.sum b/go.sum index 98ee0d9..319d26f 100644 --- a/go.sum +++ b/go.sum @@ -1,69 +1,115 @@ -github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs= -github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= -github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= -github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= -github.com/coreos/go-semver v0.2.0 h1:3Jm3tLmsgAYcjC+4Up7hJrFBPr+n7rAqYeSw/SZazuY= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= +github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= +github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= +github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= +github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE= +github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/emirpasic/gods v1.9.0 h1:rUF4PuzEjMChMiNsVjdI+SyLu7rEqpQ5reNFnhC7oFo= -github.com/emirpasic/gods v1.9.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= -github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ= -github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= -github.com/gliderlabs/ssh v0.1.1 h1:j3L6gSLQalDETeEg/Jg0mGY0/y/N6zI2xX1978P0Uqw= -github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= -github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= +github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= +github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.9.0 h1:jItGXszUDRtR/AlferWPTMN4j38BQ88XnXKbilmmBPA= +github.com/go-git/go-billy/v5 v5.9.0/go.mod h1:jCnQMLj9eUgGU7+ludSTYoZL/GGmii14RxKFj7ROgHw= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= +github.com/go-git/go-git/v5 v5.19.1 h1:nX27AnaU43/K5bKktKwgBmR9lawoYVe1Ckg0rgzzN00= +github.com/go-git/go-git/v5 v5.19.1/go.mod h1:Pb1v0c7/g8aGQJwx9Us09W85yGoyvSwuhEGMH7zjDKQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e h1:RgQk53JHp/Cjunrr1WlsXSZpqXn+uREuHvUVcK82CV8= -github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0= -github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/pelletier/go-buffruneio v0.2.0 h1:U4t4R6YkofJ5xHm3dJzuRpPZ0mr5MMCoAWooScCR7aA= -github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= -github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= +github.com/pjbgf/sha1cd v0.6.0 h1:3WJ8Wz8gvDz29quX1OcEmkAlUg9diU4GxJHqs0/XiwU= +github.com/pjbgf/sha1cd v0.6.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= -github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= -github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/pflag v1.0.1 h1:aCvUg6QPl3ibpQUxyLkrEkCHtPqYJL4x9AuhqVqFis4= -github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/src-d/gcfg v1.3.0 h1:2BEDr8r0I0b8h/fOqwtxCEiq2HJu8n2JGZJQFGXWLjg= -github.com/src-d/gcfg v1.3.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI= -github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= +github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= +github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/xanzy/ssh-agent v0.2.0 h1:Adglfbi5p9Z0BmK2oKU9nTG+zKfniSfnaMYB+ULd+Ro= -github.com/xanzy/ssh-agent v0.2.0/go.mod h1:0NyE30eGUDliuLEHJgYte/zncp2zdTStcOnWhgSqHD8= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 h1:u+LnwYTOOW7Ukr/fppxEb1Nwz0AtPflrblfvUudpo+I= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/sys v0.0.0-20180903190138-2b024373dcd9 h1:lkiLiLBHGoH3XnqSLUIaBsilGMUjI+Uy2Xu2JLUtTas= -golang.org/x/sys v0.0.0-20180903190138-2b024373dcd9/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= +golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= +golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM= +golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= +golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= +golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= +golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= +golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/src-d/go-billy.v4 v4.2.1 h1:omN5CrMrMcQ+4I8bJ0wEhOBPanIRWzFC953IiXKdYzo= -gopkg.in/src-d/go-billy.v4 v4.2.1/go.mod h1:tm33zBoOwxjYHZIE+OV8bxTWFMJLrconzFMd38aARFk= -gopkg.in/src-d/go-git-fixtures.v3 v3.1.1 h1:XWW/s5W18RaJpmo1l0IYGqXKuJITWRFuA45iOf1dKJs= -gopkg.in/src-d/go-git-fixtures.v3 v3.1.1/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g= -gopkg.in/src-d/go-git.v4 v4.7.1 h1:phAV/kNULxfYEvyInGdPuq3U2MtPpJdgmtOUF3cghkQ= -gopkg.in/src-d/go-git.v4 v4.7.1/go.mod h1:xrJH/YX8uSWewT6evfocf8qsivF18JgCN7/IMitOptY= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= -gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From cd539edf237609e9ee233a43732bec010cfdb8ed Mon Sep 17 00:00:00 2001 From: Glen Date: Tue, 2 Jun 2026 10:40:17 -0700 Subject: [PATCH 05/11] switch to primary branch language, support main as default --- cmd/gogitver/cmd/root.go | 34 ++++++++++----- pkg/git/branchWalker.go | 16 +++---- pkg/git/git.go | 90 ++++++++++++++++++++++++++++++---------- 3 files changed, 100 insertions(+), 40 deletions(-) diff --git a/cmd/gogitver/cmd/root.go b/cmd/gogitver/cmd/root.go index 4e81126..66dda9e 100644 --- a/cmd/gogitver/cmd/root.go +++ b/cmd/gogitver/cmd/root.go @@ -6,11 +6,11 @@ import ( "os" "strconv" - "github.com/syncromatics/gogitver/pkg/git" "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/syncromatics/gogitver/pkg/git" - gogit "gopkg.in/src-d/go-git.v4" + gogit "github.com/go-git/go-git/v5" ) var rootCmd = &cobra.Command{ @@ -32,11 +32,13 @@ func init() { for _, cmd := range cmds { cmd.Flags().String("path", ".", "the path to the git repository") cmd.Flags().String("settings", "./.gogitver.yaml", "the file that contains the settings") + cmd.Flags().String("primary-branch", "", "the primary branch used as the stable version baseline") cmd.Flags().Bool("trim-branch-prefix", false, "Trim branch prefixes feature/ and hotfix/ from prerelease label") cmd.Flags().BoolP("verbose", "v", false, "Show information about how the version was calculated") } - rootCmd.Flags().Bool("forbid-behind-master", false, "error if the current branch's calculated version is behind the calculated version of refs/heads/master") + rootCmd.Flags().Bool("forbid-behind-primary", false, "error if the current branch's calculated version is behind the calculated version of the primary branch") + rootCmd.Flags().Bool("forbid-behind-master", false, "deprecated alias for --forbid-behind-primary") rootCmd.AddCommand(prereleaseCmd) } @@ -78,7 +80,12 @@ func getRepoAndSettings(cmd *cobra.Command) (*gogit.Repository, *git.Settings) { } func getBoolFromFlag(cmd *cobra.Command, flagName string) bool { - result, err := strconv.ParseBool(cmd.Flag(flagName).Value.String()) + flag := cmd.Flag(flagName) + if flag == nil { + return false + } + + result, err := strconv.ParseBool(flag.Value.String()) if err != nil { result = false } @@ -87,10 +94,17 @@ func getBoolFromFlag(cmd *cobra.Command, flagName string) bool { func getBranchSettings(cmd *cobra.Command) *git.BranchSettings { fbm := getBoolFromFlag(cmd, "forbid-behind-master") + fbp := getBoolFromFlag(cmd, "forbid-behind-primary") trimPrefix := getBoolFromFlag(cmd, "trim-branch-prefix") + primaryBranch := "" + if flag := cmd.Flag("primary-branch"); flag != nil { + primaryBranch = flag.Value.String() + } return &git.BranchSettings{ - ForbidBehindMaster: fbm, - TrimBranchPrefix: trimPrefix, + ForbidBehindMaster: fbm, + ForbidBehindPrimary: fbp, + TrimBranchPrefix: trimPrefix, + PrimaryBranch: primaryBranch, } } @@ -113,17 +127,15 @@ func runRoot(cmd *cobra.Command, args []string) { func runPrerelease(cmd *cobra.Command, args []string) { r, s := getRepoAndSettings(cmd) - trimPrefix := getBoolFromFlag(cmd, "trim-branch-prefix") - branchSettings := &git.BranchSettings{ - TrimBranchPrefix: trimPrefix, - } + branchSettings := getBranchSettings(cmd) label, err := git.GetPrereleaseLabel(r, s, branchSettings) if err != nil { panic(err) } - if label == "master" { + primaryBranch, err := git.GetPrimaryBranchName(r, branchSettings) + if err == nil && label == primaryBranch { label = "" } diff --git a/pkg/git/branchWalker.go b/pkg/git/branchWalker.go index 3e78d4f..78ca89b 100644 --- a/pkg/git/branchWalker.go +++ b/pkg/git/branchWalker.go @@ -5,12 +5,12 @@ import ( "regexp" "strings" - "gopkg.in/src-d/go-git.v4/plumbing" + "github.com/go-git/go-git/v5/plumbing" "github.com/coreos/go-semver/semver" + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing/object" "github.com/pkg/errors" - "gopkg.in/src-d/go-git.v4" - "gopkg.in/src-d/go-git.v4/plumbing/object" ) type branchWalker struct { @@ -18,7 +18,7 @@ type branchWalker struct { head *object.Commit tagMap map[string]string settings *Settings - isMaster bool + isPrimary bool endHash string verbose bool @@ -30,13 +30,13 @@ type versionHolder struct { versionMap []*gitVersion } -func newBranchWalker(repository *git.Repository, head *object.Commit, tagMap map[string]string, settings *Settings, isMaster bool, endHash string, verbose bool) *branchWalker { +func newBranchWalker(repository *git.Repository, head *object.Commit, tagMap map[string]string, settings *Settings, isPrimary bool, endHash string, verbose bool) *branchWalker { return &branchWalker{ repository: repository, head: head, settings: settings, tagMap: tagMap, - isMaster: isMaster, + isPrimary: isPrimary, endHash: endHash, visited: make(map[string]bool), commitsToReconcile: make(map[string]*gitVersion), @@ -80,7 +80,7 @@ func (b *branchWalker) GetVersion() (*semver.Version, error) { baseVersion.BumpMinor() case v.PatchBump: baseVersion.BumpPatch() - default: // every commit in master has at least a patch bump + default: // every commit in the primary branch has at least a patch bump baseVersion.BumpPatch() } if b.verbose { @@ -101,7 +101,7 @@ func (b *branchWalker) GetVersionMap() ([]*gitVersion, error) { return nil, err } - if b.isMaster { + if b.isPrimary { for hash, version := range b.commitsToReconcile { err = b.reconcileCommit(hash, version) if err != nil { diff --git a/pkg/git/git.go b/pkg/git/git.go index 628f6e7..c7167ba 100644 --- a/pkg/git/git.go +++ b/pkg/git/git.go @@ -8,17 +8,19 @@ import ( "strings" "github.com/coreos/go-semver/semver" + git "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/object" "github.com/pkg/errors" - git "gopkg.in/src-d/go-git.v4" - "gopkg.in/src-d/go-git.v4/plumbing" - "gopkg.in/src-d/go-git.v4/plumbing/object" ) // BranchSettings contains flags that determine how branches are handled when calculating versions. type BranchSettings struct { - ForbidBehindMaster bool - TrimBranchPrefix bool - IgnoreEnvVars bool + ForbidBehindMaster bool + ForbidBehindPrimary bool + TrimBranchPrefix bool + IgnoreEnvVars bool + PrimaryBranch string } type gitVersion struct { @@ -106,6 +108,12 @@ func GetPrereleaseLabel(r *git.Repository, settings *Settings, branchSettings *B return getCurrentBranch(r, h, branchSettings) } +// GetPrimaryBranchName returns the branch name used as the stable version baseline. +func GetPrimaryBranchName(r *git.Repository, branchSettings *BranchSettings) (string, error) { + _, name, err := getPrimaryBranchReference(r, branchSettings) + return name, err +} + func getVersion(r *git.Repository, h *plumbing.Reference, tagMap map[string]string, branchSettings *BranchSettings, settings *Settings, verbose bool) (version *semver.Version, err error) { currentBranch, err := getCurrentBranch(r, h, branchSettings) if err != nil { @@ -115,27 +123,27 @@ func getVersion(r *git.Repository, h *plumbing.Reference, tagMap map[string]stri log.Printf("Current branch is %s", currentBranch) } - masterHead, err := r.Reference("refs/heads/master", false) + primaryHead, primaryBranch, err := getPrimaryBranchReference(r, branchSettings) if err != nil { - masterHead, err = r.Reference("refs/remotes/origin/master", false) // TODO: This needs test coverage - if err != nil { - return nil, errors.Wrap(err, "failed to get master branch at 'refs/heads/master, 'refs/remotes/origin/master'") - } + return nil, err + } + if verbose { + log.Printf("Primary branch is %s", primaryBranch) } - masterCommit, err := r.CommitObject(masterHead.Hash()) + primaryCommit, err := r.CommitObject(primaryHead.Hash()) if err != nil { - return nil, errors.Wrap(err, "failed to get master commit from reference") + return nil, errors.Wrap(err, "failed to get primary branch commit from reference") } - masterWalker := newBranchWalker(r, masterCommit, tagMap, settings, true, "", verbose) - masterVersion, err := masterWalker.GetVersion() + primaryWalker := newBranchWalker(r, primaryCommit, tagMap, settings, true, "", verbose) + primaryVersion, err := primaryWalker.GetVersion() if err != nil { return nil, err } - if h.Hash() == masterHead.Hash() { - return masterVersion, nil + if h.Hash() == primaryHead.Hash() { + return primaryVersion, nil } c, err := r.CommitObject(h.Hash()) @@ -143,7 +151,7 @@ func getVersion(r *git.Repository, h *plumbing.Reference, tagMap map[string]stri return nil, errors.Wrap(err, "getVersion failed") } - walker := newBranchWalker(r, c, tagMap, settings, false, masterHead.Hash().String(), verbose) + walker := newBranchWalker(r, c, tagMap, settings, false, primaryHead.Hash().String(), verbose) versionMap, err := walker.GetVersionMap() if err != nil { return nil, err @@ -159,7 +167,7 @@ func getVersion(r *git.Repository, h *plumbing.Reference, tagMap map[string]stri baseVersion = versionMap[index].Name index-- } else { - baseVersion = masterVersion + baseVersion = primaryVersion } if index < 0 { @@ -182,13 +190,53 @@ func getVersion(r *git.Repository, h *plumbing.Reference, tagMap map[string]stri prerelease := fmt.Sprintf("%s-%d-%s", currentBranch, len(versionMap)-1, shortHash) baseVersion.PreRelease = semver.PreRelease(prerelease) - if branchSettings.ForbidBehindMaster && baseVersion.LessThan(*masterVersion) { - return nil, errors.Errorf("Branch has calculated version '%s' whose version is less than master '%s'", baseVersion, masterVersion) + if (branchSettings.ForbidBehindPrimary || branchSettings.ForbidBehindMaster) && baseVersion.LessThan(*primaryVersion) { + return nil, errors.Errorf("Branch has calculated version '%s' whose version is less than primary branch '%s'", baseVersion, primaryVersion) } return baseVersion, nil } +func getPrimaryBranchReference(r *git.Repository, branchSettings *BranchSettings) (*plumbing.Reference, string, error) { + if branchSettings.PrimaryBranch != "" { + ref, err := getBranchReference(r, branchSettings.PrimaryBranch) + if err != nil { + return nil, "", errors.Wrapf(err, "failed to get primary branch '%s'", branchSettings.PrimaryBranch) + } + return ref, branchSettings.PrimaryBranch, nil + } + + for _, name := range []string{"main", "master"} { + ref, err := r.Reference(plumbing.NewBranchReferenceName(name), false) + if err == nil { + return ref, name, nil + } + } + + for _, name := range []string{"main", "master"} { + ref, err := r.Reference(plumbing.NewRemoteReferenceName("origin", name), false) + if err == nil { + return ref, name, nil + } + } + + return nil, "", errors.New("failed to get primary branch at refs/heads/main, refs/heads/master, refs/remotes/origin/main, or refs/remotes/origin/master") +} + +func getBranchReference(r *git.Repository, branchName string) (*plumbing.Reference, error) { + ref, err := r.Reference(plumbing.NewBranchReferenceName(branchName), false) + if err == nil { + return ref, nil + } + + ref, err = r.Reference(plumbing.NewRemoteReferenceName("origin", branchName), false) + if err == nil { + return ref, nil + } + + return nil, err +} + func getCurrentBranch(r *git.Repository, h *plumbing.Reference, branchSettings *BranchSettings) (name string, err error) { branchName := "" From 88a4aada286657e80d84edd4d6a9dfb3f9693dbe Mon Sep 17 00:00:00 2001 From: Glen Date: Tue, 2 Jun 2026 10:43:51 -0700 Subject: [PATCH 06/11] force primary flag if main and master both exist --- pkg/git/git.go | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/pkg/git/git.go b/pkg/git/git.go index c7167ba..f6e6fcd 100644 --- a/pkg/git/git.go +++ b/pkg/git/git.go @@ -206,23 +206,38 @@ func getPrimaryBranchReference(r *git.Repository, branchSettings *BranchSettings return ref, branchSettings.PrimaryBranch, nil } - for _, name := range []string{"main", "master"} { - ref, err := r.Reference(plumbing.NewBranchReferenceName(name), false) - if err == nil { - return ref, name, nil - } + mainRef, hasMain := getOptionalReference(r, plumbing.NewBranchReferenceName("main")) + masterRef, hasMaster := getOptionalReference(r, plumbing.NewBranchReferenceName("master")) + if hasMain && hasMaster { + return nil, "", errors.New("found both refs/heads/main and refs/heads/master; specify --primary-branch to choose the version baseline") + } + if hasMain { + return mainRef, "main", nil + } + if hasMaster { + return masterRef, "master", nil } - for _, name := range []string{"main", "master"} { - ref, err := r.Reference(plumbing.NewRemoteReferenceName("origin", name), false) - if err == nil { - return ref, name, nil - } + originMainRef, hasOriginMain := getOptionalReference(r, plumbing.NewRemoteReferenceName("origin", "main")) + originMasterRef, hasOriginMaster := getOptionalReference(r, plumbing.NewRemoteReferenceName("origin", "master")) + if hasOriginMain && hasOriginMaster { + return nil, "", errors.New("found both refs/remotes/origin/main and refs/remotes/origin/master; specify --primary-branch to choose the version baseline") + } + if hasOriginMain { + return originMainRef, "main", nil + } + if hasOriginMaster { + return originMasterRef, "master", nil } return nil, "", errors.New("failed to get primary branch at refs/heads/main, refs/heads/master, refs/remotes/origin/main, or refs/remotes/origin/master") } +func getOptionalReference(r *git.Repository, referenceName plumbing.ReferenceName) (*plumbing.Reference, bool) { + ref, err := r.Reference(referenceName, false) + return ref, err == nil +} + func getBranchReference(r *git.Repository, branchName string) (*plumbing.Reference, error) { ref, err := r.Reference(plumbing.NewBranchReferenceName(branchName), false) if err == nil { From 06f7650fd2ae6c7a630aea02805b25f48caa4609 Mon Sep 17 00:00:00 2001 From: Glen Date: Tue, 2 Jun 2026 10:47:50 -0700 Subject: [PATCH 07/11] clean up tests, add new tests for main --- pkg/git/git_test.go | 360 +++++++++++++++++++++++++++++---------- pkg/git/settings.go | 5 +- pkg/git/settings_test.go | 8 +- 3 files changed, 272 insertions(+), 101 deletions(-) diff --git a/pkg/git/git_test.go b/pkg/git/git_test.go index bfb41ad..d3db4ac 100644 --- a/pkg/git/git_test.go +++ b/pkg/git/git_test.go @@ -2,18 +2,18 @@ package git_test import ( "fmt" - "os" "testing" "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" - "gopkg.in/src-d/go-billy.v4/memfs" - "gopkg.in/src-d/go-billy.v4/util" - git "gopkg.in/src-d/go-git.v4" - "gopkg.in/src-d/go-git.v4/plumbing" - "gopkg.in/src-d/go-git.v4/plumbing/object" - "gopkg.in/src-d/go-git.v4/storage/memory" + "github.com/go-git/go-billy/v5/memfs" + "github.com/go-git/go-billy/v5/util" + git "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/object" + "github.com/go-git/go-git/v5/storage/memory" igit "github.com/syncromatics/gogitver/pkg/git" ) @@ -39,12 +39,174 @@ func Test_ShouldCalculateVersionFromCommitsInMaster(t *testing.T) { // Act version, err := igit.GetCurrentVersion(repository, settings, branchSettings, false) - assert.Nil(t, err) + require.NoError(t, err) // Assert assert.Equal(t, "2.2.3", version) } +func Test_ShouldCalculateVersionFromCommitsInMain(t *testing.T) { + // Arrange + repository, worktree := initRepository(t) + + commitMultiple(t, worktree, "Initial commit") + + err := worktree.Checkout(&git.CheckoutOptions{ + Create: true, + Branch: plumbing.ReferenceName("refs/heads/main"), + }) + require.NoError(t, err) + + err = repository.Storer.RemoveReference(plumbing.ReferenceName("refs/heads/master")) + require.NoError(t, err) + + commitMultiple(t, worktree, + "(+semver: breaking) This is a major commit\n", + "(+semver: minor) This is a minor commit\n", + "(+semver: patch) This is a patch commit\n", + ) + + settings := igit.GetDefaultSettings() + branchSettings := &igit.BranchSettings{ + IgnoreEnvVars: true, + } + + // Act + version, err := igit.GetCurrentVersion(repository, settings, branchSettings, false) + require.NoError(t, err) + + // Assert + assert.Equal(t, "1.1.1", version) +} + +func Test_ShouldFailWhenMainAndMasterBothExistWithoutExplicitPrimaryBranch(t *testing.T) { + // Arrange + repository, worktree := initRepository(t) + + commitMultiple(t, worktree, "Initial commit") + + err := worktree.Checkout(&git.CheckoutOptions{ + Create: true, + Branch: plumbing.ReferenceName("refs/heads/main"), + }) + require.NoError(t, err) + + commitMultiple(t, worktree, "(+semver: minor) main branch commit\n") + + settings := igit.GetDefaultSettings() + branchSettings := &igit.BranchSettings{ + IgnoreEnvVars: true, + } + + // Act + _, err = igit.GetCurrentVersion(repository, settings, branchSettings, false) + + // Assert + require.ErrorContains(t, err, "found both refs/heads/main and refs/heads/master") +} + +func Test_ShouldUseExplicitPrimaryBranch(t *testing.T) { + // Arrange + repository, worktree := initRepository(t) + + commitMultiple(t, worktree, "Initial commit") + + err := worktree.Checkout(&git.CheckoutOptions{ + Create: true, + Branch: plumbing.ReferenceName("refs/heads/main"), + }) + require.NoError(t, err) + + commitMultiple(t, worktree, "(+semver: major) main branch commit\n") + + err = worktree.Checkout(&git.CheckoutOptions{ + Branch: plumbing.ReferenceName("refs/heads/master"), + }) + require.NoError(t, err) + + settings := igit.GetDefaultSettings() + branchSettings := &igit.BranchSettings{ + IgnoreEnvVars: true, + PrimaryBranch: "master", + } + + // Act + version, err := igit.GetCurrentVersion(repository, settings, branchSettings, false) + require.NoError(t, err) + + // Assert + assert.Equal(t, "0.0.1", version) +} + +func Test_ShouldUseOriginMainWhenLocalPrimaryBranchIsMissing(t *testing.T) { + // Arrange + repository, worktree := initRepository(t) + + primaryHash := commitMultiple(t, worktree, "Initial commit") + + err := repository.Storer.SetReference(plumbing.NewHashReference(plumbing.ReferenceName("refs/remotes/origin/main"), primaryHash)) + require.NoError(t, err) + + err = worktree.Checkout(&git.CheckoutOptions{ + Create: true, + Branch: plumbing.ReferenceName("refs/heads/a-branch"), + }) + require.NoError(t, err) + + hash := commitMultiple(t, worktree, "(+semver: patch) branch commit\n") + + err = repository.Storer.RemoveReference(plumbing.ReferenceName("refs/heads/master")) + require.NoError(t, err) + + settings := igit.GetDefaultSettings() + branchSettings := &igit.BranchSettings{ + IgnoreEnvVars: true, + } + + // Act + version, err := igit.GetCurrentVersion(repository, settings, branchSettings, false) + require.NoError(t, err) + + // Assert + shortHash := hash.String()[0:4] + expected := fmt.Sprintf("0.0.2-a-branch-0-%s", shortHash) + assert.Equal(t, expected, version) +} + +func Test_ShouldFailWhenOriginMainAndOriginMasterBothExistWithoutExplicitPrimaryBranch(t *testing.T) { + // Arrange + repository, worktree := initRepository(t) + + primaryHash := commitMultiple(t, worktree, "Initial commit") + + err := repository.Storer.SetReference(plumbing.NewHashReference(plumbing.ReferenceName("refs/remotes/origin/main"), primaryHash)) + require.NoError(t, err) + err = repository.Storer.SetReference(plumbing.NewHashReference(plumbing.ReferenceName("refs/remotes/origin/master"), primaryHash)) + require.NoError(t, err) + + err = worktree.Checkout(&git.CheckoutOptions{ + Create: true, + Branch: plumbing.ReferenceName("refs/heads/a-branch"), + }) + require.NoError(t, err) + + commitMultiple(t, worktree, "(+semver: patch) branch commit\n") + + err = repository.Storer.RemoveReference(plumbing.ReferenceName("refs/heads/master")) + require.NoError(t, err) + + settings := igit.GetDefaultSettings() + branchSettings := &igit.BranchSettings{ + IgnoreEnvVars: true, + } + + // Act + _, err = igit.GetCurrentVersion(repository, settings, branchSettings, false) + + // Assert + require.ErrorContains(t, err, "found both refs/remotes/origin/main and refs/remotes/origin/master") +} + func Test_ShouldCalculateVersionFromCommitsInBranch(t *testing.T) { // Arrange repository, worktree := initRepository(t) @@ -55,7 +217,7 @@ func Test_ShouldCalculateVersionFromCommitsInBranch(t *testing.T) { Create: true, Branch: plumbing.ReferenceName("refs/heads/a-branch"), }) - assert.Nil(t, err) + require.NoError(t, err) hash := commitMultiple(t, worktree, "(+semver: major)\n", @@ -71,7 +233,7 @@ func Test_ShouldCalculateVersionFromCommitsInBranch(t *testing.T) { // Act version, err := igit.GetCurrentVersion(repository, settings, branchSettings, false) - assert.Nil(t, err) + require.NoError(t, err) // Assert shortHash := hash.String()[0:4] @@ -89,7 +251,7 @@ func Test_ShouldCalculateVersionFromCommitsInMasterWithMergeCommits(t *testing.T Create: true, Branch: plumbing.ReferenceName("refs/heads/a-branch"), }) - assert.Nil(t, err) + require.NoError(t, err) branchHash := commitMultiple(t, worktree, "(+semver: major)\n", @@ -101,22 +263,16 @@ func Test_ShouldCalculateVersionFromCommitsInMasterWithMergeCommits(t *testing.T err = worktree.Checkout(&git.CheckoutOptions{ Branch: plumbing.ReferenceName("refs/heads/master"), }) - assert.Nil(t, err) - - masterHash, err = worktree.Commit("merged a-branch\n", &git.CommitOptions{ - Author: defaultSignature(), - Parents: []plumbing.Hash{ - masterHash, - branchHash, - }, - }) - assert.Nil(t, err) + require.NoError(t, err) + + masterHash, err = commitMerge(t, worktree, "merged a-branch\n", masterHash, branchHash) + require.NoError(t, err) err = worktree.Checkout(&git.CheckoutOptions{ Create: true, Branch: plumbing.ReferenceName("refs/heads/another-branch"), }) - assert.Nil(t, err) + require.NoError(t, err) branchHash = commitMultiple(t, worktree, "(+semver: minor)\n", @@ -126,22 +282,16 @@ func Test_ShouldCalculateVersionFromCommitsInMasterWithMergeCommits(t *testing.T err = worktree.Checkout(&git.CheckoutOptions{ Branch: plumbing.ReferenceName("refs/heads/master"), }) - assert.Nil(t, err) - - masterHash, err = worktree.Commit("merged another-branch\n", &git.CommitOptions{ - Author: defaultSignature(), - Parents: []plumbing.Hash{ - masterHash, - branchHash, - }, - }) - assert.Nil(t, err) + require.NoError(t, err) + + masterHash, err = commitMerge(t, worktree, "merged another-branch\n", masterHash, branchHash) + require.NoError(t, err) err = worktree.Checkout(&git.CheckoutOptions{ Create: true, Branch: plumbing.ReferenceName("refs/heads/yet-another-branch"), }) - assert.Nil(t, err) + require.NoError(t, err) branchHash = commitMultiple(t, worktree, "(+semver: patch)\n", @@ -152,16 +302,10 @@ func Test_ShouldCalculateVersionFromCommitsInMasterWithMergeCommits(t *testing.T err = worktree.Checkout(&git.CheckoutOptions{ Branch: plumbing.ReferenceName("refs/heads/master"), }) - assert.Nil(t, err) - - _, err = worktree.Commit("merged yet-another-branch\n", &git.CommitOptions{ - Author: defaultSignature(), - Parents: []plumbing.Hash{ - masterHash, - branchHash, - }, - }) - assert.Nil(t, err) + require.NoError(t, err) + + _, err = commitMerge(t, worktree, "merged yet-another-branch\n", masterHash, branchHash) + require.NoError(t, err) settings := igit.GetDefaultSettings() branchSettings := &igit.BranchSettings{ @@ -170,7 +314,7 @@ func Test_ShouldCalculateVersionFromCommitsInMasterWithMergeCommits(t *testing.T // Act version, err := igit.GetCurrentVersion(repository, settings, branchSettings, false) - assert.Nil(t, err) + require.NoError(t, err) // Assert assert.Equal(t, "1.1.1", version) @@ -187,7 +331,7 @@ func Test_ShouldCalculateVersionFromLightweightTag(t *testing.T) { ref := plumbing.NewHashReference(plumbing.ReferenceName("refs/tags/v1.2.3"), hash) err := repository.Storer.SetReference(ref) - assert.Nil(t, err) + require.NoError(t, err) commitMultiple(t, worktree, "(+semver: minor)\n", @@ -201,7 +345,7 @@ func Test_ShouldCalculateVersionFromLightweightTag(t *testing.T) { // Act version, err := igit.GetCurrentVersion(repository, settings, branchSettings, false) - assert.Nil(t, err) + require.NoError(t, err) // Assert assert.Equal(t, "1.3.1", version) @@ -215,7 +359,7 @@ func Test_ShouldFailToCalculateVersionFromImproperlyNamedLightweightTag(t *testi ref := plumbing.NewHashReference(plumbing.ReferenceName("refs/tags/an-arbitrary-tag-name"), hash) err := repository.Storer.SetReference(ref) - assert.Nil(t, err) + require.NoError(t, err) settings := igit.GetDefaultSettings() branchSettings := &igit.BranchSettings{ @@ -226,7 +370,7 @@ func Test_ShouldFailToCalculateVersionFromImproperlyNamedLightweightTag(t *testi _, err = igit.GetCurrentVersion(repository, settings, branchSettings, false) // Assert - assert.NotNil(t, err) + require.Error(t, err) } func Test_ShouldCalculateVersionFromAnnotatedTag(t *testing.T) { @@ -247,14 +391,14 @@ func Test_ShouldCalculateVersionFromAnnotatedTag(t *testing.T) { } tagObj := repository.Storer.NewEncodedObject() err = tag.Encode(tagObj) - assert.Nil(t, err) + require.NoError(t, err) target, err := repository.Storer.SetEncodedObject(tagObj) - assert.Nil(t, err) + require.NoError(t, err) ref := plumbing.NewHashReference(plumbing.ReferenceName("refs/tags/5.6.7"), target) err = repository.Storer.SetReference(ref) - assert.Nil(t, err) + require.NoError(t, err) commitMultiple(t, worktree, "(+semver: minor)\n", @@ -268,7 +412,7 @@ func Test_ShouldCalculateVersionFromAnnotatedTag(t *testing.T) { // Act version, err := igit.GetCurrentVersion(repository, settings, branchSettings, false) - assert.Nil(t, err) + require.NoError(t, err) // Assert assert.Equal(t, "5.7.1", version) @@ -282,40 +426,67 @@ func Test_ShouldCalculateVersionFromTravisTag(t *testing.T) { settings := igit.GetDefaultSettings() branchSettings := &igit.BranchSettings{} - os.Setenv("TRAVIS_TAG", "v1.2.3") + t.Setenv("TRAVIS_TAG", "v1.2.3") // Act version, err := igit.GetCurrentVersion(repository, settings, branchSettings, false) - assert.Nil(t, err) + require.NoError(t, err) // Assert assert.Equal(t, "1.2.3", version) } func initRepository(t *testing.T) (*git.Repository, *git.Worktree) { + t.Helper() + fs := memfs.New() storage := memory.NewStorage() repository, err := git.Init(storage, fs) - assert.Nil(t, err) + require.NoError(t, err) worktree, err := repository.Worktree() - assert.Nil(t, err) + require.NoError(t, err) return repository, worktree } func commitMultiple(t *testing.T, worktree *git.Worktree, messages ...string) plumbing.Hash { + t.Helper() + var hash plumbing.Hash var err error - for _, msg := range messages { + for i, msg := range messages { + fileName := fmt.Sprintf("commit-%d-%d.txt", time.Now().UnixNano(), i) + err = util.WriteFile(worktree.Filesystem, fileName, []byte(msg), 0644) + require.NoError(t, err) + + _, err = worktree.Add(fileName) + require.NoError(t, err) + hash, err = worktree.Commit(msg, &git.CommitOptions{Author: defaultSignature()}) - assert.Nil(t, err) + require.NoError(t, err) } return hash } +func commitMerge(t *testing.T, worktree *git.Worktree, message string, parents ...plumbing.Hash) (plumbing.Hash, error) { + t.Helper() + + fileName := fmt.Sprintf("merge-%d.txt", time.Now().UnixNano()) + err := util.WriteFile(worktree.Filesystem, fileName, []byte(message), 0644) + require.NoError(t, err) + + _, err = worktree.Add(fileName) + require.NoError(t, err) + + return worktree.Commit(message, &git.CommitOptions{ + Author: defaultSignature(), + Parents: parents, + }) +} + func TestTrimBranchPrefix(t *testing.T) { r := getSingleBranchCommit("feature/should-be-trimmed", t) s := igit.GetDefaultSettings() @@ -323,75 +494,78 @@ func TestTrimBranchPrefix(t *testing.T) { IgnoreEnvVars: true, TrimBranchPrefix: true, }) - if err != nil { - t.Error(err) - t.FailNow() - } + require.NoError(t, err) assert.Equal(t, "should-be-trimmed", label) } +func Test_ShouldResolveMainAsPrimaryBranchForLabelSuppression(t *testing.T) { + r := getSingleBranchCommit("main", t) + err := r.Storer.RemoveReference(plumbing.ReferenceName("refs/heads/master")) + require.NoError(t, err) + + s := igit.GetDefaultSettings() + branchSettings := &igit.BranchSettings{ + IgnoreEnvVars: true, + } + + label, err := igit.GetPrereleaseLabel(r, s, branchSettings) + require.NoError(t, err) + + primaryBranch, err := igit.GetPrimaryBranchName(r, branchSettings) + require.NoError(t, err) + if label == primaryBranch { + label = "" + } + + assert.Equal(t, "", label) +} + func TestCleanseBranchName(t *testing.T) { r := getSingleBranchCommit("author's-branch", t) s := igit.GetDefaultSettings() label, err := igit.GetPrereleaseLabel(r, s, &igit.BranchSettings{ IgnoreEnvVars: true, }) - if err != nil { - t.Error(err) - t.FailNow() - } + require.NoError(t, err) assert.Equal(t, "author-s-branch", label) } func getSingleBranchCommit(branchName string, t *testing.T) *git.Repository { + t.Helper() + fs := memfs.New() storage := memory.NewStorage() r, err := git.Init(storage, fs) - if err != nil { - t.Error(err) - t.FailNow() - } + require.NoError(t, err) w, err := r.Worktree() - if err != nil { - t.Error(err) - t.FailNow() - } + require.NoError(t, err) - util.WriteFile(fs, "foo", []byte("foo"), 0644) + err = util.WriteFile(fs, "foo", []byte("foo"), 0644) + require.NoError(t, err) _, err = w.Add("foo") - if err != nil { - t.Error(err) - t.FailNow() - } + require.NoError(t, err) _, err = w.Commit("foo\n", &git.CommitOptions{Author: defaultSignature()}) - if err != nil { - t.Error(err) - t.FailNow() - } + require.NoError(t, err) ref := fmt.Sprintf("refs/heads/%s", branchName) b := plumbing.ReferenceName(ref) - w.Checkout(&git.CheckoutOptions{ + err = w.Checkout(&git.CheckoutOptions{ Create: true, Force: false, Branch: b, }) + require.NoError(t, err) - util.WriteFile(fs, "foo2", []byte("foo"), 0644) + err = util.WriteFile(fs, "foo2", []byte("foo"), 0644) + require.NoError(t, err) _, err = w.Add("foo2") - if err != nil { - t.Error(err) - t.FailNow() - } + require.NoError(t, err) _, err = w.Commit("(+semver: major) This is a major commit\n", &git.CommitOptions{Author: defaultSignature()}) - if err != nil { - t.Error(err) - t.FailNow() - } + require.NoError(t, err) return r } diff --git a/pkg/git/settings.go b/pkg/git/settings.go index 3980e59..5a66cdf 100644 --- a/pkg/git/settings.go +++ b/pkg/git/settings.go @@ -2,10 +2,9 @@ package git import ( "io" - "io/ioutil" "github.com/pkg/errors" - "gopkg.in/yaml.v2" + "gopkg.in/yaml.v3" ) // Settings provides the regex patterns used for version bumping @@ -19,7 +18,7 @@ type Settings struct { func GetSettingsFromFile(file io.Reader) (*Settings, error) { s := Settings{} - fileBytes, err := ioutil.ReadAll(file) + fileBytes, err := io.ReadAll(file) if err != nil { return nil, errors.Wrap(err, "read bytes from file failed") } diff --git a/pkg/git/settings_test.go b/pkg/git/settings_test.go index 47315bd..25f0910 100644 --- a/pkg/git/settings_test.go +++ b/pkg/git/settings_test.go @@ -4,8 +4,9 @@ import ( "bytes" "testing" - "github.com/syncromatics/gogitver/pkg/git" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/syncromatics/gogitver/pkg/git" ) func TestSettingsParse(t *testing.T) { @@ -19,10 +20,7 @@ patch-version-bump-message: '\+semver:\s?(fix|patch)' r := bytes.NewReader(b) s, err := git.GetSettingsFromFile(r) - if err != nil { - t.Error(err) - t.FailNow() - } + require.NoError(t, err) assert.Equal(t, "\\+semver:\\s?(breaking|major)", s.MajorPattern) From a29581d3d989c34c9ab981d06a05a3e6312fb37d Mon Sep 17 00:00:00 2001 From: Glen Date: Tue, 2 Jun 2026 10:52:31 -0700 Subject: [PATCH 08/11] Update README.md --- README.md | 43 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 5805835..45d205f 100644 --- a/README.md +++ b/README.md @@ -6,13 +6,30 @@ gogitver is a tool to determine the semantic version of a project based on keywo ### Installing -#### Snap -```snap install gogitver``` +#### Go install + +If you have Go installed, install the latest tagged release directly from GitHub: + +```sh +go install github.com/syncromatics/gogitver/cmd/gogitver@latest +``` + +For a reproducible install, use a specific version tag: + +```sh +go install github.com/syncromatics/gogitver/cmd/gogitver@v1.2.0 +``` + +Make sure Go's binary directory is on your `PATH`. This is usually `$GOPATH/bin`, or `$HOME/go/bin` when `GOPATH` is not set. #### Binaries To install download the latest release from the [releases](https://github.com/syncromatics/gogitver/releases) page for your machine architecture and place the binary in your path. You can then run the executable while in the path of your project and it should output the current version. You can then use this version to tag container images, helm charts, etc. +#### Snap + +The Snap package is outdated and should be avoided for new installs. New versions are not currently published to Snap. + ### Usage To get this most out of this tool you should be adding keywords to your git commits. @@ -36,13 +53,27 @@ minor-version-bump-message: '(feat|feature|minor)\(.*\)' patch-version-bump-message: '(patch|fix)\(.*\)' ``` -You can also override the name and location of this file by providing the settings flag ```gotgitver --settings=./anotherfile.yaml``` +You can also override the name and location of this file by providing the settings flag ```gogitver --settings=./anotherfile.yaml``` + +### Primary branch + +gogitver uses the repository's primary branch as the stable version baseline. By default it looks for `main`, `master`, `origin/main`, then `origin/master`. + +If both `main` and `master` exist, gogitver requires an explicit primary branch so it does not silently switch the version baseline. + +You can override this with: + +```sh +gogitver --primary-branch=main +``` + +Use `--forbid-behind-primary` to error when the current branch calculates a version lower than the resolved primary branch. The older `--forbid-behind-master` flag remains available as a deprecated compatibility alias. ## Development ### Requirements -This project requires at least [Go](https://golang.org/dl/) 1.11 because it makes use of go modules for dependencies. +This project requires [Go](https://golang.org/dl/) 1.26.3. ### Building @@ -50,7 +81,7 @@ To build the project simply run ```make build``` which will generate the binarie ## Built With -* [go-git](https://github.com/src-d/go-git) - The git interface +* [go-git](https://github.com/go-git/go-git) - The git interface ## License @@ -58,6 +89,6 @@ This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md ## Acknowledgments -* [go-git](https://github.com/src-d/go-git) for allowing interactions with git to be easy and without dependencies +* [go-git](https://github.com/go-git/go-git) for allowing interactions with git to be easy and without dependencies * [GitVersion](https://github.com/GitTools/GitVersion) for the inspiration * [Visual Studio Code](https://code.visualstudio.com/) for just being an all around great editor From 23c5cd66b5833bb2c13ea33e3590a6bf65e9ab08 Mon Sep 17 00:00:00 2001 From: Glen Date: Tue, 2 Jun 2026 11:05:42 -0700 Subject: [PATCH 09/11] test for ambiguous prerelease --- cmd/gogitver/cmd/root.go | 14 +++++ pkg/git/git.go | 51 ++++++++++++++--- pkg/git/git_test.go | 116 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 173 insertions(+), 8 deletions(-) diff --git a/cmd/gogitver/cmd/root.go b/cmd/gogitver/cmd/root.go index 66dda9e..46eed2e 100644 --- a/cmd/gogitver/cmd/root.go +++ b/cmd/gogitver/cmd/root.go @@ -92,6 +92,11 @@ func getBoolFromFlag(cmd *cobra.Command, flagName string) bool { return result } +func isFlagChanged(cmd *cobra.Command, flagName string) bool { + flag := cmd.Flag(flagName) + return flag != nil && flag.Changed +} + func getBranchSettings(cmd *cobra.Command) *git.BranchSettings { fbm := getBoolFromFlag(cmd, "forbid-behind-master") fbp := getBoolFromFlag(cmd, "forbid-behind-primary") @@ -117,6 +122,10 @@ func runRoot(cmd *cobra.Command, args []string) { } branchSettings := getBranchSettings(cmd) + if branchSettings.ForbidBehindMaster && isFlagChanged(cmd, "forbid-behind-master") { + fmt.Fprintln(os.Stderr, "warning: --forbid-behind-master is deprecated; use --forbid-behind-primary") + } + version, err := git.GetCurrentVersion(r, s, branchSettings, v) if err != nil { panic(err) @@ -135,6 +144,11 @@ func runPrerelease(cmd *cobra.Command, args []string) { } primaryBranch, err := git.GetPrimaryBranchName(r, branchSettings) + if git.IsAmbiguousPrimaryBranchError(err) { + panic(err) + } + // The label command can still return the current branch label if the + // primary branch is missing; only ambiguous main/master detection is unsafe. if err == nil && label == primaryBranch { label = "" } diff --git a/pkg/git/git.go b/pkg/git/git.go index f6e6fcd..c4f1ace 100644 --- a/pkg/git/git.go +++ b/pkg/git/git.go @@ -1,6 +1,7 @@ package git import ( + stderrors "errors" "fmt" "log" "os" @@ -23,6 +24,21 @@ type BranchSettings struct { PrimaryBranch string } +type ambiguousPrimaryBranchError struct { + message string +} + +func (e *ambiguousPrimaryBranchError) Error() string { + return e.message +} + +// IsAmbiguousPrimaryBranchError returns true when primary branch auto-detection +// cannot safely choose between main and master. +func IsAmbiguousPrimaryBranchError(err error) bool { + var target *ambiguousPrimaryBranchError + return stderrors.As(err, &target) +} + type gitVersion struct { IsSolid bool Name *semver.Version @@ -206,10 +222,16 @@ func getPrimaryBranchReference(r *git.Repository, branchSettings *BranchSettings return ref, branchSettings.PrimaryBranch, nil } - mainRef, hasMain := getOptionalReference(r, plumbing.NewBranchReferenceName("main")) - masterRef, hasMaster := getOptionalReference(r, plumbing.NewBranchReferenceName("master")) + mainRef, hasMain, err := getOptionalReference(r, plumbing.NewBranchReferenceName("main")) + if err != nil { + return nil, "", err + } + masterRef, hasMaster, err := getOptionalReference(r, plumbing.NewBranchReferenceName("master")) + if err != nil { + return nil, "", err + } if hasMain && hasMaster { - return nil, "", errors.New("found both refs/heads/main and refs/heads/master; specify --primary-branch to choose the version baseline") + return nil, "", &ambiguousPrimaryBranchError{message: "found both refs/heads/main and refs/heads/master; specify --primary-branch to choose the version baseline"} } if hasMain { return mainRef, "main", nil @@ -218,10 +240,16 @@ func getPrimaryBranchReference(r *git.Repository, branchSettings *BranchSettings return masterRef, "master", nil } - originMainRef, hasOriginMain := getOptionalReference(r, plumbing.NewRemoteReferenceName("origin", "main")) - originMasterRef, hasOriginMaster := getOptionalReference(r, plumbing.NewRemoteReferenceName("origin", "master")) + originMainRef, hasOriginMain, err := getOptionalReference(r, plumbing.NewRemoteReferenceName("origin", "main")) + if err != nil { + return nil, "", err + } + originMasterRef, hasOriginMaster, err := getOptionalReference(r, plumbing.NewRemoteReferenceName("origin", "master")) + if err != nil { + return nil, "", err + } if hasOriginMain && hasOriginMaster { - return nil, "", errors.New("found both refs/remotes/origin/main and refs/remotes/origin/master; specify --primary-branch to choose the version baseline") + return nil, "", &ambiguousPrimaryBranchError{message: "found both refs/remotes/origin/main and refs/remotes/origin/master; specify --primary-branch to choose the version baseline"} } if hasOriginMain { return originMainRef, "main", nil @@ -233,9 +261,16 @@ func getPrimaryBranchReference(r *git.Repository, branchSettings *BranchSettings return nil, "", errors.New("failed to get primary branch at refs/heads/main, refs/heads/master, refs/remotes/origin/main, or refs/remotes/origin/master") } -func getOptionalReference(r *git.Repository, referenceName plumbing.ReferenceName) (*plumbing.Reference, bool) { +func getOptionalReference(r *git.Repository, referenceName plumbing.ReferenceName) (*plumbing.Reference, bool, error) { ref, err := r.Reference(referenceName, false) - return ref, err == nil + if err == plumbing.ErrReferenceNotFound { + return nil, false, nil + } + if err != nil { + return nil, false, errors.Wrapf(err, "failed to read reference '%s'", referenceName) + } + + return ref, true, nil } func getBranchReference(r *git.Repository, branchName string) (*plumbing.Reference, error) { diff --git a/pkg/git/git_test.go b/pkg/git/git_test.go index d3db4ac..74c23fb 100644 --- a/pkg/git/git_test.go +++ b/pkg/git/git_test.go @@ -173,6 +173,38 @@ func Test_ShouldUseOriginMainWhenLocalPrimaryBranchIsMissing(t *testing.T) { assert.Equal(t, expected, version) } +func Test_ShouldForbidBranchBehindPrimary(t *testing.T) { + // Arrange + repository := repositoryWithBranchBehindPrimary(t) + settings := igit.GetDefaultSettings() + branchSettings := &igit.BranchSettings{ + ForbidBehindPrimary: true, + IgnoreEnvVars: true, + } + + // Act + _, err := igit.GetCurrentVersion(repository, settings, branchSettings, false) + + // Assert + require.ErrorContains(t, err, "whose version is less than primary branch") +} + +func Test_ShouldForbidBranchBehindPrimaryWithMasterCompatibilityFlag(t *testing.T) { + // Arrange + repository := repositoryWithBranchBehindPrimary(t) + settings := igit.GetDefaultSettings() + branchSettings := &igit.BranchSettings{ + ForbidBehindMaster: true, + IgnoreEnvVars: true, + } + + // Act + _, err := igit.GetCurrentVersion(repository, settings, branchSettings, false) + + // Assert + require.ErrorContains(t, err, "whose version is less than primary branch") +} + func Test_ShouldFailWhenOriginMainAndOriginMasterBothExistWithoutExplicitPrimaryBranch(t *testing.T) { // Arrange repository, worktree := initRepository(t) @@ -487,6 +519,36 @@ func commitMerge(t *testing.T, worktree *git.Worktree, message string, parents . }) } +func repositoryWithBranchBehindPrimary(t *testing.T) *git.Repository { + t.Helper() + + repository, worktree := initRepository(t) + + initialHash := commitMultiple(t, worktree, "Initial commit") + err := repository.Storer.SetReference(plumbing.NewHashReference(plumbing.ReferenceName("refs/tags/v0.0.1"), initialHash)) + require.NoError(t, err) + + err = worktree.Checkout(&git.CheckoutOptions{ + Create: true, + Branch: plumbing.ReferenceName("refs/heads/a-branch"), + }) + require.NoError(t, err) + commitMultiple(t, worktree, "branch work\n") + + err = worktree.Checkout(&git.CheckoutOptions{ + Branch: plumbing.ReferenceName("refs/heads/master"), + }) + require.NoError(t, err) + commitMultiple(t, worktree, "(+semver: minor) primary branch work\n") + + err = worktree.Checkout(&git.CheckoutOptions{ + Branch: plumbing.ReferenceName("refs/heads/a-branch"), + }) + require.NoError(t, err) + + return repository +} + func TestTrimBranchPrefix(t *testing.T) { r := getSingleBranchCommit("feature/should-be-trimmed", t) s := igit.GetDefaultSettings() @@ -521,6 +583,60 @@ func Test_ShouldResolveMainAsPrimaryBranchForLabelSuppression(t *testing.T) { assert.Equal(t, "", label) } +func Test_LabelSuppressionKeepsBranchLabelWhenPrimaryBranchMissing(t *testing.T) { + // Arrange: a lone feature branch with no main/master to act as a baseline. + r := getSingleBranchCommit("feature", t) + err := r.Storer.RemoveReference(plumbing.ReferenceName("refs/heads/master")) + require.NoError(t, err) + + s := igit.GetDefaultSettings() + branchSettings := &igit.BranchSettings{ + IgnoreEnvVars: true, + } + + label, err := igit.GetPrereleaseLabel(r, s, branchSettings) + require.NoError(t, err) + + // Act: this mirrors the prerelease command's suppression logic. + primaryBranch, err := igit.GetPrimaryBranchName(r, branchSettings) + + // Assert: a missing primary branch is an ordinary (non-ambiguous) error, so + // the command continues and the branch label is preserved rather than blanked. + require.Error(t, err) + require.False(t, igit.IsAmbiguousPrimaryBranchError(err)) + if err == nil && label == primaryBranch { + label = "" + } + assert.Equal(t, "feature", label) +} + +func Test_LabelSuppressionDetectsAmbiguousPrimaryBranch(t *testing.T) { + // Arrange: both main and master exist, so the baseline is ambiguous. + r := getSingleBranchCommit("main", t) + + branchSettings := &igit.BranchSettings{ + IgnoreEnvVars: true, + } + + // Act + _, err := igit.GetPrimaryBranchName(r, branchSettings) + + // Assert: the prerelease command treats this as fatal (it panics) rather than + // silently suppressing the label. + require.Error(t, err) + assert.True(t, igit.IsAmbiguousPrimaryBranchError(err)) +} + +func Test_IsAmbiguousPrimaryBranchErrorUnwrapsWrappedErrors(t *testing.T) { + r := getSingleBranchCommit("main", t) + + _, err := igit.GetPrimaryBranchName(r, &igit.BranchSettings{IgnoreEnvVars: true}) + require.Error(t, err) + + wrapped := fmt.Errorf("getVersion failed: %w", err) + assert.True(t, igit.IsAmbiguousPrimaryBranchError(wrapped)) +} + func TestCleanseBranchName(t *testing.T) { r := getSingleBranchCommit("author's-branch", t) s := igit.GetDefaultSettings() From 4a21ba299b9acf0c9c3b6e1942f4dd1c6d7dbc3b Mon Sep 17 00:00:00 2001 From: Glen Date: Tue, 2 Jun 2026 11:13:36 -0700 Subject: [PATCH 10/11] dont permit local OR remote ambiguity --- README.md | 8 ++++---- pkg/git/git.go | 35 +++++++++++++++++------------------ pkg/git/git_test.go | 34 ++++++++++++++++++++++++++++++++-- 3 files changed, 53 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 45d205f..1d05e31 100644 --- a/README.md +++ b/README.md @@ -57,16 +57,16 @@ You can also override the name and location of this file by providing the settin ### Primary branch -gogitver uses the repository's primary branch as the stable version baseline. By default it looks for `main`, `master`, `origin/main`, then `origin/master`. +gogitver uses the repository's primary branch as the stable version baseline. By default it looks for `main`, `master`, `origin/main`, then `origin/master`. For a single branch name, a local ref is preferred over its remote counterpart (a local `main` is used before `origin/main`). -If both `main` and `master` exist, gogitver requires an explicit primary branch so it does not silently switch the version baseline. - -You can override this with: +If both a `main` and a `master` branch exist anywhere — local or remote, in any combination — gogitver refuses to guess the version baseline and requires an explicit primary branch. This prevents it from silently switching the baseline during a branch migration: ```sh gogitver --primary-branch=main ``` +The `--primary-branch` value also takes a local ref or `origin/`, so you can point the baseline at any branch, not just `main` or `master`. + Use `--forbid-behind-primary` to error when the current branch calculates a version lower than the resolved primary branch. The older `--forbid-behind-master` flag remains available as a deprecated compatibility alias. ## Development diff --git a/pkg/git/git.go b/pkg/git/git.go index c4f1ace..287c0b7 100644 --- a/pkg/git/git.go +++ b/pkg/git/git.go @@ -222,16 +222,19 @@ func getPrimaryBranchReference(r *git.Repository, branchSettings *BranchSettings return ref, branchSettings.PrimaryBranch, nil } - mainRef, hasMain, err := getOptionalReference(r, plumbing.NewBranchReferenceName("main")) + mainRef, hasMain, err := resolvePrimaryCandidate(r, "main") if err != nil { return nil, "", err } - masterRef, hasMaster, err := getOptionalReference(r, plumbing.NewBranchReferenceName("master")) + masterRef, hasMaster, err := resolvePrimaryCandidate(r, "master") if err != nil { return nil, "", err } + + // Ambiguity is name-based across both local and remote refs: if a 'main' and + // a 'master' branch both exist anywhere, refuse to guess the version baseline. if hasMain && hasMaster { - return nil, "", &ambiguousPrimaryBranchError{message: "found both refs/heads/main and refs/heads/master; specify --primary-branch to choose the version baseline"} + return nil, "", &ambiguousPrimaryBranchError{message: "found both 'main' and 'master' branches (local or remote); specify --primary-branch to choose the version baseline"} } if hasMain { return mainRef, "main", nil @@ -240,25 +243,21 @@ func getPrimaryBranchReference(r *git.Repository, branchSettings *BranchSettings return masterRef, "master", nil } - originMainRef, hasOriginMain, err := getOptionalReference(r, plumbing.NewRemoteReferenceName("origin", "main")) - if err != nil { - return nil, "", err - } - originMasterRef, hasOriginMaster, err := getOptionalReference(r, plumbing.NewRemoteReferenceName("origin", "master")) + return nil, "", errors.New("failed to get primary branch at refs/heads/main, refs/heads/master, refs/remotes/origin/main, or refs/remotes/origin/master") +} + +// resolvePrimaryCandidate reports whether a branch of the given name exists as a +// local or remote ref, preferring the local ref when both are present. +func resolvePrimaryCandidate(r *git.Repository, branchName string) (*plumbing.Reference, bool, error) { + localRef, hasLocal, err := getOptionalReference(r, plumbing.NewBranchReferenceName(branchName)) if err != nil { - return nil, "", err - } - if hasOriginMain && hasOriginMaster { - return nil, "", &ambiguousPrimaryBranchError{message: "found both refs/remotes/origin/main and refs/remotes/origin/master; specify --primary-branch to choose the version baseline"} + return nil, false, err } - if hasOriginMain { - return originMainRef, "main", nil - } - if hasOriginMaster { - return originMasterRef, "master", nil + if hasLocal { + return localRef, true, nil } - return nil, "", errors.New("failed to get primary branch at refs/heads/main, refs/heads/master, refs/remotes/origin/main, or refs/remotes/origin/master") + return getOptionalReference(r, plumbing.NewRemoteReferenceName("origin", branchName)) } func getOptionalReference(r *git.Repository, referenceName plumbing.ReferenceName) (*plumbing.Reference, bool, error) { diff --git a/pkg/git/git_test.go b/pkg/git/git_test.go index 74c23fb..ef84334 100644 --- a/pkg/git/git_test.go +++ b/pkg/git/git_test.go @@ -102,7 +102,7 @@ func Test_ShouldFailWhenMainAndMasterBothExistWithoutExplicitPrimaryBranch(t *te _, err = igit.GetCurrentVersion(repository, settings, branchSettings, false) // Assert - require.ErrorContains(t, err, "found both refs/heads/main and refs/heads/master") + require.ErrorContains(t, err, "found both 'main' and 'master' branches") } func Test_ShouldUseExplicitPrimaryBranch(t *testing.T) { @@ -236,7 +236,37 @@ func Test_ShouldFailWhenOriginMainAndOriginMasterBothExistWithoutExplicitPrimary _, err = igit.GetCurrentVersion(repository, settings, branchSettings, false) // Assert - require.ErrorContains(t, err, "found both refs/remotes/origin/main and refs/remotes/origin/master") + require.ErrorContains(t, err, "found both 'main' and 'master' branches") +} + +func Test_ShouldFailWhenLocalMasterAndOriginMainBothExistWithoutExplicitPrimaryBranch(t *testing.T) { + // Arrange: a local master and a remote main are both present. Ambiguity is + // name-based across local and remote, so this must not silently prefer local. + repository, worktree := initRepository(t) + + primaryHash := commitMultiple(t, worktree, "Initial commit") + + err := repository.Storer.SetReference(plumbing.NewHashReference(plumbing.ReferenceName("refs/remotes/origin/main"), primaryHash)) + require.NoError(t, err) + + err = worktree.Checkout(&git.CheckoutOptions{ + Create: true, + Branch: plumbing.ReferenceName("refs/heads/a-branch"), + }) + require.NoError(t, err) + + commitMultiple(t, worktree, "(+semver: patch) branch commit\n") + + settings := igit.GetDefaultSettings() + branchSettings := &igit.BranchSettings{ + IgnoreEnvVars: true, + } + + // Act + _, err = igit.GetCurrentVersion(repository, settings, branchSettings, false) + + // Assert + require.ErrorContains(t, err, "found both 'main' and 'master' branches") } func Test_ShouldCalculateVersionFromCommitsInBranch(t *testing.T) { From c34d455a1238e1c41c81d86f502102218b233717 Mon Sep 17 00:00:00 2001 From: Glen Date: Tue, 2 Jun 2026 11:39:08 -0700 Subject: [PATCH 11/11] upgrade github runner --- .github/workflows/build.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3a438a3..24d8cb0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,12 +5,12 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: fetch-depth: 0 - name: Install Go - uses: actions/setup-go@v5 + uses: actions/setup-go@v6 with: go-version: 1.26.3 @@ -24,7 +24,7 @@ jobs: permissions: contents: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: fetch-depth: 0 @@ -32,7 +32,7 @@ jobs: id: gogitver - name: Install Go - uses: actions/setup-go@v5 + uses: actions/setup-go@v6 with: go-version: 1.26.3