From ed38052eb63d24199665a51de335e6642f3fdde1 Mon Sep 17 00:00:00 2001 From: tonbiattack Date: Wed, 17 Dec 2025 16:30:29 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E3=81=97=E3=81=84=E3=82=BF?= =?UTF-8?q?=E3=82=B0=E3=81=8C=E5=AD=98=E5=9C=A8=E3=81=97=E3=81=AA=E3=81=84?= =?UTF-8?q?=E5=A0=B4=E5=90=88=E3=81=AE=E3=82=A8=E3=83=A9=E3=83=BC=E3=83=8F?= =?UTF-8?q?=E3=83=B3=E3=83=89=E3=83=AA=E3=83=B3=E3=82=B0=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0=E3=81=97=E3=80=81=E9=96=A2=E9=80=A3=E3=81=99=E3=82=8B?= =?UTF-8?q?=E3=83=86=E3=82=B9=E3=83=88=E3=82=92=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/tag/new_tag.go | 37 +++++++++++++++++++++++++++++++--- cmd/tag/new_tag_test.go | 44 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 3 deletions(-) diff --git a/cmd/tag/new_tag.go b/cmd/tag/new_tag.go index 8f7b5ed..3e00bcf 100644 --- a/cmd/tag/new_tag.go +++ b/cmd/tag/new_tag.go @@ -42,6 +42,7 @@ package tag import ( "bytes" + "errors" "fmt" "os/exec" "regexp" @@ -53,6 +54,8 @@ import ( "github.com/tonbiattack/git-plus/internal/ui" ) +const initialVersionTag = "v0.0.0" + var ( tagMessage string // タグメッセージ(アノテーテッドタグ用) tagPush bool // 作成後に自動的にリモートへプッシュするフラグ @@ -60,6 +63,7 @@ var ( tagRelease bool // プッシュ後に自動的にGitHubリリースを作成するフラグ tagReleaseDraft bool // リリースをドラフトとして作成するフラグ tagReleasePrerelease bool // リリースをプレリリースとして作成するフラグ + errNoGitTags = errors.New("git repository has no tags") ) // newTagCmd は new-tag コマンドの定義です。 @@ -85,9 +89,13 @@ var newTagCmd = &cobra.Command{ // 最新タグを取得 currentTag, err := getLatestTag() if err != nil { - fmt.Println("エラー: 最新タグの取得に失敗しました") - fmt.Println("タグが存在しない可能性があります。最初のタグを手動で作成してください。") - return err + if errors.Is(err, errNoGitTags) { + fmt.Printf("既存のタグが見つからないため、初期タグ %s から新しいタグを計算します。\n", initialVersionTag) + currentTag = initialVersionTag + } else { + fmt.Println("エラー: 最新タグの取得に失敗しました") + return err + } } // バージョンを解析 @@ -197,11 +205,34 @@ func getLatestTag() (string, error) { cmd := exec.Command("git", "describe", "--tags", "--abbrev=0") output, err := cmd.Output() if err != nil { + if isNoTagsDescribeError(err) { + return "", errNoGitTags + } return "", err } return strings.TrimSpace(string(output)), nil } +// isNoTagsDescribeError は git describe の結果が「タグが存在しない」場合を判定します。 +// +// パラメータ: +// - err: git describe 実行時に発生したエラー +// +// 戻り値: +// - bool: エラーがタグ未作成によるものなら true +func isNoTagsDescribeError(err error) bool { + if err == nil { + return false + } + + var exitErr *exec.ExitError + if errors.As(err, &exitErr) { + stderr := string(exitErr.Stderr) + return strings.Contains(stderr, "No names found, cannot describe anything.") + } + return false +} + // extractVersion はタグからバージョン番号を抽出します。 // // パラメータ: diff --git a/cmd/tag/new_tag_test.go b/cmd/tag/new_tag_test.go index 9e2dc7f..c59f6a8 100644 --- a/cmd/tag/new_tag_test.go +++ b/cmd/tag/new_tag_test.go @@ -1,6 +1,8 @@ package tag import ( + "errors" + "os/exec" "testing" "github.com/tonbiattack/git-plus/cmd" @@ -346,3 +348,45 @@ func TestVersionTypeAliases(t *testing.T) { } } } + +// TestIsNoTagsDescribeError は git describe でタグが存在しない場合のエラー判定をテストします +func TestIsNoTagsDescribeError(t *testing.T) { + tests := []struct { + name string + err error + want bool + }{ + { + name: "detects no tags error", + err: &exec.ExitError{ + Stderr: []byte("fatal: No names found, cannot describe anything.\n"), + }, + want: true, + }, + { + name: "ignores other exit errors", + err: &exec.ExitError{ + Stderr: []byte("fatal: not a git repository (or any of the parent directories): .git"), + }, + want: false, + }, + { + name: "ignores generic errors", + err: errors.New("some other error"), + want: false, + }, + { + name: "nil error", + err: nil, + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := isNoTagsDescribeError(tt.err); got != tt.want { + t.Fatalf("isNoTagsDescribeError() = %v, want %v", got, tt.want) + } + }) + } +}