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
19 changes: 12 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,15 +98,15 @@ Default destination root is `~/Documents/worktrees`, generating:
## 🆕 Recent CLI adjustments

- Every subcommand supports command-scoped help via `--help` and `-h`.
- `create` keeps package selection flexible:
- the interactive stepper still includes the package step,
- non-interactive runs still support `--package` and `--package-base`.
- `create --no-package` is now an explicit root-only mode:
- package selection is skipped in interactive mode,
- `--no-package` conflicts with `--package` and `--package-base` (fail-fast input error).
- `add-repo` is the command for attaching repositories after a workspace already exists.
- Before syncing branches from `origin` during `create`, the CLI now asks for confirmation:
- **Yes** → sync from `origin` and continue with worktree creation.
- **No** → skip remote sync entirely and continue from local refs.
- The package step in the `create` stepper is never skipped.
If no package candidates are found, the step shows the empty-state message and still waits for Enter.
- `list --global` always uses global registry scope, regardless of current directory.
- `list --global --all` includes unmanaged worktrees across all globally selected repositories.

## ⚙️ Advanced Usage

Expand All @@ -118,6 +118,8 @@ Default destination root is `~/Documents/worktrees`, generating:

For automation/non-interactive runs, `create --non-interactive` requires explicit `--yes` and `--root-repo`.
If the target branch already exists, non-interactive runs also require `--reuse-existing-branch`.
Use `--no-package` when you need a root-only workspace and no package metadata flow.
`--no-package` cannot be combined with `--package` or `--package-base`.
In interactive mode, after selecting **Apply changes**, `create` asks whether local branches should be synced from `origin` before worktree creation.
If the answer is **Yes**, `create` syncs before worktree creation and fails fast if sync cannot be completed.
If the answer is **No**, `create` skips remote sync and continues from local refs.
Expand Down Expand Up @@ -173,6 +175,7 @@ flutree create <name> [options]
| `--yes` | boolean | `false` | Acknowledge dry plan automatically in non-interactive mode |
| `--non-interactive` | boolean | `false` | Disable prompts |
| `--reuse-existing-branch` | boolean | `false` | Reuse existing local branch in non-interactive mode |
| `--no-package` | boolean | `false` | Root-only mode (skip package selection and package metadata) |
| `--package` | string | | Package repository selector (repeatable) |
| `--package-base` | string | | Override package base branch as `<selector>=<branch>` (repeatable) |
| `--copy-root-file` | string | | Extra root-level file/pattern to copy into each worktree (repeatable). By default `.env` and `.env.*` are copied when present |
Expand All @@ -196,16 +199,18 @@ flutree add-repo <workspace> [options]

### list

Lists managed worktrees (scoped to current repo when available, otherwise global registry scope).
Lists managed worktrees. By default, scope is current repo when available (fallback to global outside a repo).
Use `--global` to force global registry scope from any location.

Usage:
```
flutree list [--all]
flutree list [options]
```

| Flag | Type | Default | Description |
|------|------|---------|-------------|
| `--all` | boolean | `false` | Include unmanaged Git worktrees |
| `--global` | boolean | `false` | Force global registry scope regardless of current directory |

### complete

