Files
bumpversion/src/git.go
Ruslan Popov 123cd38a18
All checks were successful
continuous-integration/drone/push Build is passing
Add fatal missing-version handling with opt-out flag
2026-01-01 13:11:33 +03:00

196 lines
5.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package main
import (
"fmt"
"log"
"os"
"strings"
"time"
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, 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)
}
// создаём список файлов, которые должны быть включены в коммит
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("Status error: %v", err)
}
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(hash)
if err != nil {
log.Fatalf("Commit object error: %v", err)
}
log.Printf("Committed as %s\n", commitObj.Hash)
}
// gitTag ставит тэг на текущий коммит
func gitTag(bc *BumpConfig, newVersion string) {
// Открываем локальный репозиторий (предполагается, что он существует в папке ".")
repo, err := git.PlainOpen(".")
if err != nil {
log.Fatalf("Repository open error: %v", err)
}
// Получаем текущий HEAD (он должен совпадать с только что созданным коммитом)
headRef, err := repo.Head()
if err != nil {
log.Fatalf("HEAD open error: %v", err)
}
log.Printf("Current HEAD is %s\n", headRef.Hash())
// Создаем тег на текущем коммите (HEAD)
commitMsg := strings.ReplaceAll(bc.Message, "{current_version}", bc.CurrentVersion)
commitMsg = strings.ReplaceAll(commitMsg, "{new_version}", newVersion)
tagName := strings.ReplaceAll(bc.TagName, "{new_version}", newVersion)
_, err = repo.CreateTag(tagName, headRef.Hash(), &git.CreateTagOptions{
Tagger: signatureFromEnv(),
Message: commitMsg,
})
if err != nil {
log.Fatalf("Tag creation error: %v", err)
}
log.Printf("Tag '%s' is created on commit %s\n", tagName, headRef.Hash())
}
func gitPush(bc *BumpConfig, newVersion string) {
// Открываем локальный репозиторий (предполагается, что он существует в папке ".")
repo, err := git.PlainOpen(".")
if err != nil {
log.Fatalf("Repository open error: %v", err)
}
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{
branchSpec,
tagSpec,
},
})
if err != nil {
log.Fatalf("Push error: %v", err)
}
log.Println("Changes pushed successfully")
}