diff --git a/Makefile b/Makefile index 5bc6bfa..eb31f14 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,7 @@ SHELL := /bin/bash REGISTRY := registry.halfakop.ru REPOSITORY := $(NAMESPACE)/$(PACKAGE) PLATFORM ?= --platform=linux/amd64 +GOCACHE ?= $(CURDIR)/.cache/go-build SOURCE_VERSION ?= $(shell cat VERSION) SOURCE_COMMIT ?= $(shell git rev-parse --short=8 HEAD) @@ -31,27 +32,27 @@ help: download: @echo "Download dependencies" - @go mod download + @GOCACHE=$(GOCACHE) go mod download fix: @echo "Fix code" - @go fix ./... + @GOCACHE=$(GOCACHE) go fix ./... app: download fix @echo "Build application" - @go build -ldflags "$(LDFLAGS)" -o ./${EXEC} ./src + @GOCACHE=$(GOCACHE) go build -ldflags "$(LDFLAGS)" -o ./${EXEC} ./src tests: app @echo "Run tests" - @go test ./... + @GOCACHE=$(GOCACHE) go test ./... test: @echo "Run unit tests" - @go test -count=1 ./... + @GOCACHE=$(GOCACHE) go test -count=1 ./... test-integration: @echo "Run integration tests (requires Docker for Postgres)" - @go test -tags=integration -count=1 ./... + @GOCACHE=$(GOCACHE) go test -tags=integration -count=1 ./... run: @echo "Run application" @@ -59,7 +60,7 @@ run: clean: @echo "Clean build environment" - @rm -rf ./${EXEC}% + @rm -rf ./${EXEC}% $(GOCACHE) release: clean build login push diff --git a/src/git.go b/src/git.go index 554789d..9a32a61 100644 --- a/src/git.go +++ b/src/git.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "log" "os" "strings" @@ -8,6 +9,7 @@ import ( git "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/config" + "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/object" ) @@ -27,6 +29,22 @@ func normalizePath(p string) string { return p } +func pushRefSpec(headRef *plumbing.Reference) (config.RefSpec, error) { + if headRef == nil || !headRef.Name().IsBranch() { + return "", fmt.Errorf("cannot determine branch to push from HEAD") + } + branch := headRef.Name().String() + return config.RefSpec(branch + ":" + branch), nil +} + +func signatureFromEnv() *object.Signature { + return &object.Signature{ + Name: getenvDefault("GIT_USERNAME", "bumpversion"), + Email: getenvDefault("GIT_EMAIL", "bumpversion@deploy"), + When: time.Now().UTC(), + } +} + // gitCommit выполняет коммит с внесёнными изменениями func gitCommit(bc *BumpConfig, newVersion string, configPath string) { // Открываем локальный репозиторий (предполагается, что он существует в папке ".") @@ -90,11 +108,7 @@ func gitCommit(bc *BumpConfig, newVersion string, configPath string) { "{new_version}", newVersion, ).Replace(bc.Message) - author := &object.Signature{ - Name: getenvDefault("GIT_USERNAME", "bumpversion"), - Email: getenvDefault("GIT_EMAIL", "bumpversion@deploy"), - When: time.Now().UTC(), - } + author := signatureFromEnv() hash, err := worktree.Commit(commitMsg, &git.CommitOptions{ Author: author, @@ -132,11 +146,7 @@ func gitTag(bc *BumpConfig, newVersion string) { commitMsg = strings.ReplaceAll(commitMsg, "{new_version}", newVersion) tagName := strings.ReplaceAll(bc.TagName, "{new_version}", newVersion) _, err = repo.CreateTag(tagName, headRef.Hash(), &git.CreateTagOptions{ - Tagger: &object.Signature{ - Name: os.Getenv("GIT_USERNAME"), - Email: os.Getenv("GIT_EMAIL"), - When: time.Now(), - }, + Tagger: signatureFromEnv(), Message: commitMsg, }) if err != nil { @@ -154,12 +164,22 @@ func gitPush(bc *BumpConfig, newVersion string) { tagName := strings.ReplaceAll(bc.TagName, "{new_version}", newVersion) + headRef, err := repo.Head() + if err != nil { + log.Fatalf("HEAD open error: %v", err) + } + + branchSpec, err := pushRefSpec(headRef) + if err != nil { + log.Fatalf("Push branch detection error: %v", err) + } + // (Опционально) Выполняем push на удаленный репозиторий tagSpec := config.RefSpec("refs/tags/" + tagName + ":refs/tags/" + tagName) err = repo.Push(&git.PushOptions{ RemoteName: "origin", RefSpecs: []config.RefSpec{ - "refs/heads/master:refs/heads/master", + branchSpec, tagSpec, }, }) diff --git a/src/main_test.go b/src/main_test.go index 9dfe66f..1b357ee 100644 --- a/src/main_test.go +++ b/src/main_test.go @@ -6,6 +6,9 @@ import ( "reflect" "strings" "testing" + + "github.com/go-git/go-git/v5/config" + "github.com/go-git/go-git/v5/plumbing" ) func TestGetBumpConfig(t *testing.T) { @@ -129,6 +132,57 @@ func TestBumpVersionInvalidCurrent(t *testing.T) { } } +func TestBumpVersionMissingGroups(t *testing.T) { + bc := &BumpConfig{ + CurrentVersion: "1.2.3", + Parse: `^(?P\d+)\.(?P\d+)$`, + Serialize: "{major}.{minor}.{patch}", + } + if _, err := bumpVersion(bc, "patch"); err == nil { + t.Fatalf("expected error when parse pattern misses patch group") + } +} + +func TestGetBumpConfigMissingFile(t *testing.T) { + tmpDir := t.TempDir() + if _, err := getBumpConfig(filepath.Join(tmpDir, "missing.cfg")); err == nil { + t.Fatalf("expected error for missing config file") + } +} + +func TestSignatureFromEnvDefaults(t *testing.T) { + prevUser := os.Getenv("GIT_USERNAME") + prevEmail := os.Getenv("GIT_EMAIL") + t.Cleanup(func() { + _ = os.Setenv("GIT_USERNAME", prevUser) + _ = os.Setenv("GIT_EMAIL", prevEmail) + }) + _ = os.Unsetenv("GIT_USERNAME") + _ = os.Unsetenv("GIT_EMAIL") + + sig := signatureFromEnv() + if sig.Name != "bumpversion" || sig.Email != "bumpversion@deploy" { + t.Fatalf("signatureFromEnv defaults = %s %s, want bumpversion bumpversion@deploy", sig.Name, sig.Email) + } +} + +func TestPushRefSpec(t *testing.T) { + head := plumbing.NewHashReference(plumbing.NewBranchReferenceName("main"), plumbing.ZeroHash) + spec, err := pushRefSpec(head) + if err != nil { + t.Fatalf("pushRefSpec returned error: %v", err) + } + want := config.RefSpec("refs/heads/main:refs/heads/main") + if spec != want { + t.Fatalf("pushRefSpec = %q, want %q", spec, want) + } + + _, err = pushRefSpec(plumbing.NewHashReference(plumbing.HEAD, plumbing.ZeroHash)) + if err == nil { + t.Fatalf("expected error for non-branch HEAD") + } +} + func TestUpdateFiles(t *testing.T) { tmpDir := t.TempDir() oldV := "1.2.3" @@ -168,7 +222,7 @@ func TestResolveFlag(t *testing.T) { negative *bool defaultValue bool want bool - wantPanic bool + wantErr bool }{ { name: "positive wins", @@ -192,26 +246,21 @@ func TestResolveFlag(t *testing.T) { want: true, }, { - name: "panic on conflict", + name: "error on conflict", positive: boolPtr(true), negative: boolPtr(true), defaultValue: false, - wantPanic: true, + wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - defer func() { - if r := recover(); tt.wantPanic && r == nil { - t.Fatalf("expected panic but function returned") - } else if !tt.wantPanic && r != nil { - t.Fatalf("unexpected panic: %v", r) - } - }() - - got := resolveFlag(tt.positive, tt.negative, tt.defaultValue) - if tt.wantPanic { + got, err := resolveFlag(tt.positive, tt.negative, tt.defaultValue) + if (err != nil) != tt.wantErr { + t.Fatalf("resolveFlag error = %v, wantErr %v", err, tt.wantErr) + } + if tt.wantErr { return } if got != tt.want {