All checks were successful
continuous-integration/drone/push Build is passing
196 lines
5.6 KiB
Go
196 lines
5.6 KiB
Go
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")
|
||
}
|