Compare commits
9 Commits
8b30e53a69
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 123cd38a18 | |||
| f9865168c9 | |||
| 08c1a170dc | |||
| 56848c73fd | |||
| dc05c68ee2 | |||
| e7a65571c8 | |||
| 14ff6977ab | |||
| 3628a7898b | |||
| 04298cad4e |
@@ -1,5 +1,5 @@
|
||||
[bumpversion]
|
||||
current_version = 0.1.2
|
||||
current_version = 1.0.0
|
||||
commit = True
|
||||
tag = True
|
||||
tag_name = {new_version}
|
||||
|
||||
@@ -15,7 +15,7 @@ steps:
|
||||
path: /var/run/docker.sock
|
||||
settings:
|
||||
dockerfile: Dockerfile
|
||||
tags: 0.1.2
|
||||
tags: 1.0.0
|
||||
force_tag: true
|
||||
registry: registry.halfakop.ru
|
||||
repo: registry.halfakop.ru/golang/bumpversion
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
*.run
|
||||
.env
|
||||
.env
|
||||
.cache
|
||||
|
||||
11
Dockerfile
11
Dockerfile
@@ -1,14 +1,17 @@
|
||||
FROM golang:1.24.1-alpine AS builder
|
||||
FROM golang:1.24-alpine AS builder
|
||||
|
||||
ARG SOURCE_VERSION
|
||||
ARG SOURCE_COMMIT
|
||||
WORKDIR /app
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
COPY . .
|
||||
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o bumpversion ./src
|
||||
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
|
||||
-ldflags "-X main.AppVersion=${SOURCE_VERSION} -X main.AppCommit=${SOURCE_COMMIT}" \
|
||||
-o bumpversion ./src
|
||||
|
||||
FROM gcr.io/distroless/static
|
||||
|
||||
WORKDIR /
|
||||
COPY --from=builder /app/bumpversion .
|
||||
COPY --from=builder /app/bumpversion /bumpversion
|
||||
ENTRYPOINT ["/bumpversion"]
|
||||
CMD ["--version"]
|
||||
|
||||
57
Makefile
57
Makefile
@@ -3,10 +3,13 @@ PACKAGE := bumpversion
|
||||
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)
|
||||
GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD | sed s,feature/,,g)
|
||||
LDFLAGS := -X main.AppVersion=$(SOURCE_VERSION) -X main.AppCommit=$(SOURCE_COMMIT)
|
||||
|
||||
IMAGE_NAME_TAGGED = $(REPOSITORY):$(SOURCE_VERSION)
|
||||
EXEC=$(PACKAGE).run
|
||||
@@ -14,34 +17,62 @@ EXEC=$(PACKAGE).run
|
||||
all: help
|
||||
|
||||
help:
|
||||
@echo "app - build the application"
|
||||
@echo "tests - run tests"
|
||||
@echo "run - run application locally"
|
||||
@echo "clean - clean build environment"
|
||||
@printf "\nMain make targets:\n"
|
||||
@printf " make app - Download deps, fix, build app (binary: %s)\n" "$(EXEC)"
|
||||
@printf " make test - Run unit tests\n"
|
||||
@printf " make test-integration - Run tests that require Postgres (testcontainers)\n"
|
||||
@printf " make run - Build then run locally\n"
|
||||
@printf " make clean - Clean build artifacts\n"
|
||||
@printf " make release - Clean, build image, login, push\n"
|
||||
@printf "\nVariables:\n"
|
||||
@printf " NAMESPACE=%s\n" "$(NAMESPACE)"
|
||||
@printf " PACKAGE=%s\n" "$(PACKAGE)"
|
||||
@printf " IMAGE_NAME_TAGGED=%s\n" "$(IMAGE_NAME_TAGGED)"
|
||||
@printf " EXEC=%s\n\n" "$(EXEC)"
|
||||
|
||||
download:
|
||||
@echo "Download dependencies"
|
||||
@GOCACHE=$(GOCACHE) go mod download
|
||||
|
||||
fix:
|
||||
@go fix ./...
|
||||
@echo "Fix code"
|
||||
@GOCACHE=$(GOCACHE) go fix ./...
|
||||
|
||||
app: fix
|
||||
@go build -o ./${EXEC} ./src
|
||||
app: download fix
|
||||
@echo "Build application"
|
||||
@GOCACHE=$(GOCACHE) go build -ldflags "$(LDFLAGS)" -o ./${EXEC} ./src
|
||||
|
||||
tests: build
|
||||
@go test ./...
|
||||
tests: app
|
||||
@echo "Run tests"
|
||||
@GOCACHE=$(GOCACHE) go test ./...
|
||||
|
||||
test:
|
||||
@echo "Run unit tests"
|
||||
@GOCACHE=$(GOCACHE) go test -count=1 ./...
|
||||
|
||||
test-integration:
|
||||
@echo "Run integration tests (requires Docker for Postgres)"
|
||||
@GOCACHE=$(GOCACHE) go test -tags=integration -count=1 ./...
|
||||
|
||||
run:
|
||||
@echo "Run application"
|
||||
@./${EXEC}
|
||||
|
||||
clean:
|
||||
@rm -rf ./${EXEC}%
|
||||
@echo "Clean build environment"
|
||||
@rm -rf ./${EXEC}% $(GOCACHE)
|
||||
|
||||
release: title clean build login push
|
||||
release: clean build login push
|
||||
|
||||
build:
|
||||
docker build --compress \
|
||||
DOCKER_BUILDKIT=0 \
|
||||
docker build $(PLATFORM) --progress=plain --compress \
|
||||
-t $(IMAGE_NAME_TAGGED) \
|
||||
-t $(REGISTRY)/$(IMAGE_NAME_TAGGED) \
|
||||
--build-arg SOURCE_VERSION=$(SOURCE_VERSION) \
|
||||
--build-arg SOURCE_COMMIT=$(SOURCE_COMMIT) \
|
||||
--build-arg GOPROXY=$(GOPROXY) \
|
||||
--build-arg GONOSUMDB=$(GONOSUMDB) \
|
||||
${DOCKER_OPTS} \
|
||||
-f Dockerfile .
|
||||
|
||||
@@ -54,4 +85,4 @@ login:
|
||||
push:
|
||||
docker push $(REGISTRY)/$(IMAGE_NAME_TAGGED)
|
||||
|
||||
.PHONY: tests release build login push
|
||||
.PHONY: tests test test-integration release build login push download fix app clean run help
|
||||
|
||||
19
README.md
19
README.md
@@ -1,4 +1,4 @@
|
||||
# BumpVersion v0.1.2
|
||||
# BumpVersion v1.0.0
|
||||
|
||||
[](https://drone.halfakop.ru/rad/bumpversion)
|
||||
|
||||
@@ -6,11 +6,18 @@
|
||||
|
||||
## Разработчику
|
||||
|
||||
export PATH=$PATH:/usr/local/go/bin
|
||||
go mod init src
|
||||
go mod tidy
|
||||
go build
|
||||
go run .
|
||||
```bash
|
||||
export PATH=$PATH:/usr/local/go/bin
|
||||
go mod init src
|
||||
go mod tidy
|
||||
go build
|
||||
go run .
|
||||
```
|
||||
или
|
||||
|
||||
```bash
|
||||
make
|
||||
```
|
||||
|
||||
## Девопсу
|
||||
|
||||
|
||||
33
src/build_info.go
Normal file
33
src/build_info.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package main
|
||||
|
||||
import "runtime/debug"
|
||||
|
||||
func init() {
|
||||
if AppCommit == "" || AppCommit == "unknown" {
|
||||
if commit := resolveCommitFromBuildInfo(); commit != "" {
|
||||
AppCommit = commit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func resolveCommitFromBuildInfo() string {
|
||||
info, ok := debug.ReadBuildInfo()
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
for _, setting := range info.Settings {
|
||||
if setting.Key == "vcs.revision" && setting.Value != "" {
|
||||
return shortCommit(setting.Value)
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func shortCommit(commit string) string {
|
||||
if len(commit) >= 8 {
|
||||
return commit[:8]
|
||||
}
|
||||
return commit
|
||||
}
|
||||
126
src/git.go
126
src/git.go
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
@@ -8,45 +9,122 @@ 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"
|
||||
)
|
||||
|
||||
// getenvDefault возвращает значение из окружения по ключу, или заданное
|
||||
// по умолчанию
|
||||
func getenvDefault(k, def string) string {
|
||||
if v := os.Getenv(k); v != "" {
|
||||
return v
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
// normalizePath возвращает нормализованный путь
|
||||
func normalizePath(p string) string {
|
||||
p = strings.TrimSpace(p)
|
||||
p = strings.TrimPrefix(p, "./")
|
||||
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) {
|
||||
func gitCommit(bc *BumpConfig, newVersion string, configPath string) {
|
||||
// Открываем локальный репозиторий (предполагается, что он существует в папке ".")
|
||||
repo, err := git.PlainOpen(".")
|
||||
if err != nil {
|
||||
log.Fatalf("Repository open error: %v", err)
|
||||
}
|
||||
|
||||
// Получаем рабочее дерево
|
||||
// получаем рабочее дерево
|
||||
worktree, err := repo.Worktree()
|
||||
if err != nil {
|
||||
log.Fatalf("Work directory open error: %v", err)
|
||||
}
|
||||
|
||||
// Добавляем все изменения в индекс (или конкретные файлы, если нужно)
|
||||
_, err = worktree.Add(".")
|
||||
// создаём список файлов, которые должны быть включены в коммит
|
||||
targets := make([]string, 0, len(bc.FilePaths)+1)
|
||||
targets = append(targets, bc.FilePaths...)
|
||||
targets = append(targets, configPath)
|
||||
|
||||
// проверяем на наличие изменений
|
||||
status, err := worktree.Status()
|
||||
if err != nil {
|
||||
log.Fatalf("Changes append error: %v", err)
|
||||
log.Fatalf("Status error: %v", err)
|
||||
}
|
||||
|
||||
// Формируем сообщение коммита
|
||||
commitMsg := strings.ReplaceAll(bc.Message, "{current_version}", bc.CurrentVersion)
|
||||
commitMsg = strings.ReplaceAll(commitMsg, "{new_version}", newVersion)
|
||||
commit, err := worktree.Commit(commitMsg, &git.CommitOptions{
|
||||
Author: &object.Signature{
|
||||
Name: os.Getenv("GIT_USERNAME"),
|
||||
Email: os.Getenv("GIT_EMAIL"),
|
||||
When: time.Now(),
|
||||
},
|
||||
changed := false
|
||||
for _, p := range targets {
|
||||
if p == "" {
|
||||
continue
|
||||
}
|
||||
p = normalizePath(p)
|
||||
|
||||
// пропускаем отсутствующие файлы тихо
|
||||
if _, err := os.Stat(p); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
log.Printf("Skip missing file: %s", p)
|
||||
continue
|
||||
}
|
||||
log.Fatalf("Stat error for %s: %v", p, err)
|
||||
}
|
||||
|
||||
st, ok := status[p]
|
||||
if !ok || st == nil {
|
||||
// nothing to stage for this file
|
||||
continue
|
||||
}
|
||||
|
||||
if st.Worktree != git.Unmodified {
|
||||
if _, err := worktree.Add(p); err != nil {
|
||||
log.Fatalf("Add %s error: %v", p, err)
|
||||
}
|
||||
changed = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if !changed {
|
||||
log.Printf("No changes detected in configured files; skipping commit.")
|
||||
return
|
||||
}
|
||||
|
||||
// формируем сообщение коммита
|
||||
commitMsg := strings.
|
||||
NewReplacer(
|
||||
"{current_version}", bc.CurrentVersion,
|
||||
"{new_version}", newVersion,
|
||||
).Replace(bc.Message)
|
||||
|
||||
author := signatureFromEnv()
|
||||
|
||||
hash, err := worktree.Commit(commitMsg, &git.CommitOptions{
|
||||
Author: author,
|
||||
Committer: author,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("Commit error: %v", err)
|
||||
}
|
||||
|
||||
// Получаем объект коммита (по его хэшу)
|
||||
commitObj, err := repo.CommitObject(commit)
|
||||
commitObj, err := repo.CommitObject(hash)
|
||||
if err != nil {
|
||||
log.Fatalf("Commit object error: %v", err)
|
||||
}
|
||||
@@ -73,11 +151,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 {
|
||||
@@ -95,12 +169,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,
|
||||
},
|
||||
})
|
||||
|
||||
74
src/main.go
74
src/main.go
@@ -27,7 +27,7 @@ type BumpConfig struct {
|
||||
func getBumpConfig(cfg_name string) (*BumpConfig, error) {
|
||||
cfg, err := ini.Load(cfg_name)
|
||||
if err != nil {
|
||||
log.Fatalf("Error loading config: %v", err)
|
||||
return nil, fmt.Errorf("error loading config: %w", err)
|
||||
}
|
||||
|
||||
sec, err := cfg.GetSection("bumpversion")
|
||||
@@ -70,6 +70,7 @@ func bumpVersion(bc *BumpConfig, part string) (string, error) {
|
||||
// Создадим карту групп по именам
|
||||
groupNames := re.SubexpNames()
|
||||
var major, minor, patch int
|
||||
var hasMajor, hasMinor, hasPatch bool
|
||||
for i, name := range groupNames {
|
||||
switch name {
|
||||
case "major":
|
||||
@@ -77,18 +78,24 @@ func bumpVersion(bc *BumpConfig, part string) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
hasMajor = true
|
||||
case "minor":
|
||||
minor, err = strconv.Atoi(matches[i])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
hasMinor = true
|
||||
case "patch":
|
||||
patch, err = strconv.Atoi(matches[i])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
hasPatch = true
|
||||
}
|
||||
}
|
||||
if !hasMajor || !hasMinor || !hasPatch {
|
||||
return "", fmt.Errorf("parse pattern must contain major, minor, and patch groups")
|
||||
}
|
||||
switch part {
|
||||
case "major":
|
||||
major++
|
||||
@@ -109,8 +116,8 @@ func bumpVersion(bc *BumpConfig, part string) (string, error) {
|
||||
return newVersion, nil
|
||||
}
|
||||
|
||||
// updateFiles обновляет версию в файле
|
||||
func updateFiles(filePaths []string, oldVersion, newVersion string) {
|
||||
// updateFiles обновляет версию в файле; при fatalIfMissing=true возвращает ошибку, если строка не найдена.
|
||||
func updateFiles(filePaths []string, oldVersion, newVersion string, fatalIfMissing bool) error {
|
||||
for _, path := range filePaths {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
@@ -118,12 +125,20 @@ func updateFiles(filePaths []string, oldVersion, newVersion string) {
|
||||
continue
|
||||
}
|
||||
newData := strings.ReplaceAll(string(data), oldVersion, newVersion)
|
||||
if newData == string(data) {
|
||||
log.Printf("Version %s not found in %s; skip update", oldVersion, path)
|
||||
if fatalIfMissing {
|
||||
return fmt.Errorf("version %s not found in %s", oldVersion, path)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err := os.WriteFile(path, []byte(newData), 0644); err != nil {
|
||||
log.Printf("Unable to write file %s: %v", path, err)
|
||||
} else {
|
||||
log.Printf("Updated file: %s", path)
|
||||
continue
|
||||
}
|
||||
log.Printf("Updated file: %s", path)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateConfigFile обновляет исходный конфигурационный файл
|
||||
@@ -143,39 +158,45 @@ func updateConfigFile(configPath string, newVersion string) error {
|
||||
// resolveFlag проверяет два логических флага: positive и negative.
|
||||
// Если оба заданы как true, вызывается ошибка; если задан negative, возвращается false;
|
||||
// если задан positive, возвращается true; иначе возвращается defaultValue.
|
||||
func resolveFlag(positive, negative *bool, defaultValue bool) bool {
|
||||
func resolveFlag(positive, negative *bool, defaultValue bool) (bool, error) {
|
||||
if *positive && *negative {
|
||||
// Если оба флага заданы, это противоречивое состояние.
|
||||
// Здесь можно завершить программу с ошибкой или выбрать приоритет.
|
||||
// Например, завершим выполнение:
|
||||
panic("conflicting flags: both positive and negative are set")
|
||||
return false, fmt.Errorf("conflicting flags: both positive and negative are set")
|
||||
}
|
||||
if *negative {
|
||||
return false
|
||||
return false, nil
|
||||
}
|
||||
if *positive {
|
||||
return true
|
||||
return true, nil
|
||||
}
|
||||
return defaultValue
|
||||
return defaultValue, nil
|
||||
}
|
||||
|
||||
// Версия приложения
|
||||
const (
|
||||
AppName = "BumpVersion"
|
||||
AppVersion = "0.1.2"
|
||||
const AppName = "BumpVersion"
|
||||
|
||||
var (
|
||||
AppVersion = "1.0.0"
|
||||
AppCommit = "unknown"
|
||||
)
|
||||
|
||||
func versionString() string {
|
||||
if AppCommit == "" || AppCommit == "unknown" {
|
||||
return AppVersion
|
||||
}
|
||||
return fmt.Sprintf("%s (%s)", AppVersion, AppCommit)
|
||||
}
|
||||
|
||||
func main() {
|
||||
const cfg_name = ".bumpversion.cfg"
|
||||
args := os.Args[1:]
|
||||
// Проверяем аргументы командной строки
|
||||
if len(args) > 0 && strings.ToLower(args[0]) == "--version" {
|
||||
fmt.Printf("%s version %s\n", AppName, AppVersion)
|
||||
fmt.Printf("%s version %s\n", AppName, versionString())
|
||||
return
|
||||
}
|
||||
|
||||
// Печатаем название и версию при старте
|
||||
fmt.Printf("Starting %s version %s\n", AppName, AppVersion)
|
||||
fmt.Printf("Starting %s version %s\n", AppName, versionString())
|
||||
|
||||
bc, err := getBumpConfig(cfg_name)
|
||||
if err != nil {
|
||||
@@ -188,6 +209,7 @@ func main() {
|
||||
noCommit := flag.Bool("no-commit", false, "Do not create a commit")
|
||||
tag := flag.Bool("tag", false, "Add a git tag")
|
||||
noTag := flag.Bool("no-tag", false, "Do not add a git tag")
|
||||
noFatal := flag.Bool("no-fatal", false, "Do not fail if a configured file does not contain the current version")
|
||||
push := flag.Bool("push", false, "Force push to repository")
|
||||
flag.Parse()
|
||||
|
||||
@@ -198,8 +220,14 @@ func main() {
|
||||
}
|
||||
|
||||
// Разрешаем флаги:
|
||||
shouldCommit := resolveFlag(commit, noCommit, bc.Commit)
|
||||
shouldTag := resolveFlag(tag, noTag, bc.Tag)
|
||||
shouldCommit, err := resolveFlag(commit, noCommit, bc.Commit)
|
||||
if err != nil {
|
||||
log.Fatalf("Error resolving commit flags: %v", err)
|
||||
}
|
||||
shouldTag, err := resolveFlag(tag, noTag, bc.Tag)
|
||||
if err != nil {
|
||||
log.Fatalf("Error resolving tag flags: %v", err)
|
||||
}
|
||||
|
||||
newVersion, err := bumpVersion(bc, *part)
|
||||
if err != nil {
|
||||
@@ -209,7 +237,9 @@ func main() {
|
||||
fmt.Printf("New version: %s\n", newVersion)
|
||||
|
||||
// Обновляем файлы, указанные в конфигурации
|
||||
updateFiles(bc.FilePaths, bc.CurrentVersion, newVersion)
|
||||
if err := updateFiles(bc.FilePaths, bc.CurrentVersion, newVersion, !*noFatal); err != nil {
|
||||
log.Fatalf("Error updating files: %v", err)
|
||||
}
|
||||
|
||||
// Обновляем конфигурационный файл
|
||||
if err := updateConfigFile(cfg_name, newVersion); err != nil {
|
||||
@@ -220,7 +250,7 @@ func main() {
|
||||
|
||||
// Выполняем git commit и tag, если требуется
|
||||
if shouldCommit {
|
||||
gitCommit(bc, newVersion)
|
||||
gitCommit(bc, newVersion, cfg_name)
|
||||
}
|
||||
|
||||
// Выполняем git commit и tag, если требуется
|
||||
|
||||
292
src/main_test.go
Normal file
292
src/main_test.go
Normal file
@@ -0,0 +1,292 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/go-git/go-git/v5/config"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
)
|
||||
|
||||
func TestGetBumpConfig(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
cfgPath := filepath.Join(tmpDir, ".bumpversion.cfg")
|
||||
cfg := `
|
||||
[bumpversion]
|
||||
current_version = 1.2.3
|
||||
commit = true
|
||||
tag = true
|
||||
tag_name = v{new_version}
|
||||
parse = ^(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)$
|
||||
serialize = {major}.{minor}.{patch}
|
||||
message = Release {new_version}
|
||||
|
||||
[bumpversion:file:VERSION]
|
||||
[bumpversion:file:README.md]
|
||||
`
|
||||
if err := os.WriteFile(cfgPath, []byte(cfg), 0o644); err != nil {
|
||||
t.Fatalf("write config: %v", err)
|
||||
}
|
||||
|
||||
got, err := getBumpConfig(cfgPath)
|
||||
if err != nil {
|
||||
t.Fatalf("getBumpConfig returned error: %v", err)
|
||||
}
|
||||
|
||||
if got.CurrentVersion != "1.2.3" {
|
||||
t.Fatalf("CurrentVersion = %q, want 1.2.3", got.CurrentVersion)
|
||||
}
|
||||
if !got.Commit || !got.Tag {
|
||||
t.Fatalf("expected commit and tag to be true, got commit=%v tag=%v", got.Commit, got.Tag)
|
||||
}
|
||||
if got.TagName != "v{new_version}" {
|
||||
t.Fatalf("TagName = %q, want v{new_version}", got.TagName)
|
||||
}
|
||||
if strings.TrimSpace(got.Serialize) != "{major}.{minor}.{patch}" {
|
||||
t.Fatalf("Serialize = %q, want {major}.{minor}.{patch}", got.Serialize)
|
||||
}
|
||||
if got.Parse != `^(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)$` {
|
||||
t.Fatalf("Parse = %q, want regex string", got.Parse)
|
||||
}
|
||||
|
||||
wantPaths := map[string]bool{
|
||||
"VERSION": true,
|
||||
"README.md": true,
|
||||
}
|
||||
for _, p := range got.FilePaths {
|
||||
delete(wantPaths, p)
|
||||
}
|
||||
if len(wantPaths) != 0 {
|
||||
t.Fatalf("FilePaths missing entries: %v", reflect.ValueOf(wantPaths).MapKeys())
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateConfigFile(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
cfgPath := filepath.Join(tmpDir, ".bumpversion.cfg")
|
||||
cfg := `
|
||||
[bumpversion]
|
||||
current_version = 0.0.1
|
||||
commit = false
|
||||
`
|
||||
if err := os.WriteFile(cfgPath, []byte(cfg), 0o644); err != nil {
|
||||
t.Fatalf("write config: %v", err)
|
||||
}
|
||||
|
||||
if err := updateConfigFile(cfgPath, "0.0.2"); err != nil {
|
||||
t.Fatalf("updateConfigFile returned error: %v", err)
|
||||
}
|
||||
|
||||
updated, err := getBumpConfig(cfgPath)
|
||||
if err != nil {
|
||||
t.Fatalf("getBumpConfig returned error: %v", err)
|
||||
}
|
||||
if updated.CurrentVersion != "0.0.2" {
|
||||
t.Fatalf("CurrentVersion = %q, want 0.0.2", updated.CurrentVersion)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBumpVersion(t *testing.T) {
|
||||
bc := &BumpConfig{
|
||||
CurrentVersion: "1.2.3",
|
||||
Parse: `^v?(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)$`,
|
||||
Serialize: "{major}.{minor}.{patch}",
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
part string
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{name: "major", part: "major", want: "2.0.0"},
|
||||
{name: "minor", part: "minor", want: "1.3.0"},
|
||||
{name: "patch", part: "patch", want: "1.2.4"},
|
||||
{name: "unknown", part: "build", wantErr: true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := bumpVersion(bc, tt.part)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Fatalf("bumpVersion error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
if err == nil && got != tt.want {
|
||||
t.Fatalf("bumpVersion = %q, want %q", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBumpVersionInvalidCurrent(t *testing.T) {
|
||||
bc := &BumpConfig{
|
||||
CurrentVersion: "1.2",
|
||||
Parse: `^v?(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)$`,
|
||||
Serialize: "{major}.{minor}.{patch}",
|
||||
}
|
||||
if _, err := bumpVersion(bc, "patch"); err == nil {
|
||||
t.Fatalf("expected error for invalid current_version")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBumpVersionMissingGroups(t *testing.T) {
|
||||
bc := &BumpConfig{
|
||||
CurrentVersion: "1.2.3",
|
||||
Parse: `^(?P<major>\d+)\.(?P<minor>\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"
|
||||
newV := "1.2.4"
|
||||
|
||||
filePaths := []string{
|
||||
filepath.Join(tmpDir, "VERSION"),
|
||||
filepath.Join(tmpDir, "README.md"),
|
||||
}
|
||||
|
||||
for _, p := range filePaths {
|
||||
contents := "project version " + oldV + "\n"
|
||||
if err := os.WriteFile(p, []byte(contents), 0o644); err != nil {
|
||||
t.Fatalf("write %s: %v", p, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := updateFiles(filePaths, oldV, newV, true); err != nil {
|
||||
t.Fatalf("updateFiles returned error: %v", err)
|
||||
}
|
||||
|
||||
for _, p := range filePaths {
|
||||
data, err := os.ReadFile(p)
|
||||
if err != nil {
|
||||
t.Fatalf("read %s: %v", p, err)
|
||||
}
|
||||
if !strings.Contains(string(data), newV) || strings.Contains(string(data), oldV) {
|
||||
t.Fatalf("%s not updated correctly: %s", p, string(data))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateFilesMissingVersionFatal(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
oldV := "1.2.3"
|
||||
newV := "1.2.4"
|
||||
|
||||
target := filepath.Join(tmpDir, "README.md")
|
||||
if err := os.WriteFile(target, []byte("no version here\n"), 0o644); err != nil {
|
||||
t.Fatalf("write: %v", err)
|
||||
}
|
||||
|
||||
if err := updateFiles([]string{target}, oldV, newV, true); err == nil {
|
||||
t.Fatalf("expected error when version is missing")
|
||||
}
|
||||
|
||||
if err := updateFiles([]string{target}, oldV, newV, false); err != nil {
|
||||
t.Fatalf("did not expect error when no-fatal is set: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveFlag(t *testing.T) {
|
||||
boolPtr := func(b bool) *bool { return &b }
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
positive *bool
|
||||
negative *bool
|
||||
defaultValue bool
|
||||
want bool
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "positive wins",
|
||||
positive: boolPtr(true),
|
||||
negative: boolPtr(false),
|
||||
defaultValue: false,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "negative wins",
|
||||
positive: boolPtr(false),
|
||||
negative: boolPtr(true),
|
||||
defaultValue: true,
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "default used",
|
||||
positive: boolPtr(false),
|
||||
negative: boolPtr(false),
|
||||
defaultValue: true,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "error on conflict",
|
||||
positive: boolPtr(true),
|
||||
negative: boolPtr(true),
|
||||
defaultValue: false,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
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 {
|
||||
t.Fatalf("resolveFlag = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user