From a7def9591614ee7b218897fa93a78f221a456a8a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 30 Apr 2026 06:17:26 +0000 Subject: [PATCH 1/2] Add worktree support: RunWorktree() and worktree subcommand Agent-Logs-Url: https://github.com/bubunyo/git-rbs/sessions/857cb674-c0c2-44f6-8488-875b639e071e Co-authored-by: bubunyo <5031930+bubunyo@users.noreply.github.com> --- cmd/rbs/main.go | 6 ++++ rbs.go | 91 +++++++++++++++++++++++++++++++++++++------------ 2 files changed, 76 insertions(+), 21 deletions(-) diff --git a/cmd/rbs/main.go b/cmd/rbs/main.go index 848f91e..8caea1d 100644 --- a/cmd/rbs/main.go +++ b/cmd/rbs/main.go @@ -1,9 +1,15 @@ package main import ( + "os" + rbs "github.com/bubunyo/git-rbs" ) func main() { + if len(os.Args) > 1 && os.Args[1] == "worktree" { + rbs.RunWorktree() + return + } rbs.Run() } diff --git a/rbs.go b/rbs.go index 2d495da..32c7d8a 100644 --- a/rbs.go +++ b/rbs.go @@ -3,6 +3,7 @@ package rbs import ( "fmt" "os/exec" + "path/filepath" "strings" "github.com/manifoldco/promptui" @@ -16,9 +17,7 @@ type branch struct { Subject string } -func Run() { - // command - git branch --sort=-committerdate - // --format="%(committerdate:unix)|%(committerdate:relative)|%(refname:short)|%(authorname)|%(contents:subject)|%(objectname:short)" +func listBranches() ([]branch, error) { resFormat := `%(objectname:short)|%(refname:short)|%(committerdate:relative)|%(upstream:track)|%(contents:subject)` cmd := exec.Command("git", "branch", "--sort=-committerdate", @@ -26,45 +25,42 @@ func Run() { stdout, err := cmd.Output() if err != nil { - fmt.Println(err.Error()) - return + return nil, err } out := strings.Split(string(stdout), "\n") - branches := []branch{} - - var branchNameMaxLen int - for _, v := range out { v := strings.TrimSpace(v) if v == "" { continue } p := strings.Split(v, "|") - branches = append(branches, branch{p[0], p[1], p[2], p[3], p[4]}) - if len(p[1]) > branchNameMaxLen { - branchNameMaxLen = len(p[1]) + if len(p) < 5 { + continue } + branches = append(branches, branch{p[0], p[1], p[2], p[3], p[4]}) } + return branches, nil +} +func branchSelectPrompt(label string, branches []branch, selectedTpl string) (int, error) { templates := &promptui.SelectTemplates{ Label: "{{ . }}?", Active: "\U0000279C [{{ .ObjectName | yellow }}] {{ .Name | green }} ({{ .RelativeTime | cyan }}) -{{if ne .Track \"\" }} {{ .Track | magenta }}{{end}} {{ .Subject }}", Inactive: " [{{ .ObjectName | yellow | faint }}] {{ .Name | green | faint }} ({{ .RelativeTime | cyan | faint }}) -{{if ne .Track \"\" }} {{ .Track | magenta | faint }}{{end}} {{ .Subject | faint }}", - Selected: "git checkout {{ .Name | green | cyan }}", + Selected: selectedTpl, } searcher := func(input string, index int) bool { - branch := branches[index] - name := strings.Replace(strings.ToLower(branch.Name), " ", "", -1) + b := branches[index] + name := strings.Replace(strings.ToLower(b.Name), " ", "", -1) input = strings.Replace(strings.ToLower(input), " ", "", -1) - return strings.Contains(name, input) } prompt := promptui.Select{ - Label: "Select git branch:", + Label: label, Items: branches, Templates: templates, Size: 10, @@ -72,18 +68,71 @@ func Run() { } i, _, err := prompt.Run() + return i, err +} + +func Run() { + branches, err := listBranches() + if err != nil { + fmt.Println(err.Error()) + return + } + + i, err := branchSelectPrompt("Select git branch:", branches, "git checkout {{ .Name | green | cyan }}") + if err != nil { + fmt.Printf("Prompt failed %v\n", err) + return + } + + cmd := exec.Command("git", "checkout", branches[i].Name) + out, err := cmd.CombinedOutput() + if err != nil { + fmt.Printf("Error: %s", string(out)) + } else { + fmt.Print(string(out)) + } +} + +// RunWorktree presents an interactive branch selector and adds the chosen +// branch as a new git worktree at ../ (relative to the repo +// root), replacing forward-slashes in the branch name with dashes so that +// the directory name is always valid. +func RunWorktree() { + branches, err := listBranches() + if err != nil { + fmt.Println(err.Error()) + return + } + i, err := branchSelectPrompt("Select branch for new worktree:", branches, "git worktree add {{ .Name | green | cyan }}") if err != nil { fmt.Printf("Prompt failed %v\n", err) return } - cmd = exec.Command("git", "checkout", branches[i].Name) + selected := branches[i] + + // Build the worktree path: sibling directory named after the branch + // (slashes replaced with dashes to keep the path component simple). + dirName := strings.ReplaceAll(selected.Name, "/", "-") + worktreePath := filepath.Join("..", dirName) + + prompt := promptui.Prompt{ + Label: "Worktree path", + Default: worktreePath, + } + path, err := prompt.Run() + if err != nil { + fmt.Printf("Prompt failed %v\n", err) + return + } - stdout, err = cmd.Output() + cmd := exec.Command("git", "worktree", "add", path, selected.Name) + out, err := cmd.CombinedOutput() if err != nil { - fmt.Printf("Error: %s", err.Error()) + fmt.Printf("Error: %s", string(out)) } else { - fmt.Print(string(stdout)) + fmt.Printf("Worktree created at %s\n", path) + fmt.Print(string(out)) } } From 375294a800bc89a16e917aa199eb06f1d6b03515 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 30 Apr 2026 06:23:40 +0000 Subject: [PATCH 2/2] Auto-derive worktree path from branch name, remove manual path prompt Agent-Logs-Url: https://github.com/bubunyo/git-rbs/sessions/bec9bf41-b2e6-4a47-a915-1755fc867742 Co-authored-by: bubunyo <5031930+bubunyo@users.noreply.github.com> --- rbs.go | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/rbs.go b/rbs.go index 32c7d8a..aae3c33 100644 --- a/rbs.go +++ b/rbs.go @@ -93,10 +93,10 @@ func Run() { } } -// RunWorktree presents an interactive branch selector and adds the chosen -// branch as a new git worktree at ../ (relative to the repo -// root), replacing forward-slashes in the branch name with dashes so that -// the directory name is always valid. +// RunWorktree presents an interactive branch selector and automatically adds +// the chosen branch as a new git worktree at ../ (a sibling +// directory next to the current repo), replacing forward-slashes in the +// branch name with dashes so that the directory name is always valid. func RunWorktree() { branches, err := listBranches() if err != nil { @@ -112,27 +112,17 @@ func RunWorktree() { selected := branches[i] - // Build the worktree path: sibling directory named after the branch - // (slashes replaced with dashes to keep the path component simple). + // Automatically derive the worktree path: sibling directory named after + // the branch (slashes replaced with dashes to keep the path component simple). dirName := strings.ReplaceAll(selected.Name, "/", "-") worktreePath := filepath.Join("..", dirName) - prompt := promptui.Prompt{ - Label: "Worktree path", - Default: worktreePath, - } - path, err := prompt.Run() - if err != nil { - fmt.Printf("Prompt failed %v\n", err) - return - } - - cmd := exec.Command("git", "worktree", "add", path, selected.Name) + cmd := exec.Command("git", "worktree", "add", worktreePath, selected.Name) out, err := cmd.CombinedOutput() if err != nil { fmt.Printf("Error: %s", string(out)) } else { - fmt.Printf("Worktree created at %s\n", path) + fmt.Printf("Worktree created at %s\n", worktreePath) fmt.Print(string(out)) } }