bonchi 0.6.0.rc2 → 0.6.0.rc3
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.
- checksums.yaml +4 -4
- data/lib/bonchi/cli.rb +128 -52
- data/lib/bonchi/colors.rb +4 -2
- data/lib/bonchi/git.rb +14 -0
- data/lib/bonchi/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a8333ff1974d32544950e5519f6ba6cec214a2d62563f98a8969c979efc8de7e
|
|
4
|
+
data.tar.gz: 9cc9f9147b528b6d4eb893dd8ab0787a65d3d4b64e631277513d001ef15ebcaf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ab3822a6e85532202323eeaa0c8323917a58e7bc3f6e768b0ba5efa5f74ac1106afc405a95b68c3023a471cfd38a3f8d148464dd16cc3bec7052fddf00db48a8
|
|
7
|
+
data.tar.gz: b22890d111d7c33fd80cbfcbcff9df8dfd2ad819a9174d362e30e5d838bf972c7205e07b8d295aae8fcd81abd3921b169224e80d88ffa3cef840f389a74f4df9
|
data/lib/bonchi/cli.rb
CHANGED
|
@@ -2,6 +2,8 @@ require "thor"
|
|
|
2
2
|
|
|
3
3
|
module Bonchi
|
|
4
4
|
class CLI < Thor
|
|
5
|
+
include Colors
|
|
6
|
+
|
|
5
7
|
def self.exit_on_failure?
|
|
6
8
|
true
|
|
7
9
|
end
|
|
@@ -13,20 +15,25 @@ module Bonchi
|
|
|
13
15
|
map "--version" => :version
|
|
14
16
|
map "-v" => :version
|
|
15
17
|
|
|
16
|
-
desc "
|
|
18
|
+
desc "switch BRANCH", "Switch to branch in worktree"
|
|
17
19
|
long_desc <<~DESC
|
|
18
|
-
Create a
|
|
19
|
-
|
|
20
|
+
Create a worktree for a branch and cd into it.
|
|
21
|
+
If a worktree for BRANCH already exists, switches to it instead.
|
|
22
|
+
|
|
23
|
+
Use -c to create a new branch (like git switch -c). Use --base to specify
|
|
24
|
+
the base branch (defaults to the repository's default branch, e.g. main).
|
|
20
25
|
|
|
21
|
-
When a .worktree.yml exists in the main worktree, setup runs automatically
|
|
22
|
-
(copy files, allocate ports, run pre_setup and setup commands).
|
|
26
|
+
When a .worktree.yml exists in the main worktree, setup runs automatically.
|
|
23
27
|
Skip with --no-setup, or use --upto STEP to run only up to a specific step.
|
|
28
|
+
|
|
29
|
+
Aliases: sw, create (implies -c)
|
|
24
30
|
DESC
|
|
31
|
+
option :c, type: :boolean, default: false, desc: "Create a new branch"
|
|
32
|
+
option :base, type: :string, desc: "Base branch for -c (default: repository default branch)"
|
|
25
33
|
option :setup, type: :boolean, default: true, desc: "Run setup after creating worktree"
|
|
26
34
|
option :upto, type: :string, desc: "Run setup steps up to and including STEP (copy, link, ports, replace, pre_setup, setup)"
|
|
27
|
-
def
|
|
28
|
-
base
|
|
29
|
-
path = Git.worktree_dir(branch)
|
|
35
|
+
def switch(branch)
|
|
36
|
+
abort "Error: --base requires -c flag" if options[:base] && !options[:c]
|
|
30
37
|
|
|
31
38
|
existing = Git.worktree_path_for(branch)
|
|
32
39
|
if existing
|
|
@@ -35,9 +42,18 @@ module Bonchi
|
|
|
35
42
|
return
|
|
36
43
|
end
|
|
37
44
|
|
|
38
|
-
Git.
|
|
39
|
-
puts "Worktree created at: #{path}"
|
|
45
|
+
path = Git.worktree_dir(branch)
|
|
40
46
|
|
|
47
|
+
if options[:c] && !Git.branch_exists?(branch)
|
|
48
|
+
base = options[:base] || Git.default_base_branch
|
|
49
|
+
Git.worktree_add_new_branch(path, branch, base)
|
|
50
|
+
elsif options[:c] || Git.branch_exists?(branch)
|
|
51
|
+
Git.worktree_add(path, branch)
|
|
52
|
+
else
|
|
53
|
+
abort "Error: Branch '#{branch}' does not exist\nUse 'bonchi switch -c #{branch}' to create a new branch"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
puts "Worktree created at: #{path}"
|
|
41
57
|
signal_cd(path)
|
|
42
58
|
|
|
43
59
|
if options[:setup] && Config.from_main_worktree
|
|
@@ -46,41 +62,27 @@ module Bonchi
|
|
|
46
62
|
end
|
|
47
63
|
end
|
|
48
64
|
|
|
49
|
-
desc "
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
The branch must already exist locally or on the remote.
|
|
55
|
-
To create a new branch, use `bonchi create` instead.
|
|
56
|
-
DESC
|
|
57
|
-
def switch(branch)
|
|
58
|
-
existing = Git.worktree_path_for(branch)
|
|
59
|
-
if existing
|
|
60
|
-
puts "Worktree already exists: #{existing}"
|
|
61
|
-
signal_cd(existing)
|
|
62
|
-
return
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
unless Git.branch_exists?(branch)
|
|
66
|
-
abort "Error: Branch '#{branch}' does not exist\nUse 'bonchi create #{branch}' to create a new branch"
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
path = Git.worktree_dir(branch)
|
|
70
|
-
Git.worktree_add(path, branch)
|
|
71
|
-
puts "Worktree created at: #{path}"
|
|
72
|
-
|
|
73
|
-
signal_cd(path)
|
|
65
|
+
desc "create BRANCH [BASE]", "Create new branch + worktree (alias for switch -c)"
|
|
66
|
+
option :setup, type: :boolean, default: true, desc: "Run setup after creating worktree"
|
|
67
|
+
option :upto, type: :string, desc: "Run setup steps up to and including STEP (copy, link, ports, replace, pre_setup, setup)"
|
|
68
|
+
def create(branch, base = nil)
|
|
69
|
+
invoke :switch, [branch], c: true, base: base, setup: options[:setup], upto: options[:upto]
|
|
74
70
|
end
|
|
75
71
|
|
|
76
72
|
desc "pr NUMBER_OR_URL", "Checkout GitHub PR in worktree"
|
|
77
73
|
long_desc <<~DESC
|
|
78
|
-
Fetch a GitHub pull request and
|
|
74
|
+
Fetch a GitHub pull request and switch to it in a new worktree.
|
|
79
75
|
Accepts a PR number (e.g. 123) or a full GitHub PR URL.
|
|
76
|
+
Like `bonchi switch`, but fetches the PR first.
|
|
80
77
|
|
|
81
78
|
The worktree branch will be named pr-<number>.
|
|
82
79
|
If the worktree already exists, switches to it instead.
|
|
80
|
+
|
|
81
|
+
When a .worktree.yml exists in the main worktree, setup runs automatically.
|
|
82
|
+
Skip with --no-setup, or use --upto STEP to run only up to a specific step.
|
|
83
83
|
DESC
|
|
84
|
+
option :setup, type: :boolean, default: true, desc: "Run setup after creating worktree"
|
|
85
|
+
option :upto, type: :string, desc: "Run setup steps up to and including STEP (copy, link, ports, replace, pre_setup, setup)"
|
|
84
86
|
def pr(input)
|
|
85
87
|
pr_number = extract_pr_number(input)
|
|
86
88
|
branch = "pr-#{pr_number}"
|
|
@@ -98,6 +100,11 @@ module Bonchi
|
|
|
98
100
|
puts "PR ##{pr_number} checked out at: #{path}"
|
|
99
101
|
|
|
100
102
|
signal_cd(path)
|
|
103
|
+
|
|
104
|
+
if options[:setup] && Config.from_main_worktree
|
|
105
|
+
puts ""
|
|
106
|
+
Setup.new(worktree: path).run(upto: options[:upto])
|
|
107
|
+
end
|
|
101
108
|
end
|
|
102
109
|
|
|
103
110
|
desc "init", "Generate a .worktree.yml in the current project"
|
|
@@ -123,25 +130,69 @@ module Bonchi
|
|
|
123
130
|
end
|
|
124
131
|
|
|
125
132
|
desc "list", "List all worktrees"
|
|
133
|
+
long_desc <<~DESC
|
|
134
|
+
List all worktrees. Non-main branches are annotated with:
|
|
135
|
+
|
|
136
|
+
\x5 dirty — has uncommitted changes or untracked files
|
|
137
|
+
\x5 merged — branch has been merged into the default branch
|
|
138
|
+
DESC
|
|
126
139
|
def list
|
|
127
|
-
Git.worktree_list
|
|
140
|
+
lines = Git.worktree_list
|
|
141
|
+
base = Git.default_base_branch
|
|
142
|
+
home = Dir.home
|
|
143
|
+
|
|
144
|
+
lines.each do |line|
|
|
145
|
+
branch = line[/\[([^\]]+)\]/, 1]
|
|
146
|
+
path = line.split(/\s+/).first
|
|
147
|
+
line = line.sub(home, "~")
|
|
148
|
+
|
|
149
|
+
unless branch
|
|
150
|
+
puts line
|
|
151
|
+
next
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
if branch == base
|
|
155
|
+
puts line
|
|
156
|
+
next
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
merged = Git.merged?(branch, into: base)
|
|
160
|
+
clean = Git.clean?(path)
|
|
161
|
+
tags = []
|
|
162
|
+
tags << "#{color(:yellow)}dirty#{reset}" unless clean
|
|
163
|
+
tags << "#{color(:green)}merged#{reset}" if merged
|
|
164
|
+
|
|
165
|
+
if tags.any?
|
|
166
|
+
puts "#{line} #{tags.join(" ")}"
|
|
167
|
+
else
|
|
168
|
+
puts line
|
|
169
|
+
end
|
|
170
|
+
end
|
|
128
171
|
end
|
|
129
172
|
|
|
130
|
-
desc "remove BRANCH", "Remove a worktree"
|
|
173
|
+
desc "remove BRANCH", "Remove a worktree (and merged branch)"
|
|
131
174
|
long_desc <<~DESC
|
|
132
175
|
Remove a worktree and its directory. Refuses to remove worktrees
|
|
133
176
|
with uncommitted changes or untracked files unless --force is used.
|
|
134
177
|
|
|
135
|
-
|
|
178
|
+
If the branch has been merged into the default branch, it is
|
|
179
|
+
automatically deleted. Unmerged branches are kept.
|
|
180
|
+
|
|
181
|
+
Aliases: rm, rmf (force), rmrf (force + delete unmerged branch)
|
|
136
182
|
DESC
|
|
137
183
|
option :force, type: :boolean, default: false, desc: "Force removal even with uncommitted changes"
|
|
138
184
|
def remove(branch)
|
|
139
|
-
|
|
140
|
-
|
|
185
|
+
remove_worktree(branch, force: options[:force], delete_branch: :merged)
|
|
186
|
+
end
|
|
141
187
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
188
|
+
desc "rmf BRANCH", "Force-remove a worktree (and merged branch)"
|
|
189
|
+
def rmf(branch)
|
|
190
|
+
remove_worktree(branch, force: true, delete_branch: :merged)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
desc "rmrf BRANCH", "Force-remove a worktree and branch"
|
|
194
|
+
def rmrf(branch)
|
|
195
|
+
remove_worktree(branch, force: true, delete_branch: :always)
|
|
145
196
|
end
|
|
146
197
|
|
|
147
198
|
desc "prune", "Prune stale worktree admin files"
|
|
@@ -166,8 +217,31 @@ module Bonchi
|
|
|
166
217
|
map "ls" => :list
|
|
167
218
|
map "rm" => :remove
|
|
168
219
|
|
|
220
|
+
remove_command :tree
|
|
221
|
+
|
|
169
222
|
private
|
|
170
223
|
|
|
224
|
+
def remove_worktree(branch, force:, delete_branch:)
|
|
225
|
+
path = Git.worktree_path_for(branch)
|
|
226
|
+
abort "Error: No worktree found for branch: #{branch}" unless path
|
|
227
|
+
|
|
228
|
+
Git.worktree_remove(path, force: force)
|
|
229
|
+
puts "Removed worktree: #{path}"
|
|
230
|
+
|
|
231
|
+
case delete_branch
|
|
232
|
+
when :always
|
|
233
|
+
Git.delete_branch(branch, force: true)
|
|
234
|
+
puts "Deleted branch: #{branch}"
|
|
235
|
+
when :merged
|
|
236
|
+
if Git.merged?(branch)
|
|
237
|
+
Git.delete_branch(branch)
|
|
238
|
+
puts "Deleted merged branch: #{branch}"
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
signal_cd(Git.main_worktree)
|
|
243
|
+
end
|
|
244
|
+
|
|
171
245
|
def signal_cd(path)
|
|
172
246
|
cd_file = ENV["BONCHI_CD_FILE"]
|
|
173
247
|
if cd_file
|
|
@@ -246,7 +320,7 @@ module Bonchi
|
|
|
246
320
|
COMPREPLY=()
|
|
247
321
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
|
248
322
|
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
|
249
|
-
commands="create switch sw pr setup list ls remove rm prune shellenv help"
|
|
323
|
+
commands="create switch sw pr setup list ls remove rm rmf rmrf prune shellenv help"
|
|
250
324
|
|
|
251
325
|
if [ $COMP_CWORD -eq 1 ]; then
|
|
252
326
|
COMPREPLY=( $(compgen -W "$commands" -- "$cur") )
|
|
@@ -254,7 +328,7 @@ module Bonchi
|
|
|
254
328
|
fi
|
|
255
329
|
|
|
256
330
|
case "$prev" in
|
|
257
|
-
switch|sw|remove|rm)
|
|
331
|
+
switch|sw|remove|rm|rmf|rmrf)
|
|
258
332
|
local branches
|
|
259
333
|
branches=$(git worktree list 2>/dev/null | sed -n 's/.*\[\([^]]*\)\].*/\1/p' | tail -n +2)
|
|
260
334
|
COMPREPLY=( $(compgen -W "$branches" -- "$cur") )
|
|
@@ -270,15 +344,17 @@ module Bonchi
|
|
|
270
344
|
_bonchi_complete_zsh() {
|
|
271
345
|
local -a commands branches
|
|
272
346
|
commands=(
|
|
273
|
-
'create:Create new branch + worktree'
|
|
274
|
-
'switch:Switch to
|
|
275
|
-
'sw:Switch to
|
|
347
|
+
'create:Create new branch + worktree (alias for switch -c)'
|
|
348
|
+
'switch:Switch to branch in worktree (-c to create)'
|
|
349
|
+
'sw:Switch to branch in worktree (-c to create)'
|
|
276
350
|
'pr:Checkout GitHub PR in worktree'
|
|
277
351
|
'setup:Run setup in current worktree'
|
|
278
352
|
'list:List all worktrees'
|
|
279
353
|
'ls:List all worktrees'
|
|
280
|
-
'remove:Remove a worktree'
|
|
281
|
-
'rm:Remove a worktree'
|
|
354
|
+
'remove:Remove a worktree (and merged branch)'
|
|
355
|
+
'rm:Remove a worktree (and merged branch)'
|
|
356
|
+
'rmf:Force-remove a worktree (and merged branch)'
|
|
357
|
+
'rmrf:Force-remove a worktree and branch'
|
|
282
358
|
'prune:Prune stale worktree admin files'
|
|
283
359
|
'shellenv:Output shell function for auto-cd'
|
|
284
360
|
)
|
|
@@ -287,7 +363,7 @@ module Bonchi
|
|
|
287
363
|
_describe 'command' commands
|
|
288
364
|
elif (( CURRENT == 3 )); then
|
|
289
365
|
case "$words[2]" in
|
|
290
|
-
switch|sw|remove|rm)
|
|
366
|
+
switch|sw|remove|rm|rmf|rmrf)
|
|
291
367
|
branches=(${(f)"$(git worktree list 2>/dev/null | sed -n 's/.*\[\([^]]*\)\].*/\1/p' | tail -n +1)"})
|
|
292
368
|
_describe 'branch' branches
|
|
293
369
|
;;
|
data/lib/bonchi/colors.rb
CHANGED
|
@@ -3,16 +3,18 @@ module Bonchi
|
|
|
3
3
|
private
|
|
4
4
|
|
|
5
5
|
def color(name)
|
|
6
|
-
return "" if ENV.key?("NO_COLOR")
|
|
6
|
+
return "" if ENV.key?("NO_COLOR") || !$stdout.tty?
|
|
7
7
|
|
|
8
8
|
case name
|
|
9
9
|
when :red then "\e[31m"
|
|
10
|
+
when :green then "\e[32m"
|
|
10
11
|
when :yellow then "\e[33m"
|
|
12
|
+
when :dim then "\e[2m"
|
|
11
13
|
end
|
|
12
14
|
end
|
|
13
15
|
|
|
14
16
|
def reset
|
|
15
|
-
return "" if ENV.key?("NO_COLOR")
|
|
17
|
+
return "" if ENV.key?("NO_COLOR") || !$stdout.tty?
|
|
16
18
|
|
|
17
19
|
"\e[0m"
|
|
18
20
|
end
|
data/lib/bonchi/git.rb
CHANGED
|
@@ -61,6 +61,20 @@ module Bonchi
|
|
|
61
61
|
system("git", "worktree", "prune")
|
|
62
62
|
end
|
|
63
63
|
|
|
64
|
+
def clean?(worktree)
|
|
65
|
+
`git -C #{worktree.shellescape} status --porcelain`.strip.empty?
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def merged?(branch, into: default_base_branch)
|
|
69
|
+
system("git", "merge-base", "--is-ancestor", branch, into) ||
|
|
70
|
+
system("git", "merge-base", "--is-ancestor", branch, "origin/#{into}")
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def delete_branch(branch, force: false)
|
|
74
|
+
flag = force ? "-D" : "-d"
|
|
75
|
+
system("git", "branch", flag, branch) || abort("Failed to delete branch: #{branch}")
|
|
76
|
+
end
|
|
77
|
+
|
|
64
78
|
def fetch_pr(pr_number)
|
|
65
79
|
system("git", "fetch", "origin", "pull/#{pr_number}/head:pr-#{pr_number}")
|
|
66
80
|
end
|
data/lib/bonchi/version.rb
CHANGED