Expand Down
21 changes: 19 additions & 2 deletions cmd/flutree/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ func main() {
func runList(args []string) error {
fs := newFlagSet("list", printListHelp)
showAll := fs.Bool("all", false, "Include unmanaged Git worktrees.")
globalScope := fs.Bool("global", false, "List managed worktrees across all registered repositories.")
if len(args) > 0 && isHelpToken(args[0]) {
printListHelp()
return nil
Expand All @@ -68,7 +69,7 @@ func runList(args []string) error {
}

service := app.NewListService(&infraGit.Gateway{}, registry.NewDefault())
rows, err := service.Run(*showAll)
rows, err := service.Run(domain.ListInput{ShowAll: *showAll, GlobalScope: *globalScope})
if err != nil {
return err
}
Expand Down Expand Up @@ -122,6 +123,7 @@ func runCreate(args []string) error {
yes := fs.Bool("yes", false, "Acknowledge dry plan automatically in non-interactive mode.")
nonInteractive := fs.Bool("non-interactive", false, "Disable prompts.")
reuseExistingBranch := fs.Bool("reuse-existing-branch", false, "Allow non-interactive reuse when target branch already exists.")
noPackage := fs.Bool("no-package", false, "Create root-only worktree without package selection.")

var packages multiFlag
var packageBase multiFlag
Expand All @@ -145,6 +147,15 @@ func runCreate(args []string) error {
if helpRequested {
return nil
}
if *noPackage && len(packages) > 0 && len(packageBase) > 0 {
return domain.NewError(domain.CategoryInput, 2, "Flag --no-package cannot be combined with --package or --package-base.", "Remove --no-package or remove package flags.", nil)
}
if *noPackage && len(packages) > 0 {
return domain.NewError(domain.CategoryInput, 2, "Flag --no-package cannot be combined with --package.", "Remove --no-package or remove --package.", nil)
}
if *noPackage && len(packageBase) > 0 {
return domain.NewError(domain.CategoryInput, 2, "Flag --no-package cannot be combined with --package-base.", "Remove --no-package or remove --package-base.", nil)
}
if *nonInteractive && strings.TrimSpace(*rootRepo) == "" {
return domain.NewError(domain.CategoryInput, 2, "Non-interactive mode requires explicit root repository selection.", "Pass --root-repo with a discovered repository name or path.", nil)
}
Expand Down Expand Up @@ -175,6 +186,7 @@ func runCreate(args []string) error {
BaseBranch: *baseBranch,
ExecutionScope: *scope,
RootSelector: *rootRepo,
NoPackage: *noPackage,
PackageSelectors: packages,
PackageBaseBranch: baseMap,
RootFiles: copyRootFiles,
Expand All @@ -197,6 +209,7 @@ func runCreate(args []string) error {
BaseBranch: *baseBranch,
GenerateWorkspace: genWorkspace,
RootSelector: *rootRepo,
NoPackage: *noPackage,
PackageSelectors: packages,
PackageBaseBranch: baseMap,
}, repos)
Expand All @@ -211,6 +224,7 @@ func runCreate(args []string) error {
createInput.Branch = wizardResult.Branch
createInput.BaseBranch = wizardResult.BaseBranch
createInput.RootSelector = wizardResult.RootSelector
createInput.NoPackage = wizardResult.NoPackage
createInput.PackageSelectors = wizardResult.PackageSelectors
createInput.PackageBaseBranch = wizardResult.PackageBaseBranch
createInput.GenerateWorkspace = wizardResult.GenerateWorkspace
Expand All @@ -225,6 +239,7 @@ func runCreate(args []string) error {
BaseBranch: createInput.BaseBranch,
ExecutionScope: createInput.ExecutionScope,
RootSelector: createInput.RootSelector,
NoPackage: createInput.NoPackage,
PackageSelectors: createInput.PackageSelectors,
PackageBaseBranch: createInput.PackageBaseBranch,
RootFiles: createInput.RootFiles,
Expand Down Expand Up @@ -453,7 +468,7 @@ func printHelp() {
fmt.Println(" flutree version")
fmt.Println(" flutree create <name> [options]")
fmt.Println(" flutree add-repo <workspace> [options]")
fmt.Println(" flutree list [--all]")
fmt.Println(" flutree list [options]")
fmt.Println(" flutree complete <name> [options]")
fmt.Println(" flutree pubget <name> [--force]")
fmt.Println(" flutree update [--check|--apply]")
Expand Down Expand Up @@ -526,6 +541,7 @@ func printCreateHelp() {
fmt.Println(" --yes Acknowledge dry plan automatically in non-interactive mode")
fmt.Println(" --non-interactive Disable prompts")
fmt.Println(" --reuse-existing-branch Allow non-interactive reuse when target branch already exists")
fmt.Println(" --no-package Create root-only worktree without package selection")
fmt.Println(" --copy-root-file <pattern> Extra root-level file/pattern to copy into each worktree (repeatable)")
fmt.Println(" --package <selector> Package repository selector (repeatable)")
fmt.Println(" --package-base <sel>=<branch> Override package base branch (repeatable)")
Expand All @@ -551,6 +567,7 @@ func printListHelp() {
fmt.Println("")
fmt.Println("Options:")
fmt.Println(" --all Include unmanaged Git worktrees")
fmt.Println(" --global List managed worktrees across all registered repositories")
fmt.Println(" -h, --help Show this help")
}

Expand Down
15 changes: 13 additions & 2 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,15 @@ go test ./...
- Creates a new worktree and branch.
- Persists metadata in the global registry.
- Runs preflight checkpoints before any mutation is applied.
- `--no-package` enables explicit root-only creation and skips package metadata flow.
- `--no-package` conflicts with `--package` and `--package-base`.
- If a target branch already exists, asks for explicit reuse confirmation (or requires `--reuse-existing-branch` in non-interactive mode).
- Syncs the configured base branch before any new branch worktree creation.

`flutree list [--all]`
`flutree list [--all] [--global]`
- Lists managed entries for the current repository when running inside a repo.
- If running outside a repo, it falls back to the global registry view.
- `--global` forces global registry scope from any current directory.
- `--all` also includes unmanaged Git worktrees discovered from `git worktree list --porcelain` for discovered managed repos.

`flutree complete NAME [OPTIONS]`
Expand All @@ -73,6 +76,7 @@ Options:
- `--base-branch TEXT`: source branch for root worktree creation (default: `main`).
- `--scope PATH`: execution directory scope used to discover Flutter repositories (default: current directory).
- `--root-repo TEXT`: explicit root repository selector for non-interactive usage.
- `--no-package`: explicit root-only mode; skip package selection and package metadata prompts.
- `--package, -p TEXT`: explicit package repository selector (repeatable).
- `--package-base TEXT`: per-package base branch override in `<selector>=<branch>` format (repeatable, default `develop`).
- `--workspace/--no-workspace`: enable or disable VSCode `.code-workspace` generation (enabled by default).
Expand All @@ -88,6 +92,7 @@ Examples:

```bash
flutree create auth-fix --branch feature/auth-fix --scope .
flutree create auth-fix --scope ~/code --root-repo app-root --no-package --yes --non-interactive
flutree create auth-fix --scope ~/code --root-repo app-root --package package-core --package package-ui
flutree create auth-fix --scope ~/code --root-repo app-root --package package-core --package-base package-core=develop --yes --non-interactive
```
Expand All @@ -101,7 +106,7 @@ Generated worktrees are grouped into:
- packages: `~/Documents/worktrees/<worktree-name-slug>/packages/<package-repo-folder>/`

Package override output:
- `flutree create` writes exactly one `pubspec_override.yaml` in the selected root worktree.
- `flutree create` writes exactly one `pubspec_overrides.yaml` in the selected root worktree.
- dependency paths target selected package worktree paths.
- `pubspec.yaml` is never modified by this workflow.

Expand All @@ -123,6 +128,12 @@ VSCode workspace output (MVP):

Options:
- `--all`: include unmanaged worktrees in the output table.
- `--global`: force global registry scope from any current directory.

Scope behavior:
- `flutree list`: repo-scoped when running inside a repository, global fallback outside repositories.
- `flutree list --global`: always global scope, independent of CWD.
- `flutree list --global --all`: global scope plus unmanaged rows across all selected repositories.

Output fields:
- `Name`: managed name, or `-` for unmanaged rows.
Expand Down
Loading
Loading