1
0
mirror of https://github.com/redis/go-redis.git synced 2025-12-18 23:34:11 +03:00

chore(go): update go version to 1.21 (#3640)

* chore(go): update go version to 1.21

* chore(aggregators): make aggregators work with 1.21

* fix doctests

* address copilot comments

* use atomic bool for logic and/or aggregators

* Update .github/workflows/build.yml

Co-authored-by: ccoVeille <3875889+ccoVeille@users.noreply.github.com>

* use stable/oldstable, 1.23 and 1.21

* fix versions in README

* add oldstable in wordlist

---------

Co-authored-by: ccoVeille <3875889+ccoVeille@users.noreply.github.com>
This commit is contained in:
Nedyalko Dyakov
2025-12-05 12:27:55 +02:00
committed by GitHub
parent 16b55f9519
commit 674c7b8695
23 changed files with 329 additions and 45 deletions

View File

@@ -76,3 +76,4 @@ oauth
entraid entraid
MiB MiB
KiB KiB
oldstable

View File

@@ -22,8 +22,10 @@ jobs:
- "8.2.x" # Redis CE 8.2 - "8.2.x" # Redis CE 8.2
- "8.0.x" # Redis CE 8.0 - "8.0.x" # Redis CE 8.0
go-version: go-version:
- "1.21.x"
- "1.23.x" - "1.23.x"
- "1.24.x" - oldstable
- stable
steps: steps:
- name: Set up ${{ matrix.go-version }} - name: Set up ${{ matrix.go-version }}
@@ -78,8 +80,10 @@ jobs:
- "8.2.x" # Redis CE 8.2 - "8.2.x" # Redis CE 8.2
- "8.0.x" # Redis CE 8.0 - "8.0.x" # Redis CE 8.0
go-version: go-version:
- "1.21.x"
- "1.23.x" - "1.23.x"
- "1.24.x" - oldstable
- stable
steps: steps:
- name: Checkout code - name: Checkout code

View File

@@ -21,13 +21,12 @@ In `go-redis` we are aiming to support the last three releases of Redis. Current
- [Redis 8.2](https://raw.githubusercontent.com/redis/redis/8.2/00-RELEASENOTES) - using Redis CE 8.2 - [Redis 8.2](https://raw.githubusercontent.com/redis/redis/8.2/00-RELEASENOTES) - using Redis CE 8.2
- [Redis 8.4](https://raw.githubusercontent.com/redis/redis/8.4/00-RELEASENOTES) - using Redis CE 8.4 - [Redis 8.4](https://raw.githubusercontent.com/redis/redis/8.4/00-RELEASENOTES) - using Redis CE 8.4
Although the `go.mod` states it requires at minimum `go 1.18`, our CI is configured to run the tests against all three Although the `go.mod` states it requires at minimum `go 1.21`, our CI is configured to run the tests against all three
versions of Redis and latest two versions of Go ([1.23](https://go.dev/doc/devel/release#go1.23.0), versions of Redis and multiple versions of Go ([1.21](https://go.dev/doc/devel/release#go1.21.0),
[1.24](https://go.dev/doc/devel/release#go1.24.0)). We observe that some modules related test may not pass with [1.23](https://go.dev/doc/devel/release#go1.23.0), oldstable, and stable). We observe that some modules related test may not pass with
Redis Stack 7.2 and some commands are changed with Redis CE 8.0. Redis Stack 7.2 and some commands are changed with Redis CE 8.0.
Although it is not officially supported, `go-redis/v9` should be able to work with any Redis 7.0+. Although it is not officially supported, `go-redis/v9` should be able to work with any Redis 7.0+.
Please do refer to the documentation and the tests if you experience any issues. We do plan to update the go version Please do refer to the documentation and the tests if you experience any issues.
in the `go.mod` to `go 1.24` in one of the next releases.
## How do I Redis? ## How do I Redis?

View File

@@ -5,9 +5,7 @@ package example_commands_test
import ( import (
"context" "context"
"fmt" "fmt"
"maps"
"math" "math"
"slices"
"sort" "sort"
"github.com/redis/go-redis/v9" "github.com/redis/go-redis/v9"
@@ -15,6 +13,16 @@ import (
// HIDE_END // HIDE_END
// mapKeys returns a slice of all keys from the map (Go 1.21 compatible)
// TODO: Once minimum Go version is upgraded to 1.23+, replace with slices.Collect(maps.Keys(m))
func mapKeys[K comparable, V any](m map[K]V) []K {
keys := make([]K, 0, len(m))
for k := range m {
keys = append(keys, k)
}
return keys
}
func ExampleClient_timeseries_create() { func ExampleClient_timeseries_create() {
ctx := context.Background() ctx := context.Background()
@@ -417,7 +425,7 @@ func ExampleClient_timeseries_query_multi() {
panic(err) panic(err)
} }
res28Keys := slices.Collect(maps.Keys(res28)) res28Keys := mapKeys(res28)
sort.Strings(res28Keys) sort.Strings(res28Keys)
for _, k := range res28Keys { for _, k := range res28Keys {
@@ -457,7 +465,7 @@ func ExampleClient_timeseries_query_multi() {
panic(err) panic(err)
} }
res29Keys := slices.Collect(maps.Keys(res29)) res29Keys := mapKeys(res29)
sort.Strings(res29Keys) sort.Strings(res29Keys)
for _, k := range res29Keys { for _, k := range res29Keys {
@@ -505,7 +513,7 @@ func ExampleClient_timeseries_query_multi() {
panic(err) panic(err)
} }
res30Keys := slices.Collect(maps.Keys(res30)) res30Keys := mapKeys(res30)
sort.Strings(res30Keys) sort.Strings(res30Keys)
for _, k := range res30Keys { for _, k := range res30Keys {
@@ -550,7 +558,7 @@ func ExampleClient_timeseries_query_multi() {
panic(err) panic(err)
} }
res31Keys := slices.Collect(maps.Keys(res31)) res31Keys := mapKeys(res31)
sort.Strings(res31Keys) sort.Strings(res31Keys)
for _, k := range res31Keys { for _, k := range res31Keys {
@@ -857,7 +865,7 @@ func ExampleClient_timeseries_aggmulti() {
panic(err) panic(err)
} }
res44Keys := slices.Collect(maps.Keys(res44)) res44Keys := mapKeys(res44)
sort.Strings(res44Keys) sort.Strings(res44Keys)
for _, k := range res44Keys { for _, k := range res44Keys {
@@ -905,7 +913,7 @@ func ExampleClient_timeseries_aggmulti() {
panic(err) panic(err)
} }
res45Keys := slices.Collect(maps.Keys(res45)) res45Keys := mapKeys(res45)
sort.Strings(res45Keys) sort.Strings(res45Keys)
for _, k := range res45Keys { for _, k := range res45Keys {

View File

@@ -1,6 +1,6 @@
module github.com/redis/go-redis/example/cluster-mget module github.com/redis/go-redis/example/cluster-mget
go 1.18 go 1.21
replace github.com/redis/go-redis/v9 => ../.. replace github.com/redis/go-redis/v9 => ../..

View File

@@ -1,6 +1,6 @@
module github.com/redis/go-redis/example/del-keys-without-ttl module github.com/redis/go-redis/example/del-keys-without-ttl
go 1.18 go 1.21
replace github.com/redis/go-redis/v9 => ../.. replace github.com/redis/go-redis/v9 => ../..

View File

@@ -1,6 +1,6 @@
module github.com/redis/go-redis/example/digest-optimistic-locking module github.com/redis/go-redis/example/digest-optimistic-locking
go 1.18 go 1.21
replace github.com/redis/go-redis/v9 => ../.. replace github.com/redis/go-redis/v9 => ../..

View File

@@ -1,6 +1,6 @@
module github.com/redis/go-redis/example/hll module github.com/redis/go-redis/example/hll
go 1.18 go 1.21
replace github.com/redis/go-redis/v9 => ../.. replace github.com/redis/go-redis/v9 => ../..

View File

@@ -1,6 +1,6 @@
module github.com/redis/go-redis/example/scan-struct module github.com/redis/go-redis/example/scan-struct
go 1.18 go 1.21
replace github.com/redis/go-redis/v9 => ../.. replace github.com/redis/go-redis/v9 => ../..

View File

@@ -1,6 +1,6 @@
module github.com/redis/go-redis/example/lua-scripting module github.com/redis/go-redis/example/lua-scripting
go 1.18 go 1.21
replace github.com/redis/go-redis/v9 => ../.. replace github.com/redis/go-redis/v9 => ../..

View File

@@ -1,6 +1,6 @@
module github.com/redis/go-redis/example/pubsub module github.com/redis/go-redis/example/pubsub
go 1.18 go 1.21
replace github.com/redis/go-redis/v9 => ../.. replace github.com/redis/go-redis/v9 => ../..

View File

@@ -1,6 +1,6 @@
module github.com/redis/go-redis/example/redis-bloom module github.com/redis/go-redis/example/redis-bloom
go 1.18 go 1.21
replace github.com/redis/go-redis/v9 => ../.. replace github.com/redis/go-redis/v9 => ../..

View File

@@ -1,6 +1,6 @@
module github.com/redis/go-redis/example/scan-struct module github.com/redis/go-redis/example/scan-struct
go 1.18 go 1.21
replace github.com/redis/go-redis/v9 => ../.. replace github.com/redis/go-redis/v9 => ../..

View File

@@ -1,6 +1,6 @@
module github.com/redis/go-redis/extra/rediscensus/v9 module github.com/redis/go-redis/extra/rediscensus/v9
go 1.19 go 1.21
replace github.com/redis/go-redis/v9 => ../.. replace github.com/redis/go-redis/v9 => ../..

View File

@@ -1,6 +1,6 @@
module github.com/redis/go-redis/extra/rediscmd/v9 module github.com/redis/go-redis/extra/rediscmd/v9
go 1.19 go 1.21
replace github.com/redis/go-redis/v9 => ../.. replace github.com/redis/go-redis/v9 => ../..

View File

@@ -1,6 +1,6 @@
module github.com/redis/go-redis/extra/redisotel/v9 module github.com/redis/go-redis/extra/redisotel/v9
go 1.19 go 1.21
replace github.com/redis/go-redis/v9 => ../.. replace github.com/redis/go-redis/v9 => ../..

View File

@@ -1,6 +1,6 @@
module github.com/redis/go-redis/extra/redisprometheus/v9 module github.com/redis/go-redis/extra/redisprometheus/v9
go 1.19 go 1.21
replace github.com/redis/go-redis/v9 => ../.. replace github.com/redis/go-redis/v9 => ../..

2
go.mod
View File

@@ -1,6 +1,6 @@
module github.com/redis/go-redis/v9 module github.com/redis/go-redis/v9
go 1.18 go 1.21
require ( require (
github.com/bsm/ginkgo/v2 v2.12.0 github.com/bsm/ginkgo/v2 v2.12.0

3
go.sum
View File

@@ -5,9 +5,12 @@ github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 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/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 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/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=

View File

@@ -1,6 +1,6 @@
module github.com/redis/go-redis/internal/customvet module github.com/redis/go-redis/internal/customvet
go 1.17 go 1.21
require golang.org/x/tools v0.5.0 require golang.org/x/tools v0.5.0

View File

@@ -65,7 +65,7 @@ func NewResponseAggregator(policy ResponsePolicy, cmdName string) ResponseAggreg
} }
case RespAggLogicalAnd: case RespAggLogicalAnd:
andAgg := &AggLogicalAndAggregator{} andAgg := &AggLogicalAndAggregator{}
andAgg.res.Add(1) andAgg.res.Store(true)
return andAgg return andAgg
case RespAggLogicalOr: case RespAggLogicalOr:
@@ -466,7 +466,7 @@ func (a *AggMaxAggregator) Result() (interface{}, error) {
// AggLogicalAndAggregator performs logical AND on boolean values. // AggLogicalAndAggregator performs logical AND on boolean values.
type AggLogicalAndAggregator struct { type AggLogicalAndAggregator struct {
err atomic.Value err atomic.Value
res atomic.Int64 res atomic.Bool
hasResult atomic.Bool hasResult atomic.Bool
} }
@@ -482,10 +482,9 @@ func (a *AggLogicalAndAggregator) Add(result interface{}, err error) error {
return e return e
} }
if val { // Atomic AND operation: if val is false, result is always false
a.res.And(1) if !val {
} else { a.res.Store(false)
a.res.And(0)
} }
a.hasResult.Store(true) a.hasResult.Store(true)
@@ -544,13 +543,13 @@ func (a *AggLogicalAndAggregator) Result() (interface{}, error) {
if !a.hasResult.Load() { if !a.hasResult.Load() {
return nil, ErrAndAggregation return nil, ErrAndAggregation
} }
return a.res.Load() != 0, nil return a.res.Load(), nil
} }
// AggLogicalOrAggregator performs logical OR on boolean values. // AggLogicalOrAggregator performs logical OR on boolean values.
type AggLogicalOrAggregator struct { type AggLogicalOrAggregator struct {
err atomic.Value err atomic.Value
res atomic.Int64 res atomic.Bool
hasResult atomic.Bool hasResult atomic.Bool
} }
@@ -566,10 +565,9 @@ func (a *AggLogicalOrAggregator) Add(result interface{}, err error) error {
return e return e
} }
// Atomic OR operation: if val is true, result is always true
if val { if val {
a.res.Or(1) a.res.Store(true)
} else {
a.res.Or(0)
} }
a.hasResult.Store(true) a.hasResult.Store(true)
@@ -628,7 +626,7 @@ func (a *AggLogicalOrAggregator) Result() (interface{}, error) {
if !a.hasResult.Load() { if !a.hasResult.Load() {
return nil, ErrOrAggregation return nil, ErrOrAggregation
} }
return a.res.Load() != 0, nil return a.res.Load(), nil
} }
func toInt64(val interface{}) (int64, error) { func toInt64(val interface{}) (int64, error) {

View File

@@ -0,0 +1,271 @@
package routing
import (
"errors"
"testing"
)
func TestAggLogicalAndAggregator(t *testing.T) {
t.Run("all true values", func(t *testing.T) {
agg := NewResponseAggregator(RespAggLogicalAnd, "")
err := agg.Add(true, nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
err = agg.Add(int64(1), nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
err = agg.Add(1, nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
result, err := agg.Result()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result != true {
t.Errorf("expected true, got %v", result)
}
})
t.Run("one false value", func(t *testing.T) {
agg := NewResponseAggregator(RespAggLogicalAnd, "")
err := agg.Add(true, nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
err = agg.Add(false, nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
err = agg.Add(true, nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
result, err := agg.Result()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result != false {
t.Errorf("expected false, got %v", result)
}
})
t.Run("no results", func(t *testing.T) {
agg := NewResponseAggregator(RespAggLogicalAnd, "")
_, err := agg.Result()
if err != ErrAndAggregation {
t.Errorf("expected ErrAndAggregation, got %v", err)
}
})
t.Run("with error", func(t *testing.T) {
agg := NewResponseAggregator(RespAggLogicalAnd, "")
testErr := errors.New("test error")
err := agg.Add(nil, testErr)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
_, err = agg.Result()
if err != testErr {
t.Errorf("expected test error, got %v", err)
}
})
}
func TestAggLogicalOrAggregator(t *testing.T) {
t.Run("all false values", func(t *testing.T) {
agg := NewResponseAggregator(RespAggLogicalOr, "")
err := agg.Add(false, nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
err = agg.Add(int64(0), nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
err = agg.Add(0, nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
result, err := agg.Result()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result != false {
t.Errorf("expected false, got %v", result)
}
})
t.Run("one true value", func(t *testing.T) {
agg := NewResponseAggregator(RespAggLogicalOr, "")
err := agg.Add(false, nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
err = agg.Add(true, nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
err = agg.Add(false, nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
result, err := agg.Result()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result != true {
t.Errorf("expected true, got %v", result)
}
})
t.Run("no results", func(t *testing.T) {
agg := NewResponseAggregator(RespAggLogicalOr, "")
_, err := agg.Result()
if err != ErrOrAggregation {
t.Errorf("expected ErrOrAggregation, got %v", err)
}
})
t.Run("with error", func(t *testing.T) {
agg := NewResponseAggregator(RespAggLogicalOr, "")
testErr := errors.New("test error")
err := agg.Add(nil, testErr)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
_, err = agg.Result()
if err != testErr {
t.Errorf("expected test error, got %v", err)
}
})
}
func TestAggLogicalAndBatchAdd(t *testing.T) {
t.Run("batch add all true", func(t *testing.T) {
agg := NewResponseAggregator(RespAggLogicalAnd, "")
results := map[string]AggregatorResErr{
"key1": {Result: true, Err: nil},
"key2": {Result: int64(1), Err: nil},
"key3": {Result: 1, Err: nil},
}
err := agg.BatchAdd(results)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
result, err := agg.Result()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result != true {
t.Errorf("expected true, got %v", result)
}
})
t.Run("batch add with false", func(t *testing.T) {
agg := NewResponseAggregator(RespAggLogicalAnd, "")
results := map[string]AggregatorResErr{
"key1": {Result: true, Err: nil},
"key2": {Result: false, Err: nil},
"key3": {Result: true, Err: nil},
}
err := agg.BatchAdd(results)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
result, err := agg.Result()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result != false {
t.Errorf("expected false, got %v", result)
}
})
}
func TestAggLogicalOrBatchAdd(t *testing.T) {
t.Run("batch add all false", func(t *testing.T) {
agg := NewResponseAggregator(RespAggLogicalOr, "")
results := map[string]AggregatorResErr{
"key1": {Result: false, Err: nil},
"key2": {Result: int64(0), Err: nil},
"key3": {Result: 0, Err: nil},
}
err := agg.BatchAdd(results)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
result, err := agg.Result()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result != false {
t.Errorf("expected false, got %v", result)
}
})
t.Run("batch add with true", func(t *testing.T) {
agg := NewResponseAggregator(RespAggLogicalOr, "")
results := map[string]AggregatorResErr{
"key1": {Result: false, Err: nil},
"key2": {Result: true, Err: nil},
"key3": {Result: false, Err: nil},
}
err := agg.BatchAdd(results)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
result, err := agg.Result()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result != true {
t.Errorf("expected true, got %v", result)
}
})
}

View File

@@ -48,15 +48,15 @@ PACKAGE_DIRS=$(find . -mindepth 2 -type f -name 'go.mod' -exec dirname {} \; \
for dir in $PACKAGE_DIRS for dir in $PACKAGE_DIRS
do do
printf "${dir}: go get -u && go mod tidy\n" printf "${dir}: go get -u && go mod tidy\n"
#(cd ./${dir} && go get -u && go mod tidy -compat=1.18) #(cd ./${dir} && go get -u && go mod tidy -compat=1.21)
done done
for dir in $PACKAGE_DIRS for dir in $PACKAGE_DIRS
do do
sed --in-place \ sed --in-place \
"s/redis\/go-redis\([^ ]*\) v.*/redis\/go-redis\1 ${TAG}/" "${dir}/go.mod" "s/redis\/go-redis\([^ ]*\) v.*/redis\/go-redis\1 ${TAG}/" "${dir}/go.mod"
#(cd ./${dir} && go get -u && go mod tidy -compat=1.18) #(cd ./${dir} && go get -u && go mod tidy -compat=1.21)
(cd ./${dir} && go mod tidy -compat=1.18) (cd ./${dir} && go mod tidy -compat=1.21)
done done
sed --in-place "s/\(return \)\"[^\"]*\"/\1\"${TAG#v}\"/" ./version.go sed --in-place "s/\(return \)\"[^\"]*\"/\1\"${TAG#v}\"/" ./version.go