Skip to content

Commit

Permalink
console output prettier
Browse files Browse the repository at this point in the history
  • Loading branch information
cooper-oh committed Dec 22, 2024
1 parent 93fbdd0 commit 2664bd4
Show file tree
Hide file tree
Showing 9 changed files with 313 additions and 69 deletions.
85 changes: 48 additions & 37 deletions git/cherry_pick.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,18 @@ type CherryPick struct {

func (cherryPick *CherryPick) RunWithContext(ctx context.Context) error {
logger := log.LoggerFromCtx(ctx)
err := tui.WithSpinner(ctx, "checking repository is ready ...", func(ctx context.Context, logger log.Logger) error {
logger.Infof("checking repository is dirty")

logger.Infof("🍒 %s", color.Bold("starting cherry-picker\n"))

err := tui.WithStep(ctx, "checking is repository ready", func(ctx context.Context, logger log.Logger) error {
logger.Infof("checking is repository dirty")
if dirty, err := IsDirty(ctx); err != nil {
return fmt.Errorf("error checking if the repository is dirty: %w", err)
} else if dirty {
return fmt.Errorf("the repository is dirty. please commit your changes before continuing")
}

logger.Infof("checking repository is in a rebase or am")
logger.Infof("checking is repository in a rebase or am")
if rebaseOrAm, err := IsInRebaseOrAm(ctx); err != nil {
return fmt.Errorf("error checking if the repository is in a rebase or am: %w", err)
} else if rebaseOrAm {
Expand All @@ -44,91 +47,91 @@ func (cherryPick *CherryPick) RunWithContext(ctx context.Context) error {
if err != nil {
return err
}
logger.Successf("repository is available for cherry-pick")

var pr *gitobj.PullRequest
title := fmt.Sprintf("fetching the pull request %s ...", color.Cyan(fmt.Sprintf("#%d", cherryPick.PRNumber)))
err = tui.WithSpinner(ctx, title, func(ctx context.Context, logger log.Logger) error {
logger.Infof("getting the pull request %s", color.Cyan(fmt.Sprintf("#%d", cherryPick.PRNumber)))
err = tui.WithStep(ctx, "validating the pull request", func(ctx context.Context, logger log.Logger) error {
logger.WithField("pr", cherryPick.PRNumber).Infof("fetching the pull request")
if pr, err = GetPullRequest(ctx, cherryPick.PRNumber); err != nil {
return fmt.Errorf("error getting the pull request: %w", err)
}

logger.Infof("%v (%v) by %v\n %v ← %v", color.Cyan(pr.Title), pr.PRNumberString(), color.Cyan(pr.Author.Login), color.Cyan(pr.BaseRefName), color.Cyan(pr.HeadRefName))
logger.Successf("%s %s %s", pr.PRNumberString(), pr.Url, color.Grey(pr.Author.Login))

if pr.State != gitobj.PullRequestStateMerged {
return fmt.Errorf("PR %s is not merged (current state: %s). please ensure the PR is merged before continuing", pr.PRNumberString(), pr.StateString())
return fmt.Errorf("PR is not merged (current state: %s). please ensure the PR is merged before continuing", pr.StateString())
}

return nil
})
if err != nil {
return err
}
logger.Successf("fetched the pull request")

var mergeStrategy MergeStrategy
err = tui.WithSpinner(ctx, "determining merge strategy ...", func(ctx context.Context, logger log.Logger) error {
err = tui.WithStep(ctx, "determining merge strategy", func(ctx context.Context, logger log.Logger) error {
if cherryPick.MergeStrategy == MergeStrategyAuto {
logger.Infof("determining merge strategy automatically")
logger.Infof("no merge strategy given, determining merge strategy")

if mergeStrategy, err = PRMergedWith(ctx, cherryPick.PRNumber); err != nil {
return fmt.Errorf("error determining merge strategy: %w", err)
}

logger.Successf("determined merge strategy as %s", color.Cyan(mergeStrategy))
} else {
logger.Infof("using merge strategy %s with given flag", color.Cyan(cherryPick.MergeStrategy))
logger.Infof("use merge strategy %s with given flag", color.Cyan(cherryPick.MergeStrategy))
}

return nil
})
if err != nil {
return err
}
logger.Successf("determined merge strategy as %s", color.Cyan(mergeStrategy))

var cherryPickBranchName = fmt.Sprintf("cherry-pick-pr-%d-onto-%s-%d", cherryPick.PRNumber, strings.ReplaceAll(cherryPick.OnTo, "/", "-"), time.Now().Unix())
err = tui.WithSpinner(ctx, "checking out branch ...", func(ctx context.Context, logger log.Logger) error {
logger.Infof("branch name: %v", color.Cyan(cherryPickBranchName))
logger.Infof("starting point: %v", color.Cyan(fmt.Sprintf("origin/%s", cherryPick.OnTo)))

logger.Infof("fetching the branch %v", color.Cyan(pr.BaseRefName))
err = tui.WithStep(ctx, "checking out branch", func(ctx context.Context, logger log.Logger) error {
logger.WithField("branch", pr.BaseRefName).Infof("fetching the branch")
if err = Fetch(ctx, "origin", pr.BaseRefName); err != nil {
return fmt.Errorf("error fetching the branch '%s': %w", cherryPick.OnTo, err)
}

logger.Infof("fetching the branch %v", color.Cyan(cherryPick.OnTo))
if err = Fetch(ctx, "origin", cherryPick.OnTo); err != nil {
return fmt.Errorf("error fetching the branch '%s': %w", cherryPick.OnTo, err)
if cherryPick.OnTo != pr.BaseRefName {
logger.WithField("branch", cherryPick.OnTo).Infof("fetching the branch")
if err = Fetch(ctx, "origin", cherryPick.OnTo); err != nil {
return fmt.Errorf("error fetching the branch '%s': %w", cherryPick.OnTo, err)
}
}

logger.Infof("checking out a new branch %v based on %v", color.Cyan(cherryPickBranchName), color.Cyan(cherryPick.OnTo))
logger.WithField("branch", cherryPickBranchName).
WithField("base", cherryPick.OnTo).
Infof("checking out to new branch")
if err = CheckoutNewBranch(ctx, cherryPickBranchName, fmt.Sprintf("origin/%s", cherryPick.OnTo)); err != nil {
return err
return fmt.Errorf("error checking out to new branch '%s': %w", cherryPickBranchName, err)
}

return nil
})
if err != nil {
return err
}
logger.Successf("checked out to %s based on %s", color.Cyan(cherryPickBranchName), color.Cyan(cherryPick.OnTo))

switch mergeStrategy {
case MergeStrategyRebase:
err = tui.WithSpinner(ctx, "rebasing ...", func(ctx context.Context, logger log.Logger) error {
logger.Infof("fetching diff")
err = tui.WithStep(ctx, "rebasing PR", func(ctx context.Context, logger log.Logger) error {
logger.WithField("pr", cherryPick.PRNumber).Infof("fetching diff")
var prDiff bytes.Buffer
if err = NewCommand("gh", "pr", "diff", strconv.Itoa(cherryPick.PRNumber), "--patch").Run(ctx, WithStdout(&prDiff)); err != nil {
return fmt.Errorf("error getting PR diff: %w", err)
}

logger.Infof("applying diff")
if err = NewCommand("git", "am", "-3").Run(ctx, WithStdin(&prDiff)); err != nil {
helpMsg := fmt.Sprintf("run %s after resolve the conflicts\nrun %s if you want to abort the rebase", color.Green("`git am --continue`"), color.Yellow("`git am --abort`"))

var gitError *GitError
if errors.As(err, &gitError) && gitError.ExitCode == 1 && strings.Contains(gitError.Stderr, "error: could not apply") {
return fmt.Errorf("error applying PR diff\nplease resolve the conflicts and run %s. if you want to abort the rebase, run %s", color.Green("`git am --continue`"), color.Yellow("`git am --abort`"))
if errors.As(err, &gitError) && gitError.ExitCode == 1 && strings.Contains(gitError.Stderr, "error: Failed to merge in the changes") {
return fmt.Errorf("error applying PR diff\n%s", helpMsg)
}
return fmt.Errorf("error cherry-picking PR merge commit: %w", err)
return fmt.Errorf("error applying PR diff\n%s\n\n%w", helpMsg, err)
}

return nil
Expand All @@ -139,14 +142,16 @@ func (cherryPick *CherryPick) RunWithContext(ctx context.Context) error {
logger.Successf("rebased branch %s onto %s", color.Cyan(cherryPickBranchName), color.Cyan(cherryPick.OnTo))

case MergeStrategySquash:
err = tui.WithSpinner(ctx, "cherry-picking ...", func(ctx context.Context, logger log.Logger) error {
logger.Infof("cherry-picking PR merge commit %v", color.Cyan(pr.MergeCommit.Sha))
err = tui.WithStep(ctx, "cherry-picking PR merge commit", func(ctx context.Context, logger log.Logger) error {
logger.WithField("merge_commit", pr.MergeCommit.Sha[:7]).Infof("cherry-picking")
if err = NewCommand("git", "cherry-pick", "--keep-redundant-commits", pr.MergeCommit.Sha).Run(ctx); err != nil {
helpMsg := fmt.Sprintf("run %v after resolve the conflicts\nrun %v if you want to abort the cherry-pick", color.Green("`git cherry-pick --continue`"), color.Yellow("`git cherry-pick --abort`"))

var gitError *GitError
if errors.As(err, &gitError) && gitError.ExitCode == 1 && strings.Contains(gitError.Stderr, "error: could not apply") {
return fmt.Errorf("error cherry-picking PR merge commit\nplease resolve the conflicts and run %v. if you want to abort the cherry-pick, run %v\n\n%v", color.Green("`git cherry-pick --continue`"), color.Yellow("`git cherry-pick --abort`"), err)
return fmt.Errorf("error cherry-picking PR merge commit\n%s", helpMsg)
}
return fmt.Errorf("error cherry-picking PR merge commit: %w", err)
return fmt.Errorf("error cherry-picking PR merge commit\n%s\n\n%w", helpMsg, err)
}

return nil
Expand All @@ -158,18 +163,24 @@ func (cherryPick *CherryPick) RunWithContext(ctx context.Context) error {
}

if cherryPick.Push {
err = tui.WithSpinner(ctx, "pushing ...", func(ctx context.Context, logger log.Logger) error {
logger.Infof("pushing branch %v", color.Cyan(cherryPickBranchName))
err = tui.WithStep(ctx, "pushing branch", func(ctx context.Context, logger log.Logger) error {
logger.WithField("branch", cherryPickBranchName).Infof("pushing")
if err = Push(ctx, "origin", cherryPickBranchName); err != nil {
return fmt.Errorf("error pushing branch %s: %w", cherryPickBranchName, err)
}

nameWithOwner, _ := GetNameWithOwner(ctx)

logger.Successf("pushed branch %s\ncreate a pull request by visiting:\n ",
color.Cyan(cherryPickBranchName),
fmt.Sprintf("https://github.com/%s/pull/new/%s", nameWithOwner, cherryPickBranchName),
)

return nil
})
if err != nil {
return err
}
logger.Successf("pushed branch %s", color.Cyan(cherryPickBranchName))
}

return nil
Expand Down
15 changes: 14 additions & 1 deletion gitobj/pull_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,18 @@ func (pr PullRequest) StateString() string {
}

func (pr PullRequest) PRNumberString() string {
return color.Cyan(fmt.Sprintf("#%d", pr.Number))
str := fmt.Sprintf("#%d", pr.Number)
switch pr.State {
case PullRequestStateOpen:
return color.Green(str)
case PullRequestStateClosed:
return color.Red(str)
case PullRequestStateMerged:
return color.Purple(str)
default:
if pr.IsDraft {
return color.Grey(str)
}
return "UNKNOWN"
}
}
7 changes: 7 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,15 @@ require (
)

require (
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/lipgloss v1.0.0 // indirect
github.com/charmbracelet/x/ansi v0.4.2 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/muesli/termenv v0.15.2 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/term v0.27.0 // indirect
)
15 changes: 15 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,14 +1,29 @@
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/briandowns/spinner v1.23.1 h1:t5fDPmScwUjozhDj4FA46p5acZWIPXYE30qW2Ptu650=
github.com/briandowns/spinner v1.23.1/go.mod h1:LaZeM4wm2Ywy6vO571mvhQNRcWfRUnXOs0RcKV0wYKM=
github.com/charmbracelet/lipgloss v1.0.0 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O2qFMQNg=
github.com/charmbracelet/lipgloss v1.0.0/go.mod h1:U5fy9Z+C38obMs+T+tJqst9VGzlOYGj4ri9reL3qUlo=
github.com/charmbracelet/x/ansi v0.4.2 h1:0JM6Aj/g/KC154/gOP4vfxun0ff6itogDYk41kof+qk=
github.com/charmbracelet/x/ansi v0.4.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
github.com/cli/safeexec v1.0.0 h1:0VngyaIyqACHdcMNWfo6+KdUYnqEr2Sg+bSP1pdF+dI=
github.com/cli/safeexec v1.0.0/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
Expand Down
37 changes: 22 additions & 15 deletions internal/color/color.go
Original file line number Diff line number Diff line change
@@ -1,43 +1,50 @@
package color

import (
"github.com/fatih/color"
"fmt"

"github.com/charmbracelet/lipgloss"
)

var (
blue = color.New(color.FgBlue).SprintFunc()
cyan = color.New(color.FgCyan).SprintFunc()
green = color.New(color.FgGreen).SprintFunc()
red = color.New(color.FgRed).SprintFunc()
yellow = color.New(color.FgYellow).SprintFunc()
purple = color.New(color.FgMagenta).SprintFunc()
grey = color.New(color.FgHiWhite).SprintFunc()
bold = lipgloss.NewStyle().Bold(true)
red = lipgloss.NewStyle().Foreground(lipgloss.ANSIColor(1))
green = lipgloss.NewStyle().Foreground(lipgloss.ANSIColor(2))
yellow = lipgloss.NewStyle().Foreground(lipgloss.ANSIColor(3))
blue = lipgloss.NewStyle().Foreground(lipgloss.ANSIColor(4))
purple = lipgloss.NewStyle().Foreground(lipgloss.ANSIColor(5))
cyan = lipgloss.NewStyle().Foreground(lipgloss.ANSIColor(6))
grey = lipgloss.NewStyle().Foreground(lipgloss.ANSIColor(7))
)

func Bold(a ...interface{}) string {
return bold.Render(fmt.Sprint(a...))
}

func Blue(a ...interface{}) string {
return blue(a...)
return blue.Render(fmt.Sprint(a...))
}

func Cyan(a ...interface{}) string {
return cyan(a...)
return cyan.Render(fmt.Sprint(a...))
}

func Green(a ...interface{}) string {
return green(a...)
return green.Render(fmt.Sprint(a...))
}

func Red(a ...interface{}) string {
return red(a...)
return red.Render(fmt.Sprint(a...))
}

func Yellow(a ...interface{}) string {
return yellow(a...)
return yellow.Render(fmt.Sprint(a...))
}

func Purple(a ...interface{}) string {
return purple(a...)
return purple.Render(fmt.Sprint(a...))
}

func Grey(a ...interface{}) string {
return grey(a...)
return grey.Render(fmt.Sprint(a...))
}
Loading

0 comments on commit 2664bd4

Please sign in to comment.