pf 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/publish.yml +33 -0
- package/build.mjs +24 -0
- package/commands/completion.ts +197 -0
- package/commands/ls.ts +49 -0
- package/commands/new.ts +82 -0
- package/commands/open.ts +61 -0
- package/commands/rm.ts +61 -0
- package/dist/index.js +2316 -0
- package/go/Makefile +37 -0
- package/go/cmd/completion.go +183 -0
- package/go/cmd/list.go +126 -0
- package/go/cmd/new.go +146 -0
- package/go/cmd/open.go +128 -0
- package/go/cmd/rm.go +88 -0
- package/go/cmd/root.go +74 -0
- package/go/cmd/styles.go +9 -0
- package/go/cmd/version.go +27 -0
- package/go/cmd/where.go +57 -0
- package/go/go.mod +37 -0
- package/go/go.sum +73 -0
- package/go/internal/store/store.go +124 -0
- package/go/main.go +7 -0
- package/index.ts +107 -0
- package/package.json +20 -22
- package/store.ts +67 -0
- package/tsconfig.json +16 -0
- package/utils.ts +108 -0
- package/.npmignore +0 -1
- package/index.js +0 -90
- package/test.js +0 -14
package/go/cmd/root.go
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
package cmd
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"os"
|
|
6
|
+
"os/exec"
|
|
7
|
+
"path/filepath"
|
|
8
|
+
"strings"
|
|
9
|
+
|
|
10
|
+
"github.com/pullfrog/cli/internal/store"
|
|
11
|
+
"github.com/spf13/cobra"
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
var rootCmd = &cobra.Command{
|
|
15
|
+
Use: "pf",
|
|
16
|
+
Short: "pf - git worktree utility",
|
|
17
|
+
Long: `pf is a git worktree utility for managing worktrees efficiently.`,
|
|
18
|
+
Run: func(cmd *cobra.Command, args []string) {
|
|
19
|
+
if v, _ := cmd.Flags().GetBool("version"); v {
|
|
20
|
+
fmt.Printf("pf %s\n", Version)
|
|
21
|
+
return
|
|
22
|
+
}
|
|
23
|
+
printCurrentContext()
|
|
24
|
+
fmt.Println()
|
|
25
|
+
cmd.Help()
|
|
26
|
+
},
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
func printCurrentContext() {
|
|
30
|
+
// Get current branch
|
|
31
|
+
branchCmd := exec.Command("git", "branch", "--show-current")
|
|
32
|
+
branchOutput, err := branchCmd.Output()
|
|
33
|
+
if err != nil {
|
|
34
|
+
fmt.Println("Not in a git repository")
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
branch := strings.TrimSpace(string(branchOutput))
|
|
38
|
+
|
|
39
|
+
// Get current worktree path
|
|
40
|
+
cwd, err := os.Getwd()
|
|
41
|
+
if err != nil {
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Check if we're in a pf worktree
|
|
46
|
+
s, _ := store.Load()
|
|
47
|
+
var worktreeName string
|
|
48
|
+
if s != nil {
|
|
49
|
+
for _, wt := range s.Worktrees {
|
|
50
|
+
absPath, _ := filepath.Abs(wt.Path)
|
|
51
|
+
if strings.HasPrefix(cwd, absPath) {
|
|
52
|
+
worktreeName = wt.Name
|
|
53
|
+
break
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if worktreeName != "" {
|
|
59
|
+
fmt.Printf("worktree: %s git:(%s)\n", worktreeName, branch)
|
|
60
|
+
} else {
|
|
61
|
+
fmt.Printf("main git:(%s)\n", branch)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
func init() {
|
|
66
|
+
rootCmd.Flags().BoolP("version", "v", false, "Print version information")
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
func Execute() {
|
|
70
|
+
if err := rootCmd.Execute(); err != nil {
|
|
71
|
+
fmt.Fprintln(os.Stderr, err)
|
|
72
|
+
os.Exit(1)
|
|
73
|
+
}
|
|
74
|
+
}
|
package/go/cmd/styles.go
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
package cmd
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
|
|
6
|
+
"github.com/spf13/cobra"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
var (
|
|
10
|
+
Version = "dev"
|
|
11
|
+
GitCommit = "none"
|
|
12
|
+
BuildDate = "unknown"
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
var versionCmd = &cobra.Command{
|
|
16
|
+
Use: "version",
|
|
17
|
+
Short: "Print version information",
|
|
18
|
+
Run: func(cmd *cobra.Command, args []string) {
|
|
19
|
+
fmt.Printf("pf %s\n", Version)
|
|
20
|
+
fmt.Printf(" Git commit: %s\n", GitCommit)
|
|
21
|
+
fmt.Printf(" Built: %s\n", BuildDate)
|
|
22
|
+
},
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
func init() {
|
|
26
|
+
rootCmd.AddCommand(versionCmd)
|
|
27
|
+
}
|
package/go/cmd/where.go
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
package cmd
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"os"
|
|
6
|
+
"os/exec"
|
|
7
|
+
"strings"
|
|
8
|
+
|
|
9
|
+
"github.com/spf13/cobra"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
var whereCmd = &cobra.Command{
|
|
13
|
+
Use: "where",
|
|
14
|
+
Short: "Show current worktree and branch",
|
|
15
|
+
Hidden: true,
|
|
16
|
+
RunE: runWhere,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
func init() {
|
|
20
|
+
rootCmd.AddCommand(whereCmd)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
func runWhere(cmd *cobra.Command, args []string) error {
|
|
24
|
+
// Get current branch
|
|
25
|
+
branchCmd := exec.Command("git", "branch", "--show-current")
|
|
26
|
+
branchOutput, err := branchCmd.Output()
|
|
27
|
+
if err != nil {
|
|
28
|
+
return fmt.Errorf("failed to get current branch: %w", err)
|
|
29
|
+
}
|
|
30
|
+
branch := strings.TrimSpace(string(branchOutput))
|
|
31
|
+
|
|
32
|
+
// Get current worktree path
|
|
33
|
+
cwd, err := os.Getwd()
|
|
34
|
+
if err != nil {
|
|
35
|
+
return fmt.Errorf("failed to get working directory: %w", err)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Check if we're in a worktree
|
|
39
|
+
gitDirCmd := exec.Command("git", "rev-parse", "--git-dir")
|
|
40
|
+
gitDirOutput, err := gitDirCmd.Output()
|
|
41
|
+
if err != nil {
|
|
42
|
+
return fmt.Errorf("not in a git repository: %w", err)
|
|
43
|
+
}
|
|
44
|
+
gitDir := strings.TrimSpace(string(gitDirOutput))
|
|
45
|
+
|
|
46
|
+
isWorktree := strings.Contains(gitDir, "worktrees")
|
|
47
|
+
|
|
48
|
+
fmt.Printf("Branch: %s\n", branch)
|
|
49
|
+
fmt.Printf("Path: %s\n", cwd)
|
|
50
|
+
if isWorktree {
|
|
51
|
+
fmt.Println("Type: worktree")
|
|
52
|
+
} else {
|
|
53
|
+
fmt.Println("Type: main repository")
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return nil
|
|
57
|
+
}
|
package/go/go.mod
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module github.com/pullfrog/cli
|
|
2
|
+
|
|
3
|
+
go 1.25
|
|
4
|
+
|
|
5
|
+
require github.com/spf13/cobra v1.10.2
|
|
6
|
+
|
|
7
|
+
require (
|
|
8
|
+
github.com/atotto/clipboard v0.1.4 // indirect
|
|
9
|
+
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
|
10
|
+
github.com/catppuccin/go v0.3.0 // indirect
|
|
11
|
+
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 // indirect
|
|
12
|
+
github.com/charmbracelet/bubbletea v1.3.6 // indirect
|
|
13
|
+
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
|
|
14
|
+
github.com/charmbracelet/huh v0.8.0 // indirect
|
|
15
|
+
github.com/charmbracelet/lipgloss v1.1.0 // indirect
|
|
16
|
+
github.com/charmbracelet/x/ansi v0.9.3 // indirect
|
|
17
|
+
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
|
|
18
|
+
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect
|
|
19
|
+
github.com/charmbracelet/x/term v0.2.1 // indirect
|
|
20
|
+
github.com/dustin/go-humanize v1.0.1 // indirect
|
|
21
|
+
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
|
22
|
+
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
|
23
|
+
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
|
24
|
+
github.com/mattn/go-isatty v0.0.20 // indirect
|
|
25
|
+
github.com/mattn/go-localereader v0.0.1 // indirect
|
|
26
|
+
github.com/mattn/go-runewidth v0.0.16 // indirect
|
|
27
|
+
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
|
|
28
|
+
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
|
29
|
+
github.com/muesli/cancelreader v0.2.2 // indirect
|
|
30
|
+
github.com/muesli/termenv v0.16.0 // indirect
|
|
31
|
+
github.com/rivo/uniseg v0.4.7 // indirect
|
|
32
|
+
github.com/spf13/pflag v1.0.9 // indirect
|
|
33
|
+
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
|
34
|
+
golang.org/x/sync v0.15.0 // indirect
|
|
35
|
+
golang.org/x/sys v0.33.0 // indirect
|
|
36
|
+
golang.org/x/text v0.23.0 // indirect
|
|
37
|
+
)
|
package/go/go.sum
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
|
2
|
+
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
|
3
|
+
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
|
4
|
+
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
|
5
|
+
github.com/catppuccin/go v0.3.0 h1:d+0/YicIq+hSTo5oPuRi5kOpqkVA5tAsU6dNhvRu+aY=
|
|
6
|
+
github.com/catppuccin/go v0.3.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc=
|
|
7
|
+
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 h1:JFgG/xnwFfbezlUnFMJy0nusZvytYysV4SCS2cYbvws=
|
|
8
|
+
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7/go.mod h1:ISC1gtLcVilLOf23wvTfoQuYbW2q0JevFxPfUzZ9Ybw=
|
|
9
|
+
github.com/charmbracelet/bubbletea v1.3.6 h1:VkHIxPJQeDt0aFJIsVxw8BQdh/F/L2KKZGsK6et5taU=
|
|
10
|
+
github.com/charmbracelet/bubbletea v1.3.6/go.mod h1:oQD9VCRQFF8KplacJLo28/jofOI2ToOfGYeFgBBxHOc=
|
|
11
|
+
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
|
|
12
|
+
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
|
|
13
|
+
github.com/charmbracelet/huh v0.8.0 h1:Xz/Pm2h64cXQZn/Jvele4J3r7DDiqFCNIVteYukxDvY=
|
|
14
|
+
github.com/charmbracelet/huh v0.8.0/go.mod h1:5YVc+SlZ1IhQALxRPpkGwwEKftN/+OlJlnJYlDRFqN4=
|
|
15
|
+
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
|
|
16
|
+
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
|
17
|
+
github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE=
|
|
18
|
+
github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q=
|
|
19
|
+
github.com/charmbracelet/x/ansi v0.9.3 h1:BXt5DHS/MKF+LjuK4huWrC6NCvHtexww7dMayh6GXd0=
|
|
20
|
+
github.com/charmbracelet/x/ansi v0.9.3/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
|
|
21
|
+
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8=
|
|
22
|
+
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
|
|
23
|
+
github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k=
|
|
24
|
+
github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
|
|
25
|
+
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 h1:qko3AQ4gK1MTS/de7F5hPGx6/k1u0w4TeYmBFwzYVP4=
|
|
26
|
+
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0/go.mod h1:pBhA0ybfXv6hDjQUZ7hk1lVxBiUbupdw5R31yPUViVQ=
|
|
27
|
+
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
|
|
28
|
+
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
|
|
29
|
+
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
|
30
|
+
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
|
31
|
+
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
|
32
|
+
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
|
33
|
+
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
|
34
|
+
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
|
35
|
+
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
|
36
|
+
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
|
37
|
+
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
|
38
|
+
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
|
39
|
+
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
|
40
|
+
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
|
41
|
+
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
|
42
|
+
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
|
43
|
+
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
|
44
|
+
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
|
|
45
|
+
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
|
|
46
|
+
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
|
|
47
|
+
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
|
48
|
+
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
|
49
|
+
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
|
50
|
+
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
|
|
51
|
+
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
|
52
|
+
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
|
53
|
+
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
|
54
|
+
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
|
55
|
+
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
|
56
|
+
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
|
|
57
|
+
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
|
|
58
|
+
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
|
|
59
|
+
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
|
60
|
+
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
|
61
|
+
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
|
62
|
+
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
|
63
|
+
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
|
|
64
|
+
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
|
65
|
+
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
66
|
+
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
67
|
+
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
|
68
|
+
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
69
|
+
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
|
70
|
+
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
|
71
|
+
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
|
72
|
+
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
|
73
|
+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
package store
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"os"
|
|
6
|
+
"os/exec"
|
|
7
|
+
"path/filepath"
|
|
8
|
+
"strings"
|
|
9
|
+
"time"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
const worktreesFile = "worktrees.json"
|
|
13
|
+
|
|
14
|
+
type Worktree struct {
|
|
15
|
+
Name string `json:"name"`
|
|
16
|
+
Path string `json:"path"`
|
|
17
|
+
CreatedAt time.Time `json:"created_at"`
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type Store struct {
|
|
21
|
+
Worktrees []Worktree `json:"worktrees"`
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
func GetGitRoot() (string, error) {
|
|
25
|
+
cmd := exec.Command("git", "rev-parse", "--show-toplevel")
|
|
26
|
+
output, err := cmd.Output()
|
|
27
|
+
if err != nil {
|
|
28
|
+
return "", err
|
|
29
|
+
}
|
|
30
|
+
return strings.TrimSpace(string(output)), nil
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
func GetGitCommonDir() (string, error) {
|
|
34
|
+
cmd := exec.Command("git", "rev-parse", "--git-common-dir")
|
|
35
|
+
output, err := cmd.Output()
|
|
36
|
+
if err != nil {
|
|
37
|
+
return "", err
|
|
38
|
+
}
|
|
39
|
+
path := strings.TrimSpace(string(output))
|
|
40
|
+
if !filepath.IsAbs(path) {
|
|
41
|
+
cwd, err := os.Getwd()
|
|
42
|
+
if err != nil {
|
|
43
|
+
return "", err
|
|
44
|
+
}
|
|
45
|
+
path = filepath.Join(cwd, path)
|
|
46
|
+
}
|
|
47
|
+
return path, nil
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
func getStorePath() (string, error) {
|
|
51
|
+
gitDir, err := GetGitCommonDir()
|
|
52
|
+
if err != nil {
|
|
53
|
+
return "", err
|
|
54
|
+
}
|
|
55
|
+
return filepath.Join(gitDir, "pf"), nil
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
func Load() (*Store, error) {
|
|
59
|
+
storePath, err := getStorePath()
|
|
60
|
+
if err != nil {
|
|
61
|
+
return nil, err
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
filePath := filepath.Join(storePath, worktreesFile)
|
|
65
|
+
data, err := os.ReadFile(filePath)
|
|
66
|
+
if err != nil {
|
|
67
|
+
if os.IsNotExist(err) {
|
|
68
|
+
return &Store{Worktrees: []Worktree{}}, nil
|
|
69
|
+
}
|
|
70
|
+
return nil, err
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
var store Store
|
|
74
|
+
if err := json.Unmarshal(data, &store); err != nil {
|
|
75
|
+
return nil, err
|
|
76
|
+
}
|
|
77
|
+
return &store, nil
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
func (s *Store) Save() error {
|
|
81
|
+
storePath, err := getStorePath()
|
|
82
|
+
if err != nil {
|
|
83
|
+
return err
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if err := os.MkdirAll(storePath, 0755); err != nil {
|
|
87
|
+
return err
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
filePath := filepath.Join(storePath, worktreesFile)
|
|
91
|
+
data, err := json.MarshalIndent(s, "", " ")
|
|
92
|
+
if err != nil {
|
|
93
|
+
return err
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return os.WriteFile(filePath, data, 0644)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
func (s *Store) Add(name, path string) {
|
|
100
|
+
s.Worktrees = append(s.Worktrees, Worktree{
|
|
101
|
+
Name: name,
|
|
102
|
+
Path: path,
|
|
103
|
+
CreatedAt: time.Now(),
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
func (s *Store) FindByName(name string) *Worktree {
|
|
108
|
+
for i := range s.Worktrees {
|
|
109
|
+
if s.Worktrees[i].Name == name {
|
|
110
|
+
return &s.Worktrees[i]
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return nil
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
func (s *Store) RemoveByName(name string) {
|
|
117
|
+
filtered := []Worktree{}
|
|
118
|
+
for _, w := range s.Worktrees {
|
|
119
|
+
if w.Name != name {
|
|
120
|
+
filtered = append(filtered, w)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
s.Worktrees = filtered
|
|
124
|
+
}
|
package/go/main.go
ADDED
package/index.ts
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import pc from "picocolors";
|
|
2
|
+
import { newCommand } from "./commands/new.js";
|
|
3
|
+
import { openCommand } from "./commands/open.js";
|
|
4
|
+
import { lsCommand } from "./commands/ls.js";
|
|
5
|
+
import { rmCommand } from "./commands/rm.js";
|
|
6
|
+
// import { completionCommand } from "./commands/completion.js";
|
|
7
|
+
|
|
8
|
+
const VERSION = "0.0.2";
|
|
9
|
+
|
|
10
|
+
function printHelp() {
|
|
11
|
+
const dim = pc.dim;
|
|
12
|
+
const cyan = pc.cyan;
|
|
13
|
+
const green = pc.green;
|
|
14
|
+
|
|
15
|
+
console.log(`${pc.bold("pf")} ${dim(`v${VERSION}`)} - A humane utility for git worktrees
|
|
16
|
+
|
|
17
|
+
${pc.bold("Usage:")} ${cyan("pf")} ${dim("<command>")} ${dim("[options]")}
|
|
18
|
+
|
|
19
|
+
${pc.bold("Commands:")}
|
|
20
|
+
${green("new")} ${dim("[name]")} Create a branch and worktree with the specified name ${dim('(default: "pf-[hash]")')}
|
|
21
|
+
${green("open")} ${dim("<name>")} Open a worktree in a subshell
|
|
22
|
+
${green("ls")} List all tracked worktrees
|
|
23
|
+
${green("rm")} ${dim("<name>")} Remove a worktree
|
|
24
|
+
|
|
25
|
+
${pc.bold("Options:")}
|
|
26
|
+
${cyan("--help")}, ${cyan("-h")} Show help
|
|
27
|
+
${cyan("--version")}, ${cyan("-v")} Show version
|
|
28
|
+
|
|
29
|
+
${dim("─".repeat(60))}
|
|
30
|
+
|
|
31
|
+
${pc.bold("Why pf?")}
|
|
32
|
+
|
|
33
|
+
In agentic development, you often want to spin up isolated workspaces for
|
|
34
|
+
specific tasks—without stashing or committing your current changes. pf makes
|
|
35
|
+
this effortless.
|
|
36
|
+
|
|
37
|
+
Create a new worktree for a task:
|
|
38
|
+
|
|
39
|
+
${dim("$")} ${cyan("pf new add-login-button")}
|
|
40
|
+
|
|
41
|
+
This creates a branch and worktree, then drops you into a subshell. Open it
|
|
42
|
+
in your editor or point an AI coding agent at it. Your changes are completely
|
|
43
|
+
independent of any other work on your machine.
|
|
44
|
+
|
|
45
|
+
When you're done, push to GitHub and create a PR:
|
|
46
|
+
|
|
47
|
+
${dim("$")} ${cyan("git push -u origin add-login-button")}
|
|
48
|
+
|
|
49
|
+
Or merge directly back into main:
|
|
50
|
+
|
|
51
|
+
${dim("$")} ${cyan("git checkout main && git merge add-login-button")}
|
|
52
|
+
|
|
53
|
+
Then just ${cyan("exit")} to pop out of the worktree and back to your main repo.
|
|
54
|
+
Clean up when you're done:
|
|
55
|
+
|
|
56
|
+
${dim("$")} ${cyan("pf rm add-login-button")}
|
|
57
|
+
|
|
58
|
+
Use ${cyan("pf ls")} to see your active worktrees, and ${cyan("pf open <name>")} to
|
|
59
|
+
return to one later.`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function printVersion() {
|
|
63
|
+
console.log(`pf v${VERSION}`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const args = process.argv.slice(2);
|
|
67
|
+
const cmd = args[0];
|
|
68
|
+
|
|
69
|
+
if (!cmd || cmd === "--help" || cmd === "-h") {
|
|
70
|
+
printHelp();
|
|
71
|
+
process.exit(0);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (cmd === "--version" || cmd === "-v") {
|
|
75
|
+
printVersion();
|
|
76
|
+
process.exit(0);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
switch (cmd) {
|
|
80
|
+
case "new":
|
|
81
|
+
newCommand(args[1]);
|
|
82
|
+
break;
|
|
83
|
+
case "open":
|
|
84
|
+
if (!args[1]) {
|
|
85
|
+
console.error("Usage: pf open <name>");
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
openCommand(args[1]);
|
|
89
|
+
break;
|
|
90
|
+
case "ls":
|
|
91
|
+
lsCommand(args.includes("--plain"));
|
|
92
|
+
break;
|
|
93
|
+
case "rm":
|
|
94
|
+
if (!args[1]) {
|
|
95
|
+
console.error("Usage: pf rm <name>");
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
rmCommand(args[1], args.includes("--delete-branch"));
|
|
99
|
+
break;
|
|
100
|
+
// case "completion":
|
|
101
|
+
// completionCommand(args[1]);
|
|
102
|
+
// break;
|
|
103
|
+
default:
|
|
104
|
+
console.error(`Unknown command: ${cmd}`);
|
|
105
|
+
console.error("Run 'pf --help' for usage.");
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
package/package.json
CHANGED
|
@@ -1,30 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pf",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "A humane utility for git worktrees",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"pf": "dist/index.js"
|
|
8
8
|
},
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc && node build.mjs",
|
|
11
|
+
"dev": "node build.mjs --watch",
|
|
12
|
+
"prepare": "node build.mjs",
|
|
13
|
+
"typecheck": "tsc"
|
|
12
14
|
},
|
|
13
|
-
"keywords": [
|
|
14
|
-
"point",
|
|
15
|
-
"free",
|
|
16
|
-
"functional",
|
|
17
|
-
"pure",
|
|
18
|
-
"algorithm",
|
|
19
|
-
"higher",
|
|
20
|
-
"order"
|
|
21
|
-
],
|
|
22
|
-
"author": "Ryan Munro <munro.github@gmail.com>",
|
|
23
|
-
"license": "MIT",
|
|
24
15
|
"dependencies": {
|
|
25
|
-
"
|
|
16
|
+
"picocolors": "^1.0.0",
|
|
17
|
+
"cli-table3": "^0.6.5"
|
|
26
18
|
},
|
|
27
19
|
"devDependencies": {
|
|
28
|
-
"
|
|
29
|
-
|
|
20
|
+
"@types/node": "^22.0.0",
|
|
21
|
+
"esbuild": "^0.24.0",
|
|
22
|
+
"typescript": "^5.7.0"
|
|
23
|
+
},
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=24"
|
|
26
|
+
},
|
|
27
|
+
"license": "MIT"
|
|
30
28
|
}
|
package/store.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
3
|
+
import { dirname, isAbsolute, join } from "path";
|
|
4
|
+
|
|
5
|
+
const WORKTREES_FILE = "worktrees.json";
|
|
6
|
+
|
|
7
|
+
export interface Worktree {
|
|
8
|
+
name: string;
|
|
9
|
+
path: string;
|
|
10
|
+
created_at: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface StoreData {
|
|
14
|
+
worktrees: Worktree[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function getGitRoot(): string {
|
|
18
|
+
return execSync("git rev-parse --show-toplevel", { encoding: "utf-8" }).trim();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function getGitCommonDir(): string {
|
|
22
|
+
let path = execSync("git rev-parse --git-common-dir", { encoding: "utf-8" }).trim();
|
|
23
|
+
if (!isAbsolute(path)) {
|
|
24
|
+
path = join(process.cwd(), path);
|
|
25
|
+
}
|
|
26
|
+
return path;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function getStorePath(): string {
|
|
30
|
+
return join(getGitCommonDir(), "pf");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function loadStore(): StoreData {
|
|
34
|
+
const storePath = getStorePath();
|
|
35
|
+
const filePath = join(storePath, WORKTREES_FILE);
|
|
36
|
+
|
|
37
|
+
if (!existsSync(filePath)) {
|
|
38
|
+
return { worktrees: [] };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const data = readFileSync(filePath, "utf-8");
|
|
42
|
+
return JSON.parse(data);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function saveStore(store: StoreData): void {
|
|
46
|
+
const storePath = getStorePath();
|
|
47
|
+
mkdirSync(storePath, { recursive: true });
|
|
48
|
+
|
|
49
|
+
const filePath = join(storePath, WORKTREES_FILE);
|
|
50
|
+
writeFileSync(filePath, JSON.stringify(store, null, 2));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function addWorktree(store: StoreData, name: string, path: string): void {
|
|
54
|
+
store.worktrees.push({
|
|
55
|
+
name,
|
|
56
|
+
path,
|
|
57
|
+
created_at: new Date().toISOString(),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function findWorktree(store: StoreData, name: string): Worktree | undefined {
|
|
62
|
+
return store.worktrees.find((w) => w.name === name);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function removeWorktree(store: StoreData, name: string): void {
|
|
66
|
+
store.worktrees = store.worktrees.filter((w) => w.name !== name);
|
|
67
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"outDir": "dist",
|
|
10
|
+
"rootDir": ".",
|
|
11
|
+
"declaration": false,
|
|
12
|
+
"noEmit": true
|
|
13
|
+
},
|
|
14
|
+
"include": ["*.ts", "commands/*.ts"],
|
|
15
|
+
"exclude": ["node_modules", "dist", "go"]
|
|
16
|
+
}
|