Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 19 additions & 6 deletions cmd/worktree.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (

var (
wtBaseBranch string
wtDupBase string
wtForce bool
wtDeleteBranch bool
wtDryRun bool
Expand All @@ -40,20 +41,31 @@ bare repository (.bare) + worktree pattern.
}

var wtAddCmd = &cobra.Command{
Use: "add <name> [name...]",
Use: "add [name...]",
Short: "Create new worktrees with new branches",
Long: `Create one or more worktrees with new branches.

The branch name will be the same as the worktree directory name.
When no name is given but -b is set, the worktree name is derived from
the base branch (useful for checking out an existing branch).

Examples:
gmc wt add feature-login # Create one worktree
gmc wt add feat-a feat-b feat-c # Create multiple worktrees
gmc wt add feature-login -b main # Create based on main branch
gmc wt add feature-login --sync # Sync base branch before add
gmc wt add hotfix-bug123 -b release`,
Args: cobra.MinimumNArgs(1),
gmc wt add hotfix-bug123 -b release
gmc wt add -b feat/existing-branch # Name derived from -b`,
Args: func(_ *cobra.Command, args []string) error {
if len(args) == 0 && strings.TrimSpace(wtBaseBranch) == "" {
return errors.New("requires at least 1 arg or -b/--base flag")
}
return nil
},
RunE: func(_ *cobra.Command, args []string) error {
if len(args) == 0 {
args = []string{wtBaseBranch}
}
wtClient := newWorktreeClient()
return runWorktreeAdd(wtClient, args)
},
Expand Down Expand Up @@ -210,13 +222,14 @@ func init() {
wtCloneCmd.Flags().StringVar(&wtProjectName, "name", "", "Custom project directory name")

// Flags for dup command
wtDupCmd.Flags().StringVarP(&wtBaseBranch, "base", "b", "main", "Base branch to create from")
wtDupCmd.Flags().StringVarP(&wtDupBase, "base", "b", "main", "Base branch to create from")

// Flags for prune command
wtPruneCmd.Flags().StringVarP(&wtPruneBase, "base", "b", "", "Base branch to check merge status against")
wtPruneCmd.Flags().BoolVarP(&wtPruneForce, "force", "f", false, "Force removal even if worktree is dirty")
wtPruneCmd.Flags().BoolVar(&wtPruneDryRun, "dry-run", false, "Preview what would be removed without making changes")
wtPruneCmd.Flags().BoolVar(&wtPrunePRAware, "pr-aware", false, "Check GitHub PR state before pruning (requires gh CLI)")
wtPruneCmd.Flags().BoolVar(&wtPrunePRAware, "pr-aware", false,
"Check GitHub PR state before pruning (requires gh CLI)")

// Flags for pr-review command
wtPrReviewCmd.Flags().StringVarP(&prRemote, "remote", "r", "",
Expand Down Expand Up @@ -541,7 +554,7 @@ func printWorktreeJSON(wtClient *worktree.Client, worktrees []worktree.Info) err

func runWorktreeDup(wtClient *worktree.Client, args []string) error {
opts := worktree.DupOptions{
BaseBranch: wtBaseBranch,
BaseBranch: wtDupBase,
Count: 2,
}

Expand Down
7 changes: 3 additions & 4 deletions cmd/worktree_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ func TestRunWorktreeDefault_ShowsWorktreesInNonBareRepo(t *testing.T) {

func TestWtHookRemoveCmd_RejectsTrailingCharactersInIndex(t *testing.T) {
repoDir := initCmdTestRepo(t)
require.NoError(t, os.WriteFile(filepath.Join(repoDir, ".git", "gmc-share.yml"), []byte("hooks:\n - cmd: echo ok\n"), 0o644))
cfgPath := filepath.Join(repoDir, ".git", "gmc-share.yml")
require.NoError(t, os.WriteFile(cfgPath, []byte("hooks:\n - cmd: echo ok\n"), 0o644))

oldCwd, err := os.Getwd()
require.NoError(t, err)
Expand Down Expand Up @@ -99,9 +100,7 @@ func runGitCmd(t *testing.T, dir string, args ...string) string {
return string(output)
}

var execCommand = func(name string, args ...string) *exec.Cmd {
return exec.Command(name, args...)
}
var execCommand = exec.Command

func TestRemoveAll_SkipsProtected(t *testing.T) {
repoDir := initCmdTestRepo(t)
Expand Down
10 changes: 7 additions & 3 deletions internal/worktree/prune.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ var ghRunFunc = ghRunDefault

func ghRunDefault(repoDir string, args ...string) ([]byte, error) {
if _, err := exec.LookPath("gh"); err != nil {
return nil, fmt.Errorf("gh CLI not found: install from https://cli.github.com")
return nil, errors.New("gh CLI not found: install from https://cli.github.com")
}
cmd := exec.Command("gh", args...)
cmd.Dir = repoDir
Expand Down Expand Up @@ -167,7 +167,9 @@ func ghPRStates(repoDir string) (map[string]ghPRInfo, error) {
return m, nil
}

func (c *Client) prunePRAware(opts PruneOptions, candidates []pruneCandidate, repoDir string, result PruneResult) (PruneResult, error) {
func (c *Client) prunePRAware(
opts PruneOptions, candidates []pruneCandidate, repoDir string, result PruneResult,
) (PruneResult, error) {
prMap, err := ghPRStates(repoDir)
if err != nil {
return result, err
Expand Down Expand Up @@ -227,7 +229,9 @@ func (c *Client) prunePRAware(opts PruneOptions, candidates []pruneCandidate, re
return result, nil
}

func (c *Client) pruneClassic(opts PruneOptions, candidates []pruneCandidate, root, baseBranch, repoDir string, result PruneResult) (PruneResult, error) {
func (c *Client) pruneClassic(
opts PruneOptions, candidates []pruneCandidate, root, baseBranch, repoDir string, result PruneResult,
) (PruneResult, error) {
var prunedAny bool

for _, cand := range candidates {
Expand Down
4 changes: 3 additions & 1 deletion internal/worktree/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,9 @@ func (c *Client) NormalizeSharedResourcePath(path string) (string, error) {
return trimmed, nil
}

func (c *Client) resolveSharedPaths(repoRoot, targetRoot string, res SharedResource) (srcPath string, targetPath string, skip bool, err error) {
func (c *Client) resolveSharedPaths(
repoRoot, targetRoot string, res SharedResource,
) (srcPath string, targetPath string, skip bool, err error) {
targetPath, err = sanitizeTargetRelativePath(res.Path)
if err != nil {
return "", "", false, err
Expand Down
Loading