bonchi 0.1.0.dev
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 +7 -0
- data/LICENSE.txt +21 -0
- data/exe/bonchi +4 -0
- data/lib/bonchi/cli.rb +199 -0
- data/lib/bonchi/config.rb +23 -0
- data/lib/bonchi/git.rb +64 -0
- data/lib/bonchi/global_config.rb +34 -0
- data/lib/bonchi/port_pool.rb +80 -0
- data/lib/bonchi/setup.rb +66 -0
- data/lib/bonchi/version.rb +3 -0
- data/lib/bonchi.rb +10 -0
- metadata +64 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 4401b948510406e541ccd17bc7aa34faefcf8d9f420ea9d4126da8776a6b3fc4
|
|
4
|
+
data.tar.gz: 1eca2f4dad53e1cf516fabef0def984cec178a0a9a604171a0053aa59d8e13c9
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: a1798321638aa5711f9fa042c5f1a667fb4e8469ee887830fcbd5efff5676ee1ff110c9df38c65fdb5af2521e28ff737ac032709045d8cf3feca3dec758a6890
|
|
7
|
+
data.tar.gz: 81e208dcb8c0fb44445669bf34976b7a5fbfaff7e77fc4a6ce6ddd6c998165f3a5ddeb147bf93d57ecc128bf813a79b62fa0d98be0c0b9f765937b1a7c3323e0
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Gert Goet
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/exe/bonchi
ADDED
data/lib/bonchi/cli.rb
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
require "thor"
|
|
2
|
+
|
|
3
|
+
module Bonchi
|
|
4
|
+
class CLI < Thor
|
|
5
|
+
def self.exit_on_failure?
|
|
6
|
+
true
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
desc "version", "Print version"
|
|
10
|
+
def version
|
|
11
|
+
puts "bonchi #{VERSION}"
|
|
12
|
+
end
|
|
13
|
+
map "--version" => :version
|
|
14
|
+
map "-v" => :version
|
|
15
|
+
|
|
16
|
+
desc "create BRANCH [BASE]", "Create new branch + worktree"
|
|
17
|
+
option :setup, type: :boolean, default: true, desc: "Run setup after creating worktree"
|
|
18
|
+
def create(branch, base = nil)
|
|
19
|
+
base ||= Git.default_base_branch
|
|
20
|
+
path = Git.worktree_dir(branch)
|
|
21
|
+
|
|
22
|
+
existing = Git.worktree_path_for(branch)
|
|
23
|
+
if existing
|
|
24
|
+
puts "Worktree already exists: #{existing}"
|
|
25
|
+
puts "BONCHI_CD:#{existing}"
|
|
26
|
+
return
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
Git.worktree_add_new_branch(path, branch, base)
|
|
30
|
+
puts "Worktree created at: #{path}"
|
|
31
|
+
|
|
32
|
+
if options[:setup] && Config.from_main_worktree
|
|
33
|
+
puts ""
|
|
34
|
+
Setup.new(worktree: path).run
|
|
35
|
+
else
|
|
36
|
+
puts "BONCHI_CD:#{path}"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
desc "switch BRANCH", "Switch to existing branch in worktree"
|
|
41
|
+
def switch(branch)
|
|
42
|
+
existing = Git.worktree_path_for(branch)
|
|
43
|
+
if existing
|
|
44
|
+
puts "Worktree already exists: #{existing}"
|
|
45
|
+
puts "BONCHI_CD:#{existing}"
|
|
46
|
+
return
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
unless Git.branch_exists?(branch)
|
|
50
|
+
abort "Error: Branch '#{branch}' does not exist\nUse 'bonchi create #{branch}' to create a new branch"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
path = Git.worktree_dir(branch)
|
|
54
|
+
Git.worktree_add(path, branch)
|
|
55
|
+
puts "Worktree created at: #{path}"
|
|
56
|
+
|
|
57
|
+
puts "BONCHI_CD:#{path}"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
desc "pr NUMBER_OR_URL", "Checkout GitHub PR in worktree"
|
|
61
|
+
def pr(input)
|
|
62
|
+
pr_number = extract_pr_number(input)
|
|
63
|
+
branch = "pr-#{pr_number}"
|
|
64
|
+
path = Git.worktree_dir(branch)
|
|
65
|
+
|
|
66
|
+
existing = Git.worktree_path_for(branch)
|
|
67
|
+
if existing
|
|
68
|
+
puts "Worktree already exists: #{existing}"
|
|
69
|
+
puts "BONCHI_CD:#{existing}"
|
|
70
|
+
return
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
Git.fetch_pr(pr_number)
|
|
74
|
+
Git.worktree_add(path, branch)
|
|
75
|
+
puts "PR ##{pr_number} checked out at: #{path}"
|
|
76
|
+
|
|
77
|
+
puts "BONCHI_CD:#{path}"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
desc "setup", "Run setup in current worktree (ports, copy, pre_setup, setup cmd)"
|
|
81
|
+
def setup
|
|
82
|
+
Setup.new.run
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
desc "list", "List all worktrees"
|
|
86
|
+
def list
|
|
87
|
+
Git.worktree_list.each { |line| puts line }
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
desc "remove BRANCH", "Remove a worktree"
|
|
91
|
+
def remove(branch)
|
|
92
|
+
path = Git.worktree_path_for(branch)
|
|
93
|
+
abort "Error: No worktree found for branch: #{branch}" unless path
|
|
94
|
+
|
|
95
|
+
Git.worktree_remove(path)
|
|
96
|
+
puts "Removed worktree: #{path}"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
desc "prune", "Prune stale worktree admin files"
|
|
100
|
+
def prune
|
|
101
|
+
Git.worktree_prune
|
|
102
|
+
puts "Pruned stale worktree administrative files"
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
desc "shellenv", "Output shell function for auto-cd + completions"
|
|
106
|
+
def shellenv
|
|
107
|
+
puts SHELL_ENV
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
map "sw" => :switch
|
|
111
|
+
map "ls" => :list
|
|
112
|
+
map "rm" => :remove
|
|
113
|
+
|
|
114
|
+
private
|
|
115
|
+
|
|
116
|
+
def extract_pr_number(input)
|
|
117
|
+
case input
|
|
118
|
+
when %r{^https://github.com/.*/pull/(\d+)}
|
|
119
|
+
$1
|
|
120
|
+
when /^\d+$/
|
|
121
|
+
input
|
|
122
|
+
else
|
|
123
|
+
abort "Error: Invalid PR number or URL: #{input}"
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
SHELL_ENV = <<~'SHELL'
|
|
128
|
+
bonchi() {
|
|
129
|
+
local output
|
|
130
|
+
output=$(command bonchi "$@")
|
|
131
|
+
local exit_code=$?
|
|
132
|
+
echo "$output"
|
|
133
|
+
if [ $exit_code -eq 0 ]; then
|
|
134
|
+
local cd_path=$(echo "$output" | grep "^BONCHI_CD:" | cut -d: -f2-)
|
|
135
|
+
[ -n "$cd_path" ] && cd "$cd_path"
|
|
136
|
+
fi
|
|
137
|
+
return $exit_code
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
# Bash completion
|
|
141
|
+
if [ -n "$BASH_VERSION" ]; then
|
|
142
|
+
_bonchi_complete() {
|
|
143
|
+
local cur prev commands
|
|
144
|
+
COMPREPLY=()
|
|
145
|
+
cur="${COMP_WORDS[COMP_CWORD]}"
|
|
146
|
+
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
|
147
|
+
commands="create switch sw pr setup list ls remove rm prune shellenv help"
|
|
148
|
+
|
|
149
|
+
if [ $COMP_CWORD -eq 1 ]; then
|
|
150
|
+
COMPREPLY=( $(compgen -W "$commands" -- "$cur") )
|
|
151
|
+
return 0
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
case "$prev" in
|
|
155
|
+
switch|sw|remove|rm)
|
|
156
|
+
local branches
|
|
157
|
+
branches=$(git worktree list 2>/dev/null | sed -n 's/.*\[\([^]]*\)\].*/\1/p' | tail -n +2)
|
|
158
|
+
COMPREPLY=( $(compgen -W "$branches" -- "$cur") )
|
|
159
|
+
return 0
|
|
160
|
+
;;
|
|
161
|
+
esac
|
|
162
|
+
}
|
|
163
|
+
complete -F _bonchi_complete bonchi
|
|
164
|
+
fi
|
|
165
|
+
|
|
166
|
+
# Zsh completion
|
|
167
|
+
if [ -n "$ZSH_VERSION" ]; then
|
|
168
|
+
_bonchi_complete_zsh() {
|
|
169
|
+
local -a commands branches
|
|
170
|
+
commands=(
|
|
171
|
+
'create:Create new branch + worktree'
|
|
172
|
+
'switch:Switch to existing branch in worktree'
|
|
173
|
+
'sw:Switch to existing branch in worktree'
|
|
174
|
+
'pr:Checkout GitHub PR in worktree'
|
|
175
|
+
'setup:Run setup in current worktree'
|
|
176
|
+
'list:List all worktrees'
|
|
177
|
+
'ls:List all worktrees'
|
|
178
|
+
'remove:Remove a worktree'
|
|
179
|
+
'rm:Remove a worktree'
|
|
180
|
+
'prune:Prune stale worktree admin files'
|
|
181
|
+
'shellenv:Output shell function for auto-cd'
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
if (( CURRENT == 2 )); then
|
|
185
|
+
_describe 'command' commands
|
|
186
|
+
elif (( CURRENT == 3 )); then
|
|
187
|
+
case "$words[2]" in
|
|
188
|
+
switch|sw|remove|rm)
|
|
189
|
+
branches=(${(f)"$(git worktree list 2>/dev/null | sed -n 's/.*\[\([^]]*\)\].*/\1/p' | tail -n +1)"})
|
|
190
|
+
_describe 'branch' branches
|
|
191
|
+
;;
|
|
192
|
+
esac
|
|
193
|
+
fi
|
|
194
|
+
}
|
|
195
|
+
compdef _bonchi_complete_zsh bonchi
|
|
196
|
+
fi
|
|
197
|
+
SHELL
|
|
198
|
+
end
|
|
199
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
require "yaml"
|
|
2
|
+
|
|
3
|
+
module Bonchi
|
|
4
|
+
class Config
|
|
5
|
+
attr_reader :copy, :ports, :pre_setup, :setup
|
|
6
|
+
|
|
7
|
+
def initialize(path)
|
|
8
|
+
data = YAML.load_file(path)
|
|
9
|
+
@copy = Array(data["copy"])
|
|
10
|
+
@ports = Array(data["ports"])
|
|
11
|
+
@pre_setup = Array(data["pre_setup"])
|
|
12
|
+
@setup = data["setup"] || "bin/setup"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.from_main_worktree
|
|
16
|
+
main = Git.main_worktree
|
|
17
|
+
path = File.join(main, ".worktree.yml")
|
|
18
|
+
return nil unless File.exist?(path)
|
|
19
|
+
|
|
20
|
+
new(path)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
data/lib/bonchi/git.rb
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
require "shellwords"
|
|
2
|
+
|
|
3
|
+
module Bonchi
|
|
4
|
+
module Git
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def repo_name
|
|
8
|
+
url = `git remote get-url origin 2>/dev/null`.strip
|
|
9
|
+
base = url.empty? ? `git rev-parse --show-toplevel`.strip : url
|
|
10
|
+
File.basename(base, ".git")
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def default_base_branch
|
|
14
|
+
ref = `git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null`.strip
|
|
15
|
+
ref.empty? ? "main" : ref.sub(%r{^refs/remotes/origin/}, "")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def main_worktree
|
|
19
|
+
`git worktree list --porcelain`.lines.first.sub("worktree ", "").strip
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def worktree_list
|
|
23
|
+
`git worktree list`.lines.map(&:strip).reject(&:empty?)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def worktree_branches
|
|
27
|
+
worktree_list.filter_map { |line| line[/\[([^\]]+)\]/, 1] }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def worktree_path_for(branch)
|
|
31
|
+
line = `git worktree list`.lines.find { |l| l.include?("[#{branch}]") }
|
|
32
|
+
line&.split(/\s+/)&.first
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def branch_exists?(branch)
|
|
36
|
+
system("git show-ref --verify --quiet refs/heads/#{branch.shellescape}") ||
|
|
37
|
+
system("git show-ref --verify --quiet refs/remotes/origin/#{branch.shellescape}")
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def worktree_add(path, branch)
|
|
41
|
+
system("git", "worktree", "add", path, branch) || abort("Failed to add worktree")
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def worktree_add_new_branch(path, branch, base)
|
|
45
|
+
system("git", "worktree", "add", path, "-b", branch, base) || abort("Failed to add worktree")
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def worktree_remove(path)
|
|
49
|
+
system("git", "worktree", "remove", path) || abort("Failed to remove worktree")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def worktree_prune
|
|
53
|
+
system("git", "worktree", "prune")
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def fetch_pr(pr_number)
|
|
57
|
+
system("git", "fetch", "origin", "pull/#{pr_number}/head:pr-#{pr_number}")
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def worktree_dir(branch)
|
|
61
|
+
File.join(GlobalConfig.new.worktree_root, repo_name, branch)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
require "yaml"
|
|
2
|
+
require "fileutils"
|
|
3
|
+
|
|
4
|
+
module Bonchi
|
|
5
|
+
class GlobalConfig
|
|
6
|
+
def initialize
|
|
7
|
+
@path = self.class.config_path
|
|
8
|
+
@data = if File.exist?(@path)
|
|
9
|
+
YAML.safe_load_file(@path) || {}
|
|
10
|
+
else
|
|
11
|
+
{}
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
attr_reader :path, :data
|
|
16
|
+
|
|
17
|
+
def worktree_root
|
|
18
|
+
ENV.fetch("WORKTREE_ROOT") {
|
|
19
|
+
@data["worktree_root"] || File.join(Dir.home, "dev", "worktrees")
|
|
20
|
+
}
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.config_path
|
|
24
|
+
xdg = ENV["XDG_CONFIG_HOME"]
|
|
25
|
+
if xdg && !xdg.empty?
|
|
26
|
+
dir = File.join(xdg, "bonchi")
|
|
27
|
+
FileUtils.mkdir_p(dir)
|
|
28
|
+
File.join(dir, "config.yml")
|
|
29
|
+
else
|
|
30
|
+
File.expand_path("~/.bonchi.yml")
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
require "yaml"
|
|
2
|
+
require "socket"
|
|
3
|
+
require "set"
|
|
4
|
+
|
|
5
|
+
module Bonchi
|
|
6
|
+
class PortPool
|
|
7
|
+
DEFAULT_MIN = 4000
|
|
8
|
+
DEFAULT_MAX = 5000
|
|
9
|
+
|
|
10
|
+
def initialize
|
|
11
|
+
@path = GlobalConfig.config_path
|
|
12
|
+
load_config
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def allocate(worktree_path, port_names)
|
|
16
|
+
prune_stale
|
|
17
|
+
|
|
18
|
+
existing = @allocated[worktree_path] || {}
|
|
19
|
+
if port_names.all? { |name| existing[name] }
|
|
20
|
+
port_names.each_with_object({}) do |name, result|
|
|
21
|
+
result[name] = existing[name]
|
|
22
|
+
puts "Reusing port #{existing[name]} for #{name}"
|
|
23
|
+
end
|
|
24
|
+
else
|
|
25
|
+
used = @allocated.reject { |k, _| k == worktree_path }
|
|
26
|
+
.values.flat_map { |ports| ports.values }.to_set
|
|
27
|
+
|
|
28
|
+
new_ports = {}
|
|
29
|
+
port_names.each do |name|
|
|
30
|
+
port = (@min..@max).find { |p| !used.include?(p) && port_available?(p) }
|
|
31
|
+
abort "Error: no available port for #{name}" unless port
|
|
32
|
+
used << port
|
|
33
|
+
new_ports[name] = port
|
|
34
|
+
puts "Allocated port #{port} for #{name}"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
@allocated[worktree_path] = new_ports
|
|
38
|
+
save_config
|
|
39
|
+
new_ports
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def load_config
|
|
46
|
+
if File.exist?(@path)
|
|
47
|
+
data = YAML.safe_load_file(@path) || {}
|
|
48
|
+
else
|
|
49
|
+
data = {}
|
|
50
|
+
end
|
|
51
|
+
pool = data["port_pool"] || {}
|
|
52
|
+
@min = pool["min"] || DEFAULT_MIN
|
|
53
|
+
@max = pool["max"] || DEFAULT_MAX
|
|
54
|
+
@allocated = pool["allocated"] || {}
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def save_config
|
|
58
|
+
data = {
|
|
59
|
+
"port_pool" => {
|
|
60
|
+
"min" => @min,
|
|
61
|
+
"max" => @max,
|
|
62
|
+
"allocated" => @allocated
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
File.write(@path, YAML.dump(data))
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def prune_stale
|
|
69
|
+
@allocated.delete_if { |path, _| !File.directory?(path) }
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def port_available?(port)
|
|
73
|
+
server = TCPServer.new("127.0.0.1", port)
|
|
74
|
+
server.close
|
|
75
|
+
true
|
|
76
|
+
rescue Errno::EADDRINUSE
|
|
77
|
+
false
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
data/lib/bonchi/setup.rb
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
require "fileutils"
|
|
2
|
+
require "shellwords"
|
|
3
|
+
|
|
4
|
+
module Bonchi
|
|
5
|
+
class Setup
|
|
6
|
+
def initialize(worktree: nil)
|
|
7
|
+
@worktree = worktree || Dir.pwd
|
|
8
|
+
@main_worktree = Git.main_worktree
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def run(args = [])
|
|
12
|
+
if @worktree == @main_worktree
|
|
13
|
+
abort "Error: already in the main worktree"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
config = Config.from_main_worktree
|
|
17
|
+
abort "Error: .worktree.yml not found in main worktree" unless config
|
|
18
|
+
|
|
19
|
+
ENV["MAIN_WORKTREE"] = @main_worktree
|
|
20
|
+
ENV["WORKTREE"] = @worktree
|
|
21
|
+
|
|
22
|
+
puts "Setting up worktree from: #{@main_worktree}"
|
|
23
|
+
|
|
24
|
+
allocate_ports(config.ports) if config.ports.any?
|
|
25
|
+
copy_files(config.copy)
|
|
26
|
+
run_pre_setup(config.pre_setup)
|
|
27
|
+
exec_setup(config.setup, args)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def allocate_ports(port_names)
|
|
33
|
+
pool = PortPool.new
|
|
34
|
+
ports = pool.allocate(@worktree, port_names)
|
|
35
|
+
ports.each { |name, port| ENV[name] = port.to_s }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def copy_files(files)
|
|
39
|
+
files.each do |file|
|
|
40
|
+
src = File.join(@main_worktree, file)
|
|
41
|
+
if File.exist?(src)
|
|
42
|
+
FileUtils.cp(src, File.join(@worktree, file))
|
|
43
|
+
puts "Copied #{file}"
|
|
44
|
+
else
|
|
45
|
+
puts "Warning: #{file} not found in main worktree, skipping"
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def run_pre_setup(commands)
|
|
51
|
+
commands.each do |cmd|
|
|
52
|
+
puts "Running: #{cmd}"
|
|
53
|
+
Dir.chdir(@worktree) do
|
|
54
|
+
system(cmd) || abort("Command failed: #{cmd}")
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def exec_setup(setup_cmd, args)
|
|
60
|
+
puts "\n== Running #{setup_cmd} =="
|
|
61
|
+
Dir.chdir(@worktree) do
|
|
62
|
+
exec(*setup_cmd.shellsplit, *args)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
data/lib/bonchi.rb
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
require_relative "bonchi/version"
|
|
2
|
+
require_relative "bonchi/global_config"
|
|
3
|
+
require_relative "bonchi/git"
|
|
4
|
+
require_relative "bonchi/config"
|
|
5
|
+
require_relative "bonchi/port_pool"
|
|
6
|
+
require_relative "bonchi/setup"
|
|
7
|
+
require_relative "bonchi/cli"
|
|
8
|
+
|
|
9
|
+
module Bonchi
|
|
10
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: bonchi
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0.dev
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Gert Goet
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: thor
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '1.0'
|
|
26
|
+
description: Manage git worktrees with automatic port allocation, file copying, and
|
|
27
|
+
setup commands
|
|
28
|
+
executables:
|
|
29
|
+
- bonchi
|
|
30
|
+
extensions: []
|
|
31
|
+
extra_rdoc_files: []
|
|
32
|
+
files:
|
|
33
|
+
- LICENSE.txt
|
|
34
|
+
- exe/bonchi
|
|
35
|
+
- lib/bonchi.rb
|
|
36
|
+
- lib/bonchi/cli.rb
|
|
37
|
+
- lib/bonchi/config.rb
|
|
38
|
+
- lib/bonchi/git.rb
|
|
39
|
+
- lib/bonchi/global_config.rb
|
|
40
|
+
- lib/bonchi/port_pool.rb
|
|
41
|
+
- lib/bonchi/setup.rb
|
|
42
|
+
- lib/bonchi/version.rb
|
|
43
|
+
homepage: https://github.com/gertgoet/bonchi
|
|
44
|
+
licenses:
|
|
45
|
+
- MIT
|
|
46
|
+
metadata: {}
|
|
47
|
+
rdoc_options: []
|
|
48
|
+
require_paths:
|
|
49
|
+
- lib
|
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '3.3'
|
|
55
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
56
|
+
requirements:
|
|
57
|
+
- - ">="
|
|
58
|
+
- !ruby/object:Gem::Version
|
|
59
|
+
version: '0'
|
|
60
|
+
requirements: []
|
|
61
|
+
rubygems_version: 4.0.3
|
|
62
|
+
specification_version: 4
|
|
63
|
+
summary: Git worktree manager
|
|
64
|
+
test_files: []
|