From c92ccafa8dd73413ecb3eb1be2d52b534ef0e292 Mon Sep 17 00:00:00 2001 From: Kirill Zhuravlev Date: Sat, 11 Apr 2026 23:17:52 +0200 Subject: [PATCH 1/6] upd deps --- go.mod | 24 ++++++++++++------------ go.sum | 24 ++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 2985ba9..6415bcc 100644 --- a/go.mod +++ b/go.mod @@ -1,35 +1,35 @@ module github.com/kazhuravlev/git-tools -go 1.25 +go 1.25.0 require ( github.com/Masterminds/semver/v3 v3.4.0 - github.com/go-git/go-git/v5 v5.16.3 - github.com/kazhuravlev/optional v0.5.0 - github.com/urfave/cli/v3 v3.5.0 + github.com/go-git/go-git/v5 v5.17.2 + github.com/kazhuravlev/optional v0.6.0 + github.com/urfave/cli/v3 v3.8.0 ) require ( dario.cat/mergo v1.0.2 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/ProtonMail/go-crypto v1.3.0 // indirect - github.com/cloudflare/circl v1.6.1 // indirect - github.com/cyphar/filepath-securejoin v0.5.0 // indirect + github.com/ProtonMail/go-crypto v1.4.1 // indirect + github.com/cloudflare/circl v1.6.3 // indirect + github.com/cyphar/filepath-securejoin v0.6.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect - github.com/go-git/go-billy/v5 v5.6.2 // indirect + github.com/go-git/go-billy/v5 v5.8.0 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect - github.com/kevinburke/ssh_config v1.4.0 // indirect + github.com/kevinburke/ssh_config v1.6.0 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/pjbgf/sha1cd v0.5.0 // indirect github.com/sergi/go-diff v1.4.0 // indirect github.com/skeema/knownhosts v1.3.2 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect - golang.org/x/crypto v0.43.0 // indirect - golang.org/x/net v0.46.0 // indirect - golang.org/x/sys v0.37.0 // 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 gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index fc52dfa..dac997a 100644 --- a/go.sum +++ b/go.sum @@ -7,14 +7,20 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= +github.com/ProtonMail/go-crypto v1.4.1 h1:9RfcZHqEQUvP8RzecWEUafnZVtEvrBVL9BiF67IQOfM= +github.com/ProtonMail/go-crypto v1.4.1/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo= 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.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +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/cyphar/filepath-securejoin v0.5.0 h1:hIAhkRBMQ8nIeuVwcAoymp7MY4oherZdAxD+m0u9zaw= github.com/cyphar/filepath-securejoin v0.5.0/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= +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= @@ -28,10 +34,14 @@ github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66D github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= +github.com/go-git/go-billy/v5 v5.8.0 h1:I8hjc3LbBlXTtVuFNJuwYuMiHvQJDq1AT6u4DwDzZG0= +github.com/go-git/go-billy/v5 v5.8.0/go.mod h1:RpvI/rw4Vr5QA+Z60c6d6LXH0rYJo0uD5SqfmrrheCY= 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.16.3 h1:Z8BtvxZ09bYm/yYNgPKCzgWtaRqDTgIKRgIRHBfU6Z8= github.com/go-git/go-git/v5 v5.16.3/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= +github.com/go-git/go-git/v5 v5.17.2 h1:B+nkdlxdYrvyFK4GPXVU8w1U+YkbsgciIR7f2sZJ104= +github.com/go-git/go-git/v5 v5.17.2/go.mod h1:pW/VmeqkanRFqR6AljLcs7EA7FbZaN5MQqO7oZADXpo= 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= @@ -40,8 +50,12 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/kazhuravlev/optional v0.5.0 h1:/khwujlzK1NHCkd/cqmrRmi1cf/xD8qAMopxMkbIeh0= github.com/kazhuravlev/optional v0.5.0/go.mod h1:ZF3oqjzRrpuHTIk48I/N7uf6qvHZPJG6EPGNtFjUAQc= +github.com/kazhuravlev/optional v0.6.0 h1:hOKPLKe5fCMMtjLBi1XMbX5UExzTnc6NwTJcL1QzVbw= +github.com/kazhuravlev/optional v0.6.0/go.mod h1:kbpkL+lqquwO0i8vifRSG/hnJ4FWv8Ks14JKCquhPoQ= github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ= github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M= +github.com/kevinburke/ssh_config v1.6.0 h1:J1FBfmuVosPHf5GRdltRLhPJtJpTlMdKTBjRgTaQBFY= +github.com/kevinburke/ssh_config v1.6.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M= 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= @@ -73,17 +87,23 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/urfave/cli/v3 v3.5.0 h1:qCuFMmdayTF3zmjG8TSsoBzrDqszNrklYg2x3g4MSgw= github.com/urfave/cli/v3 v3.5.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso= +github.com/urfave/cli/v3 v3.8.0 h1:XqKPrm0q4P0q5JpoclYoCAv0/MIvH/jZ2umzuf8pNTI= +github.com/urfave/cli/v3 v3.8.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso= 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= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= +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-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +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= @@ -92,12 +112,16 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +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.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= +golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= +golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= 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-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 2c77cd6e60972c8581563d3cc5013b47e0a189b1 Mon Sep 17 00:00:00 2001 From: Kirill Zhuravlev Date: Sat, 11 Apr 2026 23:17:57 +0200 Subject: [PATCH 2/6] 1.26 --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 6415bcc..e02f029 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/kazhuravlev/git-tools -go 1.25.0 +go 1.26.0 require ( github.com/Masterminds/semver/v3 v3.4.0 From 3c9fe781bcaa179163242e332f20753a44c86dcd Mon Sep 17 00:00:00 2001 From: Kirill Zhuravlev Date: Sat, 11 Apr 2026 23:18:01 +0200 Subject: [PATCH 3/6] ups alpine --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index f521c3c..ac348e4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.16 +FROM alpine:3.23 ENV WORKDIR=/workdir From 1aa65fc2011cd3268460f2999158f0b6f9420c2e Mon Sep 17 00:00:00 2001 From: Kirill Zhuravlev Date: Sat, 11 Apr 2026 23:18:08 +0200 Subject: [PATCH 4/6] upd 1.26 --- .github/workflows/check.yml | 2 +- .github/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 5e32ae0..23b7ce5 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -11,7 +11,7 @@ jobs: strategy: matrix: go_version: - - "1.25" + - "1.26" os: [ ubuntu-latest, macOS-latest, windows-latest ] steps: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f326af2..27aea55 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,7 +21,7 @@ jobs: - name: Setup golang uses: actions/setup-go@v4 with: - go-version: '>=1.25' + go-version: '>=1.26' cache: true - name: Log in to Docker Hub uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 From 02c5c4d99c2f775d6bb2acad3fa9e6531ee8ab86 Mon Sep 17 00:00:00 2001 From: Kirill Zhuravlev Date: Sat, 11 Apr 2026 23:25:49 +0200 Subject: [PATCH 5/6] refactor the app --- cmd/gt/main.go | 11 --- internal/repo-manager/manager.go | 139 ++++++++++++++++--------------- 2 files changed, 72 insertions(+), 78 deletions(-) diff --git a/cmd/gt/main.go b/cmd/gt/main.go index ca636a7..6ac65f1 100644 --- a/cmd/gt/main.go +++ b/cmd/gt/main.go @@ -3,7 +3,6 @@ package main import ( "bytes" "context" - "errors" "fmt" repomanager "github.com/kazhuravlev/git-tools/internal/repo-manager" "github.com/kazhuravlev/git-tools/internal/which" @@ -163,16 +162,6 @@ func buildTagIncrementor(component repomanager.Component) func(context.Context, return func(ctx context.Context, c *cli.Command, m *repomanager.Manager) error { ignoreExistsTag := c.Bool(flagIgnoreExistsTag) - repoPath := c.String(flagRepoPath) - if repoPath == "" { - return errors.New("path to repo must be set by flag " + flagRepoPath) - } - - m, err := repomanager.New(repoPath) - if err != nil { - return fmt.Errorf("cannot build repo manager: %w", err) - } - curTag, err := m.GetCurrentTagSemver() if err != nil { return fmt.Errorf("get current tag: %w", err) diff --git a/internal/repo-manager/manager.go b/internal/repo-manager/manager.go index 15fbe84..f06b13b 100644 --- a/internal/repo-manager/manager.go +++ b/internal/repo-manager/manager.go @@ -57,6 +57,30 @@ func (m *Manager) GetTagsAll() ([]*plumbing.Reference, error) { return tags, nil } +func (m *Manager) forEachSemverTag(fn func(SemverTag) error) error { + tagReferences, err := m.repo.Tags() + if err != nil { + return fmt.Errorf("get repo tags: %w", err) + } + + err = tagReferences.ForEach(func(t *plumbing.Reference) error { + version, err := semver.NewVersion(t.Name().Short()) + if err != nil { + return nil + } + + return fn(SemverTag{ + Version: *version, + Ref: t, + }) + }) + if err != nil { + return fmt.Errorf("iterate semver tags: %w", err) + } + + return nil +} + type SemverTag struct { Version semver.Version Ref *plumbing.Reference @@ -76,26 +100,13 @@ func (t SemverTag) CommitHash() string { // GetTagsSemver returns only semver tags func (m *Manager) GetTagsSemver() ([]SemverTag, error) { - references, err := m.GetTagsAll() + res := make([]SemverTag, 0) + err := m.forEachSemverTag(func(tag SemverTag) error { + res = append(res, tag) + return nil + }) if err != nil { - return nil, fmt.Errorf("get all tags: %w", err) - } - - res := make([]SemverTag, 0, len(references)) - for i := range references { - ref := references[i] - - tagName := ref.Name().Short() - - version, err := semver.NewVersion(tagName) - if err != nil { - continue - } - - res = append(res, SemverTag{ - Version: *version, - Ref: ref, - }) + return nil, fmt.Errorf("get semver tags: %w", err) } return res, nil @@ -103,21 +114,17 @@ func (m *Manager) GetTagsSemver() ([]SemverTag, error) { // GetTagsSemverMax return tag that point to max semver version func (m *Manager) GetTagsSemverMax() (*SemverTag, error) { - tags, err := m.GetTagsSemver() - if err != nil { - return nil, fmt.Errorf("get semver tags: %w", err) - } - - maxTag := SemverTag{ - Version: *semver.MustParse("v0.0.0"), - Ref: nil, - } + var maxTag SemverTag var found bool - for i := range tags { - if tags[i].Version.GreaterThan(&maxTag.Version) { + err := m.forEachSemverTag(func(tag SemverTag) error { + if !found || tag.Version.GreaterThan(&maxTag.Version) { found = true - maxTag = tags[i] + maxTag = tag } + return nil + }) + if err != nil { + return nil, fmt.Errorf("get semver tags: %w", err) } if !found { @@ -129,25 +136,39 @@ func (m *Manager) GetTagsSemverMax() (*SemverTag, error) { // GetTagsSemverTopN return top n semver tags func (m *Manager) GetTagsSemverTopN(n int) ([]SemverTag, error) { - tags, err := m.GetTagsSemver() - if err != nil { - return nil, fmt.Errorf("get semver tags: %w", err) + if n <= 0 { + return nil, nil } - sort.SliceStable(tags, func(i, j int) bool { - return tags[i].Version.LessThan(&tags[j].Version) - }) + top := make([]SemverTag, 0, n) + err := m.forEachSemverTag(func(tag SemverTag) error { + if len(top) < n { + top = append(top, tag) + sort.Slice(top, func(i, j int) bool { + return top[i].Version.LessThan(&top[j].Version) + }) + return nil + } - res := make([]SemverTag, 0, n) - for i := range tags { - if i == n { - break + if !top[0].Version.LessThan(&tag.Version) { + return nil } - res = append(res, tags[i]) + top[0] = tag + sort.Slice(top, func(i, j int) bool { + return top[i].Version.LessThan(&top[j].Version) + }) + return nil + }) + if err != nil { + return nil, fmt.Errorf("get semver tags: %w", err) } - return res, nil + sort.Slice(top, func(i, j int) bool { + return top[i].Version.GreaterThan(&top[j].Version) + }) + + return top, nil } // GetCurrentTagSemver return a tag if that is presented for current commit. It will ignore all non-semver tags. @@ -157,33 +178,17 @@ func (m *Manager) GetCurrentTagSemver() (optional.Val[SemverTag], error) { return optional.Empty[SemverTag](), fmt.Errorf("get repo head: %w", err) } - tagReferences, err := m.repo.Tags() - if err != nil { - return optional.Empty[SemverTag](), fmt.Errorf("get repo tags: %w", err) - } - var tag optional.Val[SemverTag] - { - err := tagReferences.ForEach(func(t *plumbing.Reference) error { - if t.Hash() == head.Hash() { - version, err := semver.NewVersion(t.Name().Short()) - if err != nil { - return nil - } - - tag = optional.New(SemverTag{ - Version: *version, - Ref: t, - }) - - return storer.ErrStop - } - + err = m.forEachSemverTag(func(candidate SemverTag) error { + if candidate.Ref.Hash() != head.Hash() { return nil - }) - if err != nil { - return optional.Empty[SemverTag](), fmt.Errorf("get repo tags: %w", err) } + + tag = optional.New(candidate) + return storer.ErrStop + }) + if err != nil && !errors.Is(err, storer.ErrStop) { + return optional.Empty[SemverTag](), fmt.Errorf("get repo tags: %w", err) } return tag, nil From f7736806018e06837a9dc0eacc3a8e94f4e5e844 Mon Sep 17 00:00:00 2001 From: Kirill Zhuravlev Date: Sat, 11 Apr 2026 23:27:47 +0200 Subject: [PATCH 6/6] refine readme --- README.md | 38 +++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 7675f3b..085545a 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,8 @@ [![Go Report Card](https://goreportcard.com/badge/github.com/kazhuravlev/git-tools)](https://goreportcard.com/report/github.com/kazhuravlev/git-tools) [![Mentioned in Awesome Go](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go) -A CLI tool for managing git repositories with semantic versioning tags. Simplifies version bumping, tag validation, and -git hook management. +A CLI tool for managing git repositories with semantic versioning tags. It helps with version bumps, tag validation, +author reporting, and lightweight git hook automation. **Quick example:** @@ -19,9 +19,9 @@ git commit -am "changes" && gt t i min && git push --follow-tags **Key features:** - Automatic semver tag incrementing (major/minor/patch) -- Tag format consistency checking (`v1.2.3` vs `1.2.3`) -- Git hook installation (commit-msg with branch name) -- Author statistics +- Tag format consistency checking (`v1.2.3` vs `1.2.3`) and duplicate semver tags on the same commit +- Git hook installation for `commit-msg` +- Author statistics based on commit history ## Installation @@ -37,10 +37,10 @@ go install github.com/kazhuravlev/git-tools/cmd/gt@latest brew install kazhuravlev/git-tools/git-tools ``` -**Docker (zsh)** (will work only in current directory) +**Docker (zsh)** ```shell -echo 'alias gt="docker run -it --rm -v `pwd`:/workdir kazhuravlev/gt:latest"' >> ~/.zshrc +echo 'alias gt="docker run -it --rm -v $(pwd):/workdir -w /workdir kazhuravlev/gt:latest"' >> ~/.zshrc ``` ## Usage @@ -52,20 +52,20 @@ All commands work with the current directory by default. Use `--repo=/path/to/re **Tag Management:** ```shell -gt tag last # Show last semver tag (alias: gt t l) -gt tag last -f tag # Show only tag name (useful for CI/CD) +gt tag last # Show the highest semver tag and commit hash (alias: gt t l) +gt tag last -f tag # Show only the tag name (useful for CI/CD) -gt tag increment major # Bump major version: v1.2.3 → v2.0.0 (alias: gt t i maj) -gt tag increment minor # Bump minor version: v1.2.3 → v1.3.0 (alias: gt t i min) -gt tag increment patch # Bump patch version: v1.2.3 → v1.2.4 (alias: gt t i pat) +gt tag increment major # Create the next major tag: v1.2.3 → v2.0.0 (alias: gt t i maj) +gt tag increment minor # Create the next minor tag: v1.2.3 → v1.3.0 (alias: gt t i min) +gt tag increment patch # Create the next patch tag: v1.2.3 → v1.2.4 (alias: gt t i pat) ``` **Other Commands:** ```shell -gt lint # Validate tag format consistency +gt lint # Validate recent semver tags for style consistency and duplicate tags per commit gt authors # List commit authors with statistics (alias: gt a) -gt hooks install all # Install git hooks (alias: gt h i a) +gt hooks install all # Install supported git hooks into .git/hooks (alias: gt h i a) gt hooks list # List available hooks (alias: gt h l) ``` @@ -78,6 +78,8 @@ gt hooks list # List available hooks (alias: gt h l) gt t i min --ignore-exists-tag ``` +By default, `gt tag increment ...` refuses to create a new semver tag if the current commit already has one. + **Custom repository path:** ```shell @@ -96,6 +98,12 @@ git push --follow-tags VERSION=$(gt tag last -f tag) echo "Building version: $VERSION" -# Check tag consistency before release +# Check recent tags before release gt lint + +# Show commit authors sorted by commit count +gt authors + +# Install the commit-msg hook that prefixes the current branch name +gt hooks install all ```