diff --git a/git/git.go b/git/git.go index 7df4e805..756f5678 100644 --- a/git/git.go +++ b/git/git.go @@ -25,6 +25,7 @@ type Branch struct { CommitsAhead int CommitsBehind int IsCheckedOut bool + Remotes []string } func GetOriginUrl(dir string) (string, error) { @@ -87,10 +88,15 @@ func GetRepo(dir string) (*Repo, error) { if err != nil { commitsBehind = 0 } + remotes, err := repo.RemoteGetURL(b) + if err != nil { + commitsBehind = 0 + } branches[i] = Branch{ Name: b, LastUpdatedAt: updatedAt, IsCheckedOut: isHead, + Remotes: remotes, LastCommitMsg: lastCommitMsg, CommitsAhead: int(commitsAhead), CommitsBehind: int(commitsBehind), diff --git a/ui/components/branch/branch.go b/ui/components/branch/branch.go index 8fbb43bc..555336b8 100644 --- a/ui/components/branch/branch.go +++ b/ui/components/branch/branch.go @@ -175,42 +175,12 @@ func (b *Branch) renderTitle() string { } func (b *Branch) renderExtendedTitle(isSelected bool) string { - baseStyle := lipgloss.NewStyle() - if isSelected { - baseStyle = baseStyle.Background(b.Ctx.Theme.SelectedBackground) - } + baseStyle := b.getBaseStyle(isSelected) + width := b.getMaxWidth() - title := "-" - if b.PR != nil { - title = fmt.Sprintf("#%d %s", b.PR.Number, b.PR.Title) - } else if b.Data.LastCommitMsg != nil { - title = *b.Data.LastCommitMsg - } - var titleColumn table.Column - for _, column := range b.Columns { - if column.Title == "Title" { - titleColumn = column - } - } - width := titleColumn.ComputedWidth - 2 - title = baseStyle.Foreground(b.Ctx.Theme.SecondaryText).Width(width).MaxWidth(width).Render(title) - name := b.Data.Name - if b.Data.IsCheckedOut { - name = baseStyle.Foreground(b.Ctx.Theme.SuccessText).Render(name) - } else { - name = baseStyle.Foreground(b.Ctx.Theme.PrimaryText).Render(name) - } - commitsAhead := "" - commitsBehind := "" - if b.Data.CommitsAhead > 0 { - commitsAhead = baseStyle.Render(fmt.Sprintf(" ↑%d", b.Data.CommitsAhead)) - } - if b.Data.CommitsBehind > 0 { - commitsBehind = baseStyle.Render(fmt.Sprintf(" ↓%d", b.Data.CommitsBehind)) - } - top := baseStyle.Width(width).MaxWidth(width).Render(lipgloss.JoinHorizontal(lipgloss.Left, name, commitsAhead, commitsBehind)) - - return baseStyle.Render(lipgloss.JoinVertical(lipgloss.Left, top, title)) + title := b.renderPRTitleOrCommigMsg(isSelected, width) + branch := b.renderBranch(isSelected, width) + return baseStyle.Render(lipgloss.JoinVertical(lipgloss.Left, branch, title)) } func (pr *Branch) renderAuthor() string { @@ -311,3 +281,64 @@ func (b *Branch) ToTableRow(isSelected bool) table.Row { b.renderUpdateAt(), } } + +func (b *Branch) renderBranch(isSelected bool, width int) string { + baseStyle := b.getBaseStyle(isSelected) + name := b.Data.Name + if b.Data.IsCheckedOut { + name = baseStyle.Foreground(b.Ctx.Theme.SuccessText).Render(name) + } else { + name = baseStyle.Foreground(b.Ctx.Theme.PrimaryText).Render(name) + } + return baseStyle.MaxHeight(1).Width(width).MaxWidth(width).Render(lipgloss.JoinHorizontal( + lipgloss.Top, + name, + b.renderCommitsAheadBehind(isSelected), + )) + +} + +func (b *Branch) getBaseStyle(isSelected bool) lipgloss.Style { + baseStyle := lipgloss.NewStyle() + if isSelected { + baseStyle = baseStyle.Background(b.Ctx.Theme.SelectedBackground) + } + + return baseStyle +} + +func (b *Branch) getMaxWidth() int { + var titleColumn table.Column + for _, column := range b.Columns { + if column.Grow != nil && *column.Grow { + titleColumn = column + } + } + return titleColumn.ComputedWidth - 2 +} + +func (b *Branch) renderCommitsAheadBehind(isSelected bool) string { + baseStyle := b.getBaseStyle(isSelected) + + commitsAhead := "" + commitsBehind := "" + if b.Data.CommitsAhead > 0 { + commitsAhead = baseStyle.Foreground(b.Ctx.Theme.WarningText).Render(fmt.Sprintf(" ↑%d", b.Data.CommitsAhead)) + } + if b.Data.CommitsBehind > 0 { + commitsBehind = baseStyle.Foreground(b.Ctx.Theme.WarningText).Render(fmt.Sprintf(" ↓%d", b.Data.CommitsBehind)) + } + + return lipgloss.JoinHorizontal(lipgloss.Top, commitsAhead, commitsBehind) +} + +func (b *Branch) renderPRTitleOrCommigMsg(isSelected bool, width int) string { + baseStyle := b.getBaseStyle(isSelected) + title := "-" + if b.PR != nil { + title = fmt.Sprintf("#%d %s", b.PR.Number, b.PR.Title) + } else if b.Data.LastCommitMsg != nil { + title = *b.Data.LastCommitMsg + } + return baseStyle.Foreground(b.Ctx.Theme.SecondaryText).Width(width).MaxWidth(width).Render(title) +} diff --git a/ui/components/pr/pr.go b/ui/components/pr/pr.go index 81e81cc7..084b2802 100644 --- a/ui/components/pr/pr.go +++ b/ui/components/pr/pr.go @@ -191,9 +191,6 @@ func (pr *PullRequest) renderExtendedTitle(isSelected bool) string { baseStyle = baseStyle.Foreground(pr.Ctx.Theme.SecondaryText).Background(pr.Ctx.Theme.SelectedBackground) } - if pr.Data == nil { - return pr.renderBranch(isSelected) - } repoName := baseStyle.Render(lipgloss.JoinHorizontal(lipgloss.Top, pr.Data.Repository.NameWithOwner, fmt.Sprintf(" #%d", pr.Data.Number))) author := baseStyle.Render(fmt.Sprintf("@%s", pr.Data.Author.Login)) branch := baseStyle.Render(pr.Data.HeadRefName) @@ -201,30 +198,14 @@ func (pr *PullRequest) renderExtendedTitle(isSelected bool) string { title := pr.Data.Title var titleColumn table.Column for _, column := range pr.Columns { - if column.Title == "Title" { + if column.Grow != nil && *column.Grow { titleColumn = column } } width := titleColumn.ComputedWidth - 2 top = baseStyle.Foreground(pr.Ctx.Theme.SecondaryText).Width(width).MaxWidth(width).Height(1).MaxHeight(1).Render(top) - title = baseStyle.Foreground(pr.Ctx.Theme.PrimaryText).Width(width).MaxWidth(width).Render(title) - - return baseStyle.Render(lipgloss.JoinVertical(lipgloss.Left, top, title)) -} - -func (pr *PullRequest) renderBranch(isSelected bool) string { - baseStyle := lipgloss.NewStyle() - if isSelected { - baseStyle = baseStyle.Foreground(pr.Ctx.Theme.SecondaryText).Background(pr.Ctx.Theme.SelectedBackground) - } + title = baseStyle.Foreground(pr.Ctx.Theme.PrimaryText).Width(width).MaxWidth(width).Height(1).MaxHeight(1).Render(title) - // repoName := strings.TrimPrefix(pr.Ctx.Repo.Origin, "https://github.com/") - // repoName = strings.TrimSuffix(repoName, ".git") - top := lipgloss.JoinHorizontal(lipgloss.Top, baseStyle.Render("TODO"), baseStyle.Render(" · "), baseStyle.Render("UNPUBLISHED")) - branch := pr.Branch.Name - width := max(lipgloss.Width(top), lipgloss.Width(branch)) - top = baseStyle.Foreground(pr.Ctx.Theme.SecondaryText).Width(width).Render(top) - title := baseStyle.Foreground(pr.Ctx.Theme.PrimaryText).Width(width).Render(branch) return baseStyle.Render(lipgloss.JoinVertical(lipgloss.Left, top, title)) } diff --git a/ui/components/reposection/checkout.go b/ui/components/reposection/checkout.go deleted file mode 100644 index 083a685b..00000000 --- a/ui/components/reposection/checkout.go +++ /dev/null @@ -1,45 +0,0 @@ -package reposection - -import ( - "fmt" - "time" - - gitm "github.com/aymanbagabas/git-module" - tea "github.com/charmbracelet/bubbletea" - - "github.com/dlvhdr/gh-dash/v4/git" - "github.com/dlvhdr/gh-dash/v4/ui/constants" - "github.com/dlvhdr/gh-dash/v4/ui/context" -) - -func (m *Model) checkout() (tea.Cmd, error) { - b := m.getCurrBranch() - - taskId := fmt.Sprintf("checkout_%s_%d", b.Data.Name, time.Now().Unix()) - task := context.Task{ - Id: taskId, - StartText: fmt.Sprintf("Checking out branch %s", b.Data.Name), - FinishedText: fmt.Sprintf("Branch %s has been checked out", b.Data.Name), - State: context.TaskStart, - Error: nil, - } - startCmd := m.Ctx.StartTask(task) - return tea.Batch(startCmd, func() tea.Msg { - err := gitm.Checkout(*m.Ctx.RepoPath, b.Data.Name) - if err != nil { - return constants.TaskFinishedMsg{TaskId: taskId, Err: err} - } - repo, err := git.GetRepo(*m.Ctx.RepoPath) - if err != nil { - return constants.TaskFinishedMsg{TaskId: taskId, Err: err} - } - - return constants.TaskFinishedMsg{ - SectionId: 0, - SectionType: SectionType, - TaskId: taskId, - Msg: repoMsg{repo: repo}, - Err: err, - } - }), nil -} diff --git a/ui/components/reposection/commands.go b/ui/components/reposection/commands.go index 245c1037..0c8cb2c1 100644 --- a/ui/components/reposection/commands.go +++ b/ui/components/reposection/commands.go @@ -4,6 +4,7 @@ import ( "fmt" "time" + gitm "github.com/aymanbagabas/git-module" tea "github.com/charmbracelet/bubbletea" "github.com/dlvhdr/gh-dash/v4/data" @@ -22,6 +23,75 @@ type UpdatePRMsg struct { RemovedAssignees *data.Assignees } +func (m *Model) push() (tea.Cmd, error) { + b := m.getCurrBranch() + + taskId := fmt.Sprintf("push_%s_%d", b.Data.Name, time.Now().Unix()) + 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), + State: context.TaskStart, + Error: nil, + } + startCmd := m.Ctx.StartTask(task) + return tea.Batch(startCmd, func() tea.Msg { + var err error + 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"}}}) + } else { + err = gitm.Push(*m.Ctx.RepoPath, b.Data.Remotes[0], b.Data.Name) + } + if err != nil { + return constants.TaskFinishedMsg{TaskId: taskId, Err: err} + } + repo, err := git.GetRepo(*m.Ctx.RepoPath) + if err != nil { + return constants.TaskFinishedMsg{TaskId: taskId, Err: err} + } + + return constants.TaskFinishedMsg{ + SectionId: 0, + SectionType: SectionType, + TaskId: taskId, + Msg: repoMsg{repo: repo}, + Err: err, + } + }), nil +} + +func (m *Model) checkout() (tea.Cmd, error) { + b := m.getCurrBranch() + + taskId := fmt.Sprintf("checkout_%s_%d", b.Data.Name, time.Now().Unix()) + task := context.Task{ + Id: taskId, + StartText: fmt.Sprintf("Checking out branch %s", b.Data.Name), + FinishedText: fmt.Sprintf("Branch %s has been checked out", b.Data.Name), + State: context.TaskStart, + Error: nil, + } + startCmd := m.Ctx.StartTask(task) + return tea.Batch(startCmd, func() tea.Msg { + err := gitm.Checkout(*m.Ctx.RepoPath, b.Data.Name) + if err != nil { + return constants.TaskFinishedMsg{TaskId: taskId, Err: err} + } + repo, err := git.GetRepo(*m.Ctx.RepoPath) + if err != nil { + return constants.TaskFinishedMsg{TaskId: taskId, Err: err} + } + + return constants.TaskFinishedMsg{ + SectionId: 0, + SectionType: SectionType, + TaskId: taskId, + Msg: repoMsg{repo: repo}, + Err: err, + } + }), nil +} + type repoMsg struct { repo *git.Repo err error diff --git a/ui/components/reposection/reposection.go b/ui/components/reposection/reposection.go index cc0ab9ba..de134570 100644 --- a/ui/components/reposection/reposection.go +++ b/ui/components/reposection/reposection.go @@ -72,6 +72,12 @@ func (m *Model) Update(msg tea.Msg) (section.Section, tea.Cmd) { m.Ctx.Error = err } + case key.Matches(msg, keys.BranchKeys.Push): + cmd, err = m.push() + if err != nil { + m.Ctx.Error = err + } + } case repoMsg: diff --git a/ui/keys/branchKeys.go b/ui/keys/branchKeys.go index e471f557..4f9c0429 100644 --- a/ui/keys/branchKeys.go +++ b/ui/keys/branchKeys.go @@ -11,6 +11,7 @@ import ( type BranchKeyMap struct { Checkout key.Binding + Push key.Binding Delete key.Binding ViewPRs key.Binding } @@ -20,6 +21,10 @@ var BranchKeys = BranchKeyMap{ key.WithKeys("C", " "), key.WithHelp("C/space", "checkout"), ), + Push: key.NewBinding( + key.WithKeys("P"), + key.WithHelp("P", "push"), + ), Delete: key.NewBinding( key.WithKeys("d", "backspace"), key.WithHelp("d/backspace", "checkout"), diff --git a/ui/theme/theme.go b/ui/theme/theme.go index 730717b4..91584db9 100644 --- a/ui/theme/theme.go +++ b/ui/theme/theme.go @@ -29,7 +29,7 @@ var DefaultTheme = &Theme{ FaintText: lipgloss.AdaptiveColor{Light: "007", Dark: "245"}, InvertedText: lipgloss.AdaptiveColor{Light: "015", Dark: "236"}, SuccessText: lipgloss.AdaptiveColor{Light: "002", Dark: "002"}, - WarningText: lipgloss.AdaptiveColor{Light: "001", Dark: "001"}, + WarningText: lipgloss.AdaptiveColor{Light: "003", Dark: "003"}, } func ParseTheme(cfg *config.Config) Theme { diff --git a/ui/ui.go b/ui/ui.go index 9ecaf12d..22a694e6 100644 --- a/ui/ui.go +++ b/ui/ui.go @@ -71,7 +71,6 @@ func NewModel(repoPath *string, configPath string) Model { }, } - m.currSectionId = m.getCurrentViewDefaultSection() m.taskSpinner.Style = lipgloss.NewStyle(). Background(m.ctx.Theme.SelectedBackground) @@ -293,7 +292,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.onViewedRowChanged() } - case m.ctx.View == config.PRsView, m.ctx.View == config.RepoView: + case m.ctx.View == config.PRsView: switch { case key.Matches(msg, keys.PRKeys.Approve): m.sidebar.IsOpen = true @@ -430,8 +429,8 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.ctx.Config = &msg.Config m.ctx.Theme = theme.ParseTheme(m.ctx.Config) m.ctx.Styles = context.InitStyles(m.ctx.Theme) - log.Debug("Config loaded", "default view", m.ctx.Config.Defaults.View) m.ctx.View = m.ctx.Config.Defaults.View + m.currSectionId = m.getCurrentViewDefaultSection() m.sidebar.IsOpen = msg.Config.Defaults.Preview.Open m.tabs.UpdateSectionsConfigs(&m.ctx) m.syncMainContentWidth()