Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@ RUN go mod download
ADD ./ /code/
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o /tmp/bot .

FROM alpine:3.6
FROM alpine:3.21
EXPOSE 8080 443
WORKDIR /root/
RUN apk --no-cache --update add bash curl less jq openssl git
COPY --from=builder /tmp/bot /root/
# HEALTHCHECK --interval=10s --timeout=3s \
# CMD curl -f http://localhost:8080/health || exit 1

ENV SENTRY_RELEASE=2.2.0 SENTRY_ENVIRONMENT=container

CMD exec /root/bot
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ Usage of merge-bot:
which domain is used for ssl certificate (also via TLS_DOMAIN)
-tls-enabled
whether tls enabled or not, bot will use Letsencrypt, default is false (also via TLS_ENABLED)
-sentry-enabled
whether sentry enabled or not, default is true (also via SENTRY_ENABLED)
```

Run bot
Expand Down
21 changes: 13 additions & 8 deletions bot.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
package main

import (
"log/slog"
"mergebot/config"
"mergebot/handlers"
"mergebot/logger"
"mergebot/webhook"
"os"
"path"
"sync"

"net/http"

sentryecho "github.com/getsentry/sentry-go/echo"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"golang.org/x/crypto/acme/autocert"
Expand All @@ -32,6 +33,10 @@ func start() {
e.Use(middleware.Logger())
e.Use(middleware.Recover())

if logger.IsSentryEnabled() {
e.Use(sentryecho.New(sentryecho.Options{Repanic: true, WaitForDelivery: false}))
}

e.GET("/healthy", healthcheck)
e.POST("/mergebot/webhook/:provider/:owner/:repo/", Handler)

Expand Down Expand Up @@ -63,16 +68,16 @@ func Handler(c echo.Context) error {
providerName := c.Param("provider")
hook, err := webhook.New(providerName)
if err != nil {
slog.Error("webhook", "err", err)
logger.Error("webhook", "err", err)
return err
}

if err = hook.ParseRequest(c.Request()); err != nil {
slog.Error("ParseRequest", "err", err)
logger.Error("ParseRequest", "err", err)
return err
}

slog.Debug("handler", "event", hook.Event)
logger.Debug("handler", "event", hook.Event)

handlerMu.RLock()
defer handlerMu.RUnlock()
Expand All @@ -81,22 +86,22 @@ func Handler(c echo.Context) error {
go func() {
command, err := handlers.New(providerName)
if err != nil {
slog.Error("can't initialize provider", "provider", providerName, "event", hook.Event, "err", err)
logger.Error("can't initialize provider", "provider", providerName, "event", hook.Event, "err", err)
return
}

// if err := command.LoadInfoAndConfig(hook.GetProjectID(), hook.GetID()); err != nil {
// slog.Error("can't load repo config", "provider", providerName, "command", command, "err", err)
// logger.Error("can't load repo config", "provider", providerName, "command", command, "err", err)
// return
// }

if !command.ValidateSecret(hook.GetProjectID(), hook.GetSecret()) {
slog.Error("webhook secret is not valid", "projectId", hook.GetProjectID(), "provider", providerName)
logger.Error("webhook secret is not valid", "projectId", hook.GetProjectID(), "provider", providerName)
return
}

if err := f(command, hook); err != nil {
slog.Error("handlerFunc returns err", "provider", providerName, "event", hook.Event, "err", err)
logger.Error("handlerFunc returns err", "provider", providerName, "event", hook.Event, "err", err)
return
}
}()
Expand Down
4 changes: 2 additions & 2 deletions commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package main

import (
"fmt"
"log/slog"
"mergebot/handlers"
"mergebot/logger"
"mergebot/webhook"
)

Expand All @@ -17,7 +17,7 @@ func init() {

func UpdateBranchCmd(command *handlers.Request, hook *webhook.Webhook) error {
if err := command.UpdateFromMaster(hook.GetProjectID(), hook.GetID()); err != nil {
slog.Error("command.UpdateFromMaster failed", "error", err)
logger.Error("command.UpdateFromMaster failed", "error", err)
return command.LeaveComment(hook.GetProjectID(), hook.GetID(), "❌ i couldn't update branch from master")
}

Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ services:
# github registry
# image: ghcr.io/gasoid/merge-bot:latest
# dockerhub registry
image: gasoid/merge-bot:latest
image: gasoid/merge-bot:2.2.0
restart: always
volumes:
- tls-cache:/tmp/tls/.cache
Expand Down
12 changes: 7 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ module mergebot
go 1.24.1

require (
github.com/getsentry/sentry-go v0.33.0
github.com/getsentry/sentry-go/echo v0.33.0
github.com/getsentry/sentry-go/slog v0.33.0
github.com/labstack/echo/v4 v4.12.0
github.com/ldez/go-git-cmd-wrapper/v2 v2.7.0
github.com/peterbourgon/ff/v3 v3.4.0
github.com/stretchr/testify v1.9.0
github.com/xanzy/go-gitlab v0.93.2
golang.org/x/crypto v0.22.0
golang.org/x/crypto v0.31.0
gopkg.in/yaml.v3 v3.0.1
)

Expand All @@ -28,12 +31,11 @@ require (
github.com/rogpeppe/go-internal v1.11.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
golang.org/x/net v0.24.0 // indirect
golang.org/x/net v0.33.0 // indirect
golang.org/x/oauth2 v0.6.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/time v0.5.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.29.1 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
)
33 changes: 22 additions & 11 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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/getsentry/sentry-go v0.33.0 h1:YWyDii0KGVov3xOaamOnF0mjOrqSjBqwv48UEzn7QFg=
github.com/getsentry/sentry-go v0.33.0/go.mod h1:C55omcY9ChRQIUcVcGcs+Zdy4ZpQGvNJ7JYHIoSWOtE=
github.com/getsentry/sentry-go/echo v0.33.0 h1:wizehrPbzUUooTLcxB0XUnlAoP7reSy4GQvH4zdY+xA=
github.com/getsentry/sentry-go/echo v0.33.0/go.mod h1:Vl3SWPlBrTMvQYY8LVHVr7jjTCQLtQ96yVS6P2AA4Bc=
github.com/getsentry/sentry-go/slog v0.33.0 h1:/00kTrdJ5InG3VU1l1uXXTK0ivCiY1MWLTYNFPCqK4s=
github.com/getsentry/sentry-go/slog v0.33.0/go.mod h1:Y+LOL05bbKhfiR8dT7zsa2ulsKAnNhSxDVB89TcXC0o=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
Expand All @@ -19,11 +27,8 @@ github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxC
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-retryablehttp v0.7.2 h1:AcYqCvkpalPnPF2pn0KamgwamS42TqUDDYFRKq/RAd0=
github.com/hashicorp/go-retryablehttp v0.7.2/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
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/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0=
Expand All @@ -39,7 +44,11 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/peterbourgon/ff/v3 v3.4.0 h1:QBvM/rizZM1cB0p0lGMdmR7HxZeI/ZrBWB4DqLkMUBc=
github.com/peterbourgon/ff/v3 v3.4.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
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/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
Expand All @@ -54,23 +63,25 @@ github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQ
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/xanzy/go-gitlab v0.93.2 h1:kNNf3BYNYn/Zkig0B89fma12l36VLcYSGu7OnaRlRDg=
github.com/xanzy/go-gitlab v0.93.2/go.mod h1:5ryv+MnpZStBH8I/77HuQBsMbBGANtVpLWC15qOjWAw=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw=
golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
Expand Down
9 changes: 5 additions & 4 deletions handlers/gitlab/gitlab.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"log/slog"
"mergebot/config"
"mergebot/handlers"
"mergebot/logger"
"net/http"

"github.com/xanzy/go-gitlab"
Expand Down Expand Up @@ -74,7 +75,7 @@ func (g *GitlabProvider) UpdateFromMaster(projectId, mergeId int) error {
}

func (g *GitlabProvider) LeaveComment(projectId, mergeId int, message string) error {
slog.Debug("leaveComment in gitlab", "message", message, "projectId", projectId)
logger.Debug("leaveComment in gitlab", "message", message, "projectId", projectId)

_, _, err := g.client.Notes.CreateMergeRequestNote(
projectId,
Expand Down Expand Up @@ -205,7 +206,7 @@ func (g *GitlabProvider) GetVar(projectId int, varName string) (string, error) {
secretVar, resp, err := g.client.ProjectVariables.GetVariable(projectId, varName, &gitlab.GetProjectVariableOptions{})
if err != nil {
if resp.StatusCode == http.StatusNotFound {
slog.Debug("variable not found", "varName", varName, "projectId", projectId)
logger.Debug("variable not found", "varName", varName, "projectId", projectId)
return "", nil
}

Expand Down Expand Up @@ -243,7 +244,7 @@ func New() handlers.RequestProvider {

token := gitlabToken
if token == "" {
slog.Error("gitlab init", "err", "gitlab requires token, please set env variable GITLAB_TOKEN")
logger.Error("gitlab init", "err", "gitlab requires token, please set env variable GITLAB_TOKEN")
return nil
}

Expand All @@ -255,7 +256,7 @@ func New() handlers.RequestProvider {
p.client, err = gitlab.NewClient(token)
}
if err != nil {
slog.Error("gitlabProvider new", "err", err)
logger.Error("gitlabProvider new", "err", err)
return nil
}

Expand Down
20 changes: 10 additions & 10 deletions handlers/merge.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package handlers

import (
"fmt"
"log/slog"
"mergebot/logger"
"net/url"
"os"

Expand Down Expand Up @@ -31,47 +31,47 @@ func MergeMaster(username, password, repoUrl, branchName, master string) error {

dir, err := os.MkdirTemp("", "merge-bot")
if err != nil {
slog.Debug("temp dir error")
logger.Debug("temp dir error")
return err
}

defer os.RemoveAll(dir)

if _, err := git.Clone(clone.Repository(repoUrl), clone.Directory(dir)); err != nil {
slog.Debug("git clone error", "dir", dir)
logger.Debug("git clone error", "dir", dir)
return err
}

if err := os.Chdir(dir); err != nil {
slog.Debug("chdir error")
logger.Debug("chdir error")
return err
}

if _, err := git.Config(config.Entry("user.email", fmt.Sprintf("%s@localhost", username))); err != nil {
slog.Debug("git config error", "user.email", fmt.Sprintf("%s@localhost", username))
logger.Debug("git config error", "user.email", fmt.Sprintf("%s@localhost", username))
return err
}

if _, err := git.Config(config.Entry("user.name", username)); err != nil {
slog.Debug("git config error", "user.name", username)
logger.Debug("git config error", "user.name", username)
return err
}

if _, err := git.Checkout(checkout.Branch(branchName)); err != nil {
slog.Debug("git checkout error", "branch", branchName)
logger.Debug("git checkout error", "branch", branchName)
return err
}

if _, err := git.Merge(merge.Commits(master), merge.M("update from master")); err != nil {
slog.Debug("git merge error")
logger.Debug("git merge error")
if _, err := git.Merge(merge.NoFf, merge.Commits(master), merge.M("update from master")); err != nil {
slog.Debug("git merge --noff error")
logger.Debug("git merge --noff error")
return err
}
}

if _, err := git.Push(push.Remote(defaultRemote), push.RefSpec(branchName)); err != nil {
slog.Debug("git push error")
logger.Debug("git push error")
return err
}

Expand Down
4 changes: 2 additions & 2 deletions handlers/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"bytes"
"fmt"
"html/template"
"log/slog"
"mergebot/logger"
"strings"

"gopkg.in/yaml.v3"
Expand Down Expand Up @@ -164,7 +164,7 @@ func (r Request) ValidateSecret(projectId int, secret string) bool {

secretVar, err := r.provider.GetVar(projectId, mergeBotSecret)
if err != nil {
slog.Error("cound't validate secret", "err", err)
logger.Error("cound't validate secret", "err", err)

return false
}
Expand Down
6 changes: 3 additions & 3 deletions handlers/stalebranches.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package handlers

import (
"fmt"
"log/slog"
"mergebot/logger"
"time"
)

Expand All @@ -12,7 +12,7 @@ type Branch struct {
}

func (r *Request) cleanStaleBranches(projectId int) error {
slog.Debug("deletion of stale branches has been run")
logger.Debug("deletion of stale branches has been run")

candidates, err := r.provider.ListBranches(projectId)
if err != nil {
Expand All @@ -26,7 +26,7 @@ func (r *Request) cleanStaleBranches(projectId int) error {
if span > time.Duration(time.Duration(days)*24*time.Hour) {
// branch is stale
// delete branch
slog.Debug("branch info", "name", b.Name, "createdAt", b.LastUpdated.String())
logger.Debug("branch info", "name", b.Name, "createdAt", b.LastUpdated.String())
if err := r.provider.DeleteBranch(projectId, b.Name); err != nil {
return fmt.Errorf("DeleteBranch returns error: %w", err)
}
Expand Down
Loading
Loading