Skip to content

Commit

Permalink
feat(branches): create pr (#447)
Browse files Browse the repository at this point in the history
* feat(branches): force push

* feat(branches): create pr
  • Loading branch information
dlvhdr authored Sep 6, 2024
1 parent dba6346 commit f9057e5
Show file tree
Hide file tree
Showing 6 changed files with 174 additions and 16 deletions.
79 changes: 74 additions & 5 deletions ui/components/reposection/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

gitm "github.com/aymanbagabas/git-module"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/log"

"github.com/dlvhdr/gh-dash/v4/data"
"github.com/dlvhdr/gh-dash/v4/git"
Expand Down Expand Up @@ -76,24 +77,49 @@ func (m *Model) fastForward() (tea.Cmd, error) {
}), nil
}

func (m *Model) push() (tea.Cmd, error) {
type pushOptions struct {
force bool
}

func (m *Model) push(opts pushOptions) (tea.Cmd, error) {
b := m.getCurrBranch()

taskId := fmt.Sprintf("push_%s_%d", b.Data.Name, time.Now().Unix())
withForceText := func() string {
if opts.force {
return " with force"
}
return ""
}
task := context.Task{
Id: taskId,
StartText: fmt.Sprintf("Pushing branch %s", b.Data.Name),
FinishedText: fmt.Sprintf("Branch %s has been pushed", b.Data.Name),
StartText: fmt.Sprintf("Pushing branch %s%s", b.Data.Name, withForceText()),
FinishedText: fmt.Sprintf("Branch %s has been pushed%s", b.Data.Name, withForceText()),
State: context.TaskStart,
Error: nil,
}
startCmd := m.Ctx.StartTask(task)
return tea.Batch(startCmd, func() tea.Msg {
var err error
args := []string{}
if opts.force {
args = append(args, "--force")
}
if len(b.Data.Remotes) == 0 {
err = gitm.Push(*m.Ctx.RepoPath, "origin", b.Data.Name, gitm.PushOptions{CommandOptions: gitm.CommandOptions{Args: []string{"--set-upstream"}}})
args = append(args, "--set-upstream")
err = gitm.Push(
*m.Ctx.RepoPath,
"origin",
b.Data.Name,
gitm.PushOptions{CommandOptions: gitm.CommandOptions{Args: args}},
)
} else {
err = gitm.Push(*m.Ctx.RepoPath, b.Data.Remotes[0], b.Data.Name)
err = gitm.Push(
*m.Ctx.RepoPath,
b.Data.Remotes[0],
b.Data.Name,
gitm.PushOptions{CommandOptions: gitm.CommandOptions{Args: args}},
)
}
if err != nil {
return constants.TaskFinishedMsg{TaskId: taskId, Err: err}
Expand Down Expand Up @@ -248,6 +274,49 @@ func (m *Model) fetchPRsCmd() tea.Cmd {
})
}

func (m *Model) fetchPRCmd(branch string) []tea.Cmd {
prsTaskId := fmt.Sprintf("fetching_pr_for_branch_%s_%d", branch, time.Now().Unix())
task := context.Task{
Id: prsTaskId,
StartText: fmt.Sprintf("Fetching PR for branch %s", branch),
FinishedText: "PR fetched",
State: context.TaskStart,
Error: nil,
}
startCmd := m.Ctx.StartTask(task)
return []tea.Cmd{startCmd, func() tea.Msg {
res, err := data.FetchPullRequests(fmt.Sprintf("author:@me repo:%s head:%s", git.GetRepoShortName(*m.Ctx.RepoUrl), branch), 1, nil)
log.Debug("Fetching PRs", "res", res)
if err != nil {
return constants.TaskFinishedMsg{
SectionId: 0,
SectionType: SectionType,
TaskId: prsTaskId,
Err: err,
}
}

if len(res.Prs) != 1 {
return constants.TaskFinishedMsg{
SectionId: 0,
SectionType: SectionType,
TaskId: prsTaskId,
Err: fmt.Errorf("expected 1 PR, got %d", len(res.Prs)),
}
}

return constants.TaskFinishedMsg{
SectionId: 0,
SectionType: SectionType,
TaskId: prsTaskId,
Msg: tasks.UpdateBranchMsg{
Name: branch,
NewPr: &res.Prs[0],
},
}
}}
}

type RefreshBranchesMsg struct {
id int
time time.Time
Expand Down
23 changes: 19 additions & 4 deletions ui/components/reposection/reposection.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,14 @@ func (m *Model) Update(msg tea.Msg) (section.Section, tea.Cmd) {
case msg.Type == tea.KeyEnter:
input := m.PromptConfirmationBox.Value()
action := m.GetPromptConfirmationAction()
branch := m.getCurrBranch().Data.Name
sid := tasks.SectionIdentifer{Id: m.Id, Type: SectionType}
if action == "new" {
cmd = m.newBranch(input)
} else if action == "create_pr" {
cmd = tasks.CreatePR(m.Ctx, sid, branch, input)
} else {
pr := findPRForRef(m.Prs, m.getCurrBranch().Data.Name)
sid := tasks.SectionIdentifer{Id: m.Id, Type: SectionType}
pr := findPRForRef(m.Prs, branch)
if input == "Y" || input == "y" {
switch action {
case "delete":
Expand Down Expand Up @@ -142,7 +145,12 @@ func (m *Model) Update(msg tea.Msg) (section.Section, tea.Cmd) {
}

case key.Matches(msg, keys.BranchKeys.Push):
cmd, err = m.push()
cmd, err = m.push(pushOptions{force: false})
if err != nil {
m.Ctx.Error = err
}
case key.Matches(msg, keys.BranchKeys.ForcePush):
cmd, err = m.push(pushOptions{force: true})
if err != nil {
m.Ctx.Error = err
}
Expand All @@ -154,6 +162,14 @@ func (m *Model) Update(msg tea.Msg) (section.Section, tea.Cmd) {

}

case tasks.UpdateBranchMsg:
if msg.IsCreated != nil && *msg.IsCreated {
cmds = append(cmds, m.fetchPRCmd(msg.Name)...)
}
if msg.NewPr != nil {
m.Prs = append(m.Prs, *msg.NewPr)
}

case repoMsg:
m.repo = msg.repo
m.Table.SetIsLoading(false)
Expand Down Expand Up @@ -193,7 +209,6 @@ func (m *Model) Update(msg tea.Msg) (section.Section, tea.Cmd) {

m.Table.SetRows(m.BuildRows())
table, tableCmd := m.Table.Update(msg)
cmds = append(cmds, tableCmd)
m.Table = table
cmds = append(cmds, tableCmd)

Expand Down
2 changes: 2 additions & 0 deletions ui/components/section/section.go
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,8 @@ func (m *BaseModel) GetPromptConfirmation() string {
prompt = "Are you sure you want to delete this branch? (Y/n) "
case m.PromptConfirmationAction == "new" && m.Ctx.View == config.RepoView:
prompt = "Enter branch name: "
case m.PromptConfirmationAction == "create_pr" && m.Ctx.View == config.RepoView:
prompt = "Enter PR title: "
}

m.PromptConfirmationBox.SetPrompt(prompt)
Expand Down
56 changes: 51 additions & 5 deletions ui/components/tasks/pr.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ package tasks
import (
"fmt"
"os/exec"
"strings"

tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/log"

"github.com/dlvhdr/gh-dash/v4/data"
"github.com/dlvhdr/gh-dash/v4/ui/constants"
Expand All @@ -27,6 +29,12 @@ type UpdatePRMsg struct {
RemovedAssignees *data.Assignees
}

type UpdateBranchMsg struct {
Name string
IsCreated *bool
NewPr *data.PullRequestData
}

func buildTaskId(prefix string, prNumber int) string {
return fmt.Sprintf("%s_%d", prefix, prNumber)
}
Expand All @@ -37,7 +45,7 @@ type GitHubTask struct {
Section SectionIdentifer
StartText string
FinishedText string
Msg func(c *exec.Cmd, err error) UpdatePRMsg
Msg func(c *exec.Cmd, err error) tea.Msg
}

func fireTask(ctx *context.ProgramContext, task GitHubTask) tea.Cmd {
Expand All @@ -51,6 +59,7 @@ func fireTask(ctx *context.ProgramContext, task GitHubTask) tea.Cmd {

startCmd := ctx.StartTask(start)
return tea.Batch(startCmd, func() tea.Msg {
log.Debug("Running task", "cmd", "gh "+strings.Join(task.Args, " "))
c := exec.Command("gh", task.Args...)

err := c.Run()
Expand Down Expand Up @@ -78,7 +87,7 @@ func OpenBranchPR(ctx *context.ProgramContext, section SectionIdentifer, branch
Section: section,
StartText: fmt.Sprintf("Opening PR for branch %s", branch),
FinishedText: fmt.Sprintf("PR for branch %s has been opened", branch),
Msg: func(c *exec.Cmd, err error) UpdatePRMsg {
Msg: func(c *exec.Cmd, err error) tea.Msg {
return UpdatePRMsg{}
},
})
Expand All @@ -98,7 +107,7 @@ func ReopenPR(ctx *context.ProgramContext, section SectionIdentifer, pr data.Row
Section: section,
StartText: fmt.Sprintf("Reopening PR #%d", prNumber),
FinishedText: fmt.Sprintf("PR #%d has been reopened", prNumber),
Msg: func(c *exec.Cmd, err error) UpdatePRMsg {
Msg: func(c *exec.Cmd, err error) tea.Msg {
return UpdatePRMsg{
PrNumber: prNumber,
IsClosed: utils.BoolPtr(false),
Expand All @@ -121,7 +130,7 @@ func ClosePR(ctx *context.ProgramContext, section SectionIdentifer, pr data.RowD
Section: section,
StartText: fmt.Sprintf("Closing PR #%d", prNumber),
FinishedText: fmt.Sprintf("PR #%d has been closed", prNumber),
Msg: func(c *exec.Cmd, err error) UpdatePRMsg {
Msg: func(c *exec.Cmd, err error) tea.Msg {
return UpdatePRMsg{
PrNumber: prNumber,
IsClosed: utils.BoolPtr(true),
Expand All @@ -144,7 +153,7 @@ func PRReady(ctx *context.ProgramContext, section SectionIdentifer, pr data.RowD
Section: section,
StartText: fmt.Sprintf("Marking PR #%d as ready for review", prNumber),
FinishedText: fmt.Sprintf("PR #%d has been marked as ready for review", prNumber),
Msg: func(c *exec.Cmd, err error) UpdatePRMsg {
Msg: func(c *exec.Cmd, err error) tea.Msg {
return UpdatePRMsg{
PrNumber: prNumber,
ReadyForReview: utils.BoolPtr(true),
Expand Down Expand Up @@ -192,3 +201,40 @@ func MergePR(ctx *context.ProgramContext, section SectionIdentifer, pr data.RowD
}
}))
}

func CreatePR(ctx *context.ProgramContext, section SectionIdentifer, branchName string, title string) tea.Cmd {
c := exec.Command(
"gh",
"pr",
"create",
"--title",
title,
"-R",
*ctx.RepoUrl,
)

taskId := fmt.Sprintf("create_pr_%s", title)
task := context.Task{
Id: taskId,
StartText: fmt.Sprintf(`Creating PR "%s"`, title),
FinishedText: fmt.Sprintf(`PR "%s" has been created`, title),
State: context.TaskStart,
Error: nil,
}
startCmd := ctx.StartTask(task)

return tea.Batch(startCmd, tea.ExecProcess(c, func(err error) tea.Msg {
isCreated := false
if err == nil && c.ProcessState.ExitCode() == 0 {
isCreated = true
}

return constants.TaskFinishedMsg{
SectionId: section.Id,
SectionType: section.Type,
TaskId: taskId,
Err: nil,
Msg: UpdateBranchMsg{Name: branchName, IsCreated: &isCreated},
}
}))
}
16 changes: 16 additions & 0 deletions ui/keys/branchKeys.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ import (
type BranchKeyMap struct {
Checkout key.Binding
New key.Binding
CreatePr key.Binding
FastForward key.Binding
Push key.Binding
ForcePush key.Binding
Delete key.Binding
ViewPRs key.Binding
}
Expand All @@ -27,6 +29,10 @@ var BranchKeys = BranchKeyMap{
key.WithKeys("n"),
key.WithHelp("n", "new"),
),
CreatePr: key.NewBinding(
key.WithKeys("O"),
key.WithHelp("O", "create PR"),
),
FastForward: key.NewBinding(
key.WithKeys("f"),
key.WithHelp("f", "fast-forward"),
Expand All @@ -35,6 +41,10 @@ var BranchKeys = BranchKeyMap{
key.WithKeys("P"),
key.WithHelp("P", "push"),
),
ForcePush: key.NewBinding(
key.WithKeys("F"),
key.WithHelp("F", "force-push"),
),
Delete: key.NewBinding(
key.WithKeys("d", "backspace"),
key.WithHelp("d/backspace", "delete"),
Expand All @@ -50,7 +60,9 @@ func BranchFullHelp() []key.Binding {
BranchKeys.Checkout,
BranchKeys.FastForward,
BranchKeys.Push,
BranchKeys.ForcePush,
BranchKeys.New,
BranchKeys.CreatePr,
BranchKeys.Delete,
BranchKeys.ViewPRs,
}
Expand All @@ -69,10 +81,14 @@ func rebindBranchKeys(keys []config.Keybinding) error {
switch branchKey.Builtin {
case "new":
key = &BranchKeys.New
case "createPr":
key = &BranchKeys.CreatePr
case "delete":
key = &BranchKeys.Delete
case "push":
key = &BranchKeys.Push
case "forcePush":
key = &BranchKeys.ForcePush
case "fastForward":
key = &BranchKeys.FastForward
case "checkout":
Expand Down
14 changes: 12 additions & 2 deletions ui/ui.go
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,13 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
return m, cmd

case key.Matches(msg, keys.BranchKeys.CreatePr):
if currSection != nil {
currSection.SetPromptConfirmationAction("create_pr")
cmd = currSection.SetIsPromptConfirmationShown(true)
}
return m, cmd

case key.Matches(msg, keys.BranchKeys.ViewPRs):
m.ctx.View = m.switchSelectedView()
m.syncMainContentWidth()
Expand Down Expand Up @@ -495,11 +502,14 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
now := time.Now()
task.FinishedTime = &now
m.tasks[msg.TaskId] = task
cmd = tea.Tick(2*time.Second, func(t time.Time) tea.Msg {
clear := tea.Tick(2*time.Second, func(t time.Time) tea.Msg {
return constants.ClearTaskMsg{TaskId: msg.TaskId}
})
cmds = append(cmds, clear)

scmd := m.updateSection(msg.SectionId, msg.SectionType, msg.Msg)
cmds = append(cmds, scmd)

m.updateSection(msg.SectionId, msg.SectionType, msg.Msg)
m.syncSidebar()
}

Expand Down

0 comments on commit f9057e5

Please sign in to comment.