diff --git a/go.mod b/go.mod index f1dd0a2..09abe5a 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/handofgod94/gh-jira-changelog go 1.20 require ( + github.com/looplab/fsm v1.0.1 github.com/samber/lo v1.38.1 github.com/spf13/cobra v1.7.0 github.com/spf13/viper v1.16.0 diff --git a/go.sum b/go.sum index 33e1622..626fda0 100644 --- a/go.sum +++ b/go.sum @@ -134,6 +134,8 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/looplab/fsm v1.0.1 h1:OEW0ORrIx095N/6lgoGkFkotqH6s7vaFPsgjLAaF5QU= +github.com/looplab/fsm v1.0.1/go.mod h1:PmD3fFvQEIsjMEfvZdrCDZ6y8VwKTwWNjlpEr6IKPO4= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= diff --git a/pkg/jira_changelog/generator.go b/pkg/jira_changelog/generator.go index a0b9694..0196b2f 100644 --- a/pkg/jira_changelog/generator.go +++ b/pkg/jira_changelog/generator.go @@ -25,11 +25,11 @@ func panicIfErr(err error) { } func (c Generator) Generate(ctx context.Context) *Changelog { - gitOutput, err := git.ExecGitLog(ctx, c.fromRef, c.toRef) - panicIfErr(err) - - commits, err := gitOutput.Commits() - panicIfErr(err) + gcw := git.NewCommitParseWorkflow(c.fromRef, c.toRef) + commits, err := gcw.Commits(ctx) + if err != nil { + panic(fmt.Errorf("failed at \"%s\" state. %w. State: %+v", gcw.FSM.Current(), err, gcw)) + } changes, err := c.changesFromCommits(commits) panicIfErr(err) diff --git a/pkg/jira_changelog/git/commits.go b/pkg/jira_changelog/git/commits.go index 210490d..05e16cb 100644 --- a/pkg/jira_changelog/git/commits.go +++ b/pkg/jira_changelog/git/commits.go @@ -5,11 +5,9 @@ import ( "fmt" "os/exec" "regexp" - "strconv" - "strings" "time" - "golang.org/x/exp/slog" + "github.com/looplab/fsm" ) type Commit struct { @@ -18,92 +16,77 @@ type Commit struct { Sha string } -type GitOutput string - var gitoutputPattern = regexp.MustCompile(`^\((\d+)\)\s+\{(\w+)\}\s*(.*)`) -func ExecGitLog(ctx context.Context, fromRef, toRef string) (GitOutput, error) { - cmd := exec.CommandContext(ctx, "git", "log", "--decorate-refs-exclude=refs/*", "--pretty=(%ct) {%h} %d %s", "--no-merges", fromRef+".."+toRef) - stdout, err := cmd.Output() - if err != nil { - return "", fmt.Errorf("failed to execute git command: %v", err) - } +const ( + InitialState = "initial" + CommandExecuted = "command_executed" + OutuptParsed = "output_parsed" +) - result := string(stdout) - return GitOutput(result), nil +type commitParseWorkflow struct { + fromRef string + toRef string + gitOutput GitOutput + commits []Commit + FSM *fsm.FSM } -func FetchGitRemoteURL(ctx context.Context) (string, error) { - cmd := exec.CommandContext(ctx, "git", "remote", "get-url", "origin") - stdout, err := cmd.Output() - if err != nil { - return "", fmt.Errorf("failed to execute git command: %v", err) +func NewCommitParseWorkflow(fromRef, toRef string) *commitParseWorkflow { + cpw := &commitParseWorkflow{ + fromRef: fromRef, + toRef: toRef, } - result := strings.TrimSpace(string(stdout)) - result = strings.TrimSuffix(result, ".git") - return result, nil + cpw.FSM = fsm.NewFSM( + InitialState, + fsm.Events{ + {Name: "execute_git_log", Src: []string{InitialState}, Dst: CommandExecuted}, + {Name: "parse_output", Src: []string{CommandExecuted}, Dst: OutuptParsed}, + }, + fsm.Callbacks{ + "before_execute_git_log": func(ctx context.Context, e *fsm.Event) { + ouptut, err := execGitLog(ctx, fromRef, toRef) + if err != nil { + e.Cancel(err) + return + } + cpw.gitOutput = ouptut + }, + "before_parse_output": func(ctx context.Context, e *fsm.Event) { + commits, err := cpw.gitOutput.Commits() + if err != nil { + e.Cancel(err) + return + } + cpw.commits = commits + }, + }, + ) + return cpw } -func (gt GitOutput) Commits() ([]Commit, error) { - output := strings.TrimSpace(string(gt)) - commits := make([]Commit, 0) - for _, line := range strings.Split(output, "\n") { - message, err := extractCommitMessage(line) - if err != nil { - slog.Error("failed to extract commit message", "gitlogLine", line) - return []Commit{}, fmt.Errorf("failed to extract commit message. %w", err) - } - - commitTime, err := extractTime(line) - if err != nil { - slog.Error("failed to extract timestamp", "gitlogLine", line) - return []Commit{}, fmt.Errorf("failed to extract timestamp. %w", err) - } - - sha, err := extractSha(line) - if err != nil { - slog.Error("failed to extract sha", "gitlogLine", line) - return []Commit{}, fmt.Errorf("failed to extract sha. %w", err) - } - - commits = append(commits, Commit{ - Message: message, - Time: commitTime, - Sha: sha, - }) +func (cpw *commitParseWorkflow) Commits(ctx context.Context) ([]Commit, error) { + err := cpw.FSM.Event(ctx, "execute_git_log") + if err != nil { + return []Commit{}, fmt.Errorf("failed to execute git log. %w", err) } - return commits, nil -} -func extractCommitMessage(gitlogLine string) (string, error) { - gitlogLine = strings.TrimSpace(gitlogLine) - result := gitoutputPattern.FindStringSubmatch(gitlogLine) - if len(result) < 4 { - return "", fmt.Errorf("couldn't find commit message in git log. %v", gitlogLine) + err = cpw.FSM.Event(ctx, "parse_output") + if err != nil { + return []Commit{}, fmt.Errorf("failed to parse output. %w", err) } - return result[3], nil + return cpw.commits, nil } -func extractTime(gitlogLine string) (time.Time, error) { - result := gitoutputPattern.FindStringSubmatch(gitlogLine) - if len(result) < 2 { - return time.Time{}, fmt.Errorf("couldn't find timestamp in commit message. %v", gitlogLine) - } - - timestamp, err := strconv.Atoi(result[1]) +func execGitLog(ctx context.Context, fromRef, toRef string) (GitOutput, error) { + cmd := exec.CommandContext(ctx, "git", "log", "--decorate-refs-exclude=refs/*", "--pretty=(%ct) {%h} %d %s", "--no-merges", fromRef+".."+toRef) + stdout, err := cmd.Output() if err != nil { - return time.Time{}, fmt.Errorf("failed to extract timestamp in commit message. %w", err) - } - return time.Unix(int64(timestamp), 0), nil -} - -func extractSha(gitlogLine string) (string, error) { - result := gitoutputPattern.FindStringSubmatch(gitlogLine) - if len(result) < 3 { - return "", fmt.Errorf("couldn't find sha in commit message. %v", gitlogLine) + return "", fmt.Errorf("failed to execute git command: %v", err) } - return result[2], nil + result := string(stdout) + return GitOutput(result), nil } diff --git a/pkg/jira_changelog/git/git_output.go b/pkg/jira_changelog/git/git_output.go new file mode 100644 index 0000000..4d5106c --- /dev/null +++ b/pkg/jira_changelog/git/git_output.go @@ -0,0 +1,75 @@ +package git + +import ( + "fmt" + "strconv" + "strings" + "time" + + "golang.org/x/exp/slog" +) + +type GitOutput string + +func (gt GitOutput) Commits() ([]Commit, error) { + output := strings.TrimSpace(string(gt)) + commits := make([]Commit, 0) + for _, line := range strings.Split(output, "\n") { + message, err := extractCommitMessage(line) + if err != nil { + slog.Error("failed to extract commit message", "gitlogLine", line) + return []Commit{}, fmt.Errorf("failed to extract commit message. %w", err) + } + + commitTime, err := extractTime(line) + if err != nil { + slog.Error("failed to extract timestamp", "gitlogLine", line) + return []Commit{}, fmt.Errorf("failed to extract timestamp. %w", err) + } + + sha, err := extractSha(line) + if err != nil { + slog.Error("failed to extract sha", "gitlogLine", line) + return []Commit{}, fmt.Errorf("failed to extract sha. %w", err) + } + + commits = append(commits, Commit{ + Message: message, + Time: commitTime, + Sha: sha, + }) + } + return commits, nil +} + +func extractCommitMessage(gitlogLine string) (string, error) { + gitlogLine = strings.TrimSpace(gitlogLine) + result := gitoutputPattern.FindStringSubmatch(gitlogLine) + if len(result) < 4 { + return "", fmt.Errorf("couldn't find commit message in git log. %v", gitlogLine) + } + + return result[3], nil +} + +func extractTime(gitlogLine string) (time.Time, error) { + result := gitoutputPattern.FindStringSubmatch(gitlogLine) + if len(result) < 2 { + return time.Time{}, fmt.Errorf("couldn't find timestamp in commit message. %v", gitlogLine) + } + + timestamp, err := strconv.Atoi(result[1]) + if err != nil { + return time.Time{}, fmt.Errorf("failed to extract timestamp in commit message. %w", err) + } + return time.Unix(int64(timestamp), 0), nil +} + +func extractSha(gitlogLine string) (string, error) { + result := gitoutputPattern.FindStringSubmatch(gitlogLine) + if len(result) < 3 { + return "", fmt.Errorf("couldn't find sha in commit message. %v", gitlogLine) + } + + return result[2], nil +}