Skip to content

Commit

Permalink
Add a sort order menu for local branches
Browse files Browse the repository at this point in the history
  • Loading branch information
hosaka authored and stefanhaller committed Dec 27, 2023
1 parent 1e85c43 commit 36a29f2
Show file tree
Hide file tree
Showing 16 changed files with 146 additions and 56 deletions.
1 change: 1 addition & 0 deletions docs/keybindings/Keybindings_en.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<kbd>M</kbd>: Merge into currently checked out branch
<kbd>f</kbd>: Fast-forward this branch from its upstream
<kbd>T</kbd>: Create tag
<kbd>s</kbd>: Sort order
<kbd>g</kbd>: View reset options
<kbd>R</kbd>: Rename branch
<kbd>u</kbd>: View upstream options
Expand Down
1 change: 1 addition & 0 deletions docs/keybindings/Keybindings_ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<kbd>M</kbd>: 現在のブランチにマージ
<kbd>f</kbd>: Fast-forward this branch from its upstream
<kbd>T</kbd>: タグを作成
<kbd>s</kbd>: 並び替え
<kbd>g</kbd>: View reset options
<kbd>R</kbd>: ブランチ名を変更
<kbd>u</kbd>: View upstream options
Expand Down
1 change: 1 addition & 0 deletions docs/keybindings/Keybindings_ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<kbd>M</kbd>: 현재 브랜치에 병합
<kbd>f</kbd>: Fast-forward this branch from its upstream
<kbd>T</kbd>: 태그를 생성
<kbd>s</kbd>: Sort order
<kbd>g</kbd>: View reset options
<kbd>R</kbd>: 브랜치 이름 변경
<kbd>u</kbd>: View upstream options
Expand Down
1 change: 1 addition & 0 deletions docs/keybindings/Keybindings_nl.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<kbd>M</kbd>: Merge in met huidige checked out branch
<kbd>f</kbd>: Fast-forward deze branch vanaf zijn upstream
<kbd>T</kbd>: Creëer tag
<kbd>s</kbd>: Sort order
<kbd>g</kbd>: Bekijk reset opties
<kbd>R</kbd>: Hernoem branch
<kbd>u</kbd>: View upstream options
Expand Down
1 change: 1 addition & 0 deletions docs/keybindings/Keybindings_pl.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<kbd>M</kbd>: Scal do obecnej gałęzi
<kbd>f</kbd>: Fast-forward this branch from its upstream
<kbd>T</kbd>: Create tag
<kbd>s</kbd>: Sort order
<kbd>g</kbd>: Wyświetl opcje resetu
<kbd>R</kbd>: Rename branch
<kbd>u</kbd>: View upstream options
Expand Down
1 change: 1 addition & 0 deletions docs/keybindings/Keybindings_ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ _Связки клавиш_
<kbd>M</kbd>: Слияние с текущей переключённой веткой
<kbd>f</kbd>: Перемотать эту ветку вперёд из её upstream-ветки
<kbd>T</kbd>: Создать тег
<kbd>s</kbd>: Порядок сортировки
<kbd>g</kbd>: Просмотреть параметры сброса
<kbd>R</kbd>: Переименовать ветку
<kbd>u</kbd>: View upstream options
Expand Down
1 change: 1 addition & 0 deletions docs/keybindings/Keybindings_zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
<kbd>M</kbd>: 合并到当前检出的分支
<kbd>f</kbd>: 从上游快进此分支
<kbd>T</kbd>: 创建标签
<kbd>s</kbd>: Sort order
<kbd>g</kbd>: 查看重置选项
<kbd>R</kbd>: 重命名分支
<kbd>u</kbd>: View upstream options
Expand Down
1 change: 1 addition & 0 deletions docs/keybindings/Keybindings_zh-TW.md
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B_
<kbd>M</kbd>: 合併到當前檢出的分支
<kbd>f</kbd>: 從上游快進此分支
<kbd>T</kbd>: 建立標籤
<kbd>s</kbd>: Sort order
<kbd>g</kbd>: 檢視重設選項
<kbd>R</kbd>: 重新命名分支
<kbd>u</kbd>: View upstream options
Expand Down
73 changes: 48 additions & 25 deletions pkg/commands/git_commands/branch_loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package git_commands
import (
"fmt"
"regexp"
"strconv"
"strings"

"github.com/jesseduffield/generics/set"
Expand Down Expand Up @@ -62,32 +63,33 @@ func NewBranchLoader(
func (self *BranchLoader) Load(reflogCommits []*models.Commit) ([]*models.Branch, error) {
branches := self.obtainBranches()

reflogBranches := self.obtainReflogBranches(reflogCommits)

// loop through reflog branches. If there is a match, merge them, then remove it from the branches and keep it in the reflog branches
branchesWithRecency := make([]*models.Branch, 0)
outer:
for _, reflogBranch := range reflogBranches {
for j, branch := range branches {
if branch.Head {
continue
}
if strings.EqualFold(reflogBranch.Name, branch.Name) {
branch.Recency = reflogBranch.Recency
branchesWithRecency = append(branchesWithRecency, branch)
branches = utils.Remove(branches, j)
continue outer
if self.AppState.LocalBranchSortOrder == "recency" {
reflogBranches := self.obtainReflogBranches(reflogCommits)
// loop through reflog branches. If there is a match, merge them, then remove it from the branches and keep it in the reflog branches
branchesWithRecency := make([]*models.Branch, 0)
outer:
for _, reflogBranch := range reflogBranches {
for j, branch := range branches {
if branch.Head {
continue
}
if strings.EqualFold(reflogBranch.Name, branch.Name) {
branch.Recency = reflogBranch.Recency
branchesWithRecency = append(branchesWithRecency, branch)
branches = utils.Remove(branches, j)
continue outer
}
}
}
}

// Sort branches that don't have a recency value alphabetically
// (we're really doing this for the sake of deterministic behaviour across git versions)
slices.SortFunc(branches, func(a *models.Branch, b *models.Branch) bool {
return a.Name < b.Name
})
// Sort branches that don't have a recency value alphabetically
// (we're really doing this for the sake of deterministic behaviour across git versions)
slices.SortFunc(branches, func(a *models.Branch, b *models.Branch) bool {
return a.Name < b.Name
})

branches = utils.Prepend(branches, branchesWithRecency...)
branches = utils.Prepend(branches, branchesWithRecency...)
}

foundHead := false
for i, branch := range branches {
Expand Down Expand Up @@ -144,7 +146,8 @@ func (self *BranchLoader) obtainBranches() []*models.Branch {
return nil, false
}

return obtainBranch(split), true
storeCommitDateAsRecency := self.AppState.LocalBranchSortOrder != "recency"
return obtainBranch(split, storeCommitDateAsRecency), true
})
}

Expand All @@ -156,8 +159,18 @@ func (self *BranchLoader) getRawBranches() (string, error) {
"%00",
)

var sortOrder string
switch strings.ToLower(self.AppState.LocalBranchSortOrder) {
case "recency", "date":
sortOrder = "-committerdate"
case "alphabetical":
sortOrder = "refname"
default:
sortOrder = "refname"
}

cmdArgs := NewGitCmd("for-each-ref").
Arg("--sort=-committerdate").
Arg(fmt.Sprintf("--sort=%s", sortOrder)).
Arg(fmt.Sprintf("--format=%s", format)).
Arg("refs/heads").
ToArgv()
Expand All @@ -172,22 +185,32 @@ var branchFields = []string{
"upstream:track",
"subject",
"objectname",
"committerdate:unix",
}

// Obtain branch information from parsed line output of getRawBranches()
func obtainBranch(split []string) *models.Branch {
func obtainBranch(split []string, storeCommitDateAsRecency bool) *models.Branch {
headMarker := split[0]
fullName := split[1]
upstreamName := split[2]
track := split[3]
subject := split[4]
commitHash := split[5]
commitDate := split[6]

name := strings.TrimPrefix(fullName, "heads/")
pushables, pullables, gone := parseUpstreamInfo(upstreamName, track)

recency := ""
if storeCommitDateAsRecency {
if unixTimestamp, err := strconv.ParseInt(commitDate, 10, 64); err == nil {
recency = utils.UnixToTimeAgo(unixTimestamp)
}
}

return &models.Branch{
Name: name,
Recency: recency,
Pushables: pushables,
Pullables: pullables,
UpstreamGone: gone,
Expand Down
54 changes: 40 additions & 14 deletions pkg/commands/git_commands/branch_loader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,31 @@ package git_commands

// "*|feat/detect-purge|origin/feat/detect-purge|[ahead 1]"
import (
"strconv"
"testing"
"time"

"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/stretchr/testify/assert"
)

func TestObtainBranch(t *testing.T) {
type scenario struct {
testName string
input []string
expectedBranch *models.Branch
testName string
input []string
storeCommitDateAsRecency bool
expectedBranch *models.Branch
}

// Use a time stamp of 2 1/2 hours ago, resulting in a recency string of "2h"
now := time.Now().Unix()
timeStamp := strconv.Itoa(int(now - 2.5*60*60))

scenarios := []scenario{
{
testName: "TrimHeads",
input: []string{"", "heads/a_branch", "", "", "subject", "123"},
testName: "TrimHeads",
input: []string{"", "heads/a_branch", "", "", "subject", "123", timeStamp},
storeCommitDateAsRecency: false,
expectedBranch: &models.Branch{
Name: "a_branch",
Pushables: "?",
Expand All @@ -29,8 +37,9 @@ func TestObtainBranch(t *testing.T) {
},
},
{
testName: "NoUpstream",
input: []string{"", "a_branch", "", "", "subject", "123"},
testName: "NoUpstream",
input: []string{"", "a_branch", "", "", "subject", "123", timeStamp},
storeCommitDateAsRecency: false,
expectedBranch: &models.Branch{
Name: "a_branch",
Pushables: "?",
Expand All @@ -41,8 +50,9 @@ func TestObtainBranch(t *testing.T) {
},
},
{
testName: "IsHead",
input: []string{"*", "a_branch", "", "", "subject", "123"},
testName: "IsHead",
input: []string{"*", "a_branch", "", "", "subject", "123", timeStamp},
storeCommitDateAsRecency: false,
expectedBranch: &models.Branch{
Name: "a_branch",
Pushables: "?",
Expand All @@ -53,8 +63,9 @@ func TestObtainBranch(t *testing.T) {
},
},
{
testName: "IsBehindAndAhead",
input: []string{"", "a_branch", "a_remote/a_branch", "[behind 2, ahead 3]", "subject", "123"},
testName: "IsBehindAndAhead",
input: []string{"", "a_branch", "a_remote/a_branch", "[behind 2, ahead 3]", "subject", "123", timeStamp},
storeCommitDateAsRecency: false,
expectedBranch: &models.Branch{
Name: "a_branch",
Pushables: "3",
Expand All @@ -65,8 +76,9 @@ func TestObtainBranch(t *testing.T) {
},
},
{
testName: "RemoteBranchIsGone",
input: []string{"", "a_branch", "a_remote/a_branch", "[gone]", "subject", "123"},
testName: "RemoteBranchIsGone",
input: []string{"", "a_branch", "a_remote/a_branch", "[gone]", "subject", "123", timeStamp},
storeCommitDateAsRecency: false,
expectedBranch: &models.Branch{
Name: "a_branch",
UpstreamGone: true,
Expand All @@ -77,11 +89,25 @@ func TestObtainBranch(t *testing.T) {
CommitHash: "123",
},
},
{
testName: "WithCommitDateAsRecency",
input: []string{"", "a_branch", "", "", "subject", "123", timeStamp},
storeCommitDateAsRecency: true,
expectedBranch: &models.Branch{
Name: "a_branch",
Recency: "2h",
Pushables: "?",
Pullables: "?",
Head: false,
Subject: "subject",
CommitHash: "123",
},
},
}

for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
branch := obtainBranch(s.input)
branch := obtainBranch(s.input, s.storeCommitDateAsRecency)
assert.EqualValues(t, s.expectedBranch, branch)
})
}
Expand Down
2 changes: 2 additions & 0 deletions pkg/config/app_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@ type AppState struct {
HideCommandLog bool
IgnoreWhitespaceInDiffView bool
DiffContextSize int
LocalBranchSortOrder string
RemoteBranchSortOrder string
}

Expand All @@ -332,6 +333,7 @@ func getDefaultAppState() *AppState {
RecentRepos: []string{},
StartupPopupVersion: 0,
DiffContextSize: 3,
LocalBranchSortOrder: "recency",
RemoteBranchSortOrder: "alphabetical",
}
}
Expand Down
18 changes: 18 additions & 0 deletions pkg/gui/controllers/branches_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,12 @@ func (self *BranchesController) GetKeybindings(opts types.KeybindingsOpts) []*ty
Handler: self.checkSelected(self.createTag),
Description: self.c.Tr.CreateTag,
},
{
Key: opts.GetKey(opts.Config.Branches.SortOrder),
Handler: self.createSortMenu,
Description: self.c.Tr.SortOrder,
OpensMenu: true,
},
{
Key: opts.GetKey(opts.Config.Commits.ViewResetOptions),
Handler: self.checkSelected(self.createResetMenu),
Expand Down Expand Up @@ -617,6 +623,18 @@ func (self *BranchesController) createTag(branch *models.Branch) error {
return self.c.Helpers().Tags.OpenCreateTagPrompt(branch.FullRefName(), func() {})
}

func (self *BranchesController) createSortMenu() error {
return self.c.Helpers().Refs.CreateSortOrderMenu([]string{"recency", "alphabetical", "date"}, func(sortOrder string) error {
if self.c.GetAppState().LocalBranchSortOrder != sortOrder {
self.c.GetAppState().LocalBranchSortOrder = sortOrder
self.c.SaveAppStateAndLogError()
self.c.Contexts().Branches.SetSelectedLineIdx(0)
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}})
}
return nil
})
}

func (self *BranchesController) createResetMenu(selectedBranch *models.Branch) error {
return self.c.Helpers().Refs.CreateGitResetMenu(selectedBranch.Name)
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/gui/controllers/helpers/refresh_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@ func (self *RefreshHelper) refreshBranches(refreshWorktrees bool) {
defer self.c.Mutexes().RefreshingBranchesMutex.Unlock()

reflogCommits := self.c.Model().FilteredReflogCommits
if self.c.Modes().Filtering.Active() {
if self.c.Modes().Filtering.Active() && self.c.AppState.LocalBranchSortOrder == "recency" {
// in filter mode we filter our reflog commits to just those containing the path
// however we need all the reflog entries to populate the recencies of our branches
// which allows us to order them correctly. So if we're filtering we'll just
Expand Down
Loading

0 comments on commit 36a29f2

Please sign in to comment.