bonchi 0.2.0 → 0.4.0
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 +25 -4
- data/lib/bonchi/colors.rb +20 -0
- data/lib/bonchi/config.rb +42 -3
- data/lib/bonchi/git.rb +10 -2
- data/lib/bonchi/setup.rb +78 -7
- data/lib/bonchi/version.rb +1 -1
- data/lib/bonchi.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d0506e896860d23e52035729f193224f9a1d2a54799b2f7deb73691ddde7ba61
|
|
4
|
+
data.tar.gz: 831b6a75a71e40d6ccb7d09c8db83aafff0a56ea301d6083649b440245cfb3c6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7e483c14fee4002b3cbcb0816a4cd351b190ff50a7e01e0db48606e749df21a954d71ea79c8ad7ae982807f90618f44f6461fbd26ad7814196c1c22c53c51bd0
|
|
7
|
+
data.tar.gz: 178967a54213235cd13552fb36283a12ccb4ea669533df6abad0a0049fb3063cc7d6332e9e076e7c6cb407650628d597eb884bd3e5827a11cecc355eef3bda80
|
data/lib/bonchi/cli.rb
CHANGED
|
@@ -127,16 +127,18 @@ module Bonchi
|
|
|
127
127
|
desc "remove BRANCH", "Remove a worktree"
|
|
128
128
|
long_desc <<~DESC
|
|
129
129
|
Remove a worktree and its directory. Refuses to remove worktrees
|
|
130
|
-
with uncommitted changes or untracked files
|
|
130
|
+
with uncommitted changes or untracked files unless --force is used.
|
|
131
131
|
|
|
132
132
|
Aliases: rm
|
|
133
133
|
DESC
|
|
134
|
+
option :force, type: :boolean, default: false, desc: "Force removal even with uncommitted changes"
|
|
134
135
|
def remove(branch)
|
|
135
136
|
path = Git.worktree_path_for(branch)
|
|
136
137
|
abort "Error: No worktree found for branch: #{branch}" unless path
|
|
137
138
|
|
|
138
|
-
Git.worktree_remove(path)
|
|
139
|
+
Git.worktree_remove(path, force: options[:force])
|
|
139
140
|
puts "Removed worktree: #{path}"
|
|
141
|
+
signal_cd(Git.main_worktree)
|
|
140
142
|
end
|
|
141
143
|
|
|
142
144
|
desc "prune", "Prune stale worktree admin files"
|
|
@@ -183,18 +185,37 @@ module Bonchi
|
|
|
183
185
|
end
|
|
184
186
|
end
|
|
185
187
|
|
|
186
|
-
WORKTREE_YML_TEMPLATE = <<~
|
|
188
|
+
WORKTREE_YML_TEMPLATE = <<~YAML
|
|
189
|
+
# Worktree configuration for bonchi.
|
|
190
|
+
# See https://github.com/eval/bonchi
|
|
191
|
+
|
|
187
192
|
# Files to copy from the main worktree before setup.
|
|
188
193
|
# copy:
|
|
189
194
|
# - .env.local
|
|
190
195
|
|
|
196
|
+
# Files to symlink from the main worktree (useful for large directories).
|
|
197
|
+
# link:
|
|
198
|
+
# - node_modules
|
|
199
|
+
|
|
191
200
|
# Env var names to allocate unique ports for (from global pool).
|
|
192
201
|
# ports:
|
|
193
202
|
# - PORT
|
|
194
203
|
|
|
204
|
+
# Regex replacements in copied files. Env vars ($VAR) are expanded.
|
|
205
|
+
# Short form:
|
|
206
|
+
# replace:
|
|
207
|
+
# .env.local:
|
|
208
|
+
# - "^PORT=.*": "PORT=$PORT"
|
|
209
|
+
# Full form (with optional missing: warn, default: halt):
|
|
210
|
+
# replace:
|
|
211
|
+
# .env.local:
|
|
212
|
+
# - match: "^PORT=.*"
|
|
213
|
+
# with: "PORT=$PORT"
|
|
214
|
+
# missing: warn
|
|
215
|
+
|
|
195
216
|
# Commands to run before the setup command (port env vars are available).
|
|
196
217
|
# pre_setup:
|
|
197
|
-
# -
|
|
218
|
+
# - echo "preparing..."
|
|
198
219
|
|
|
199
220
|
# The setup command to run (default: bin/setup).
|
|
200
221
|
setup: bin/setup
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Bonchi
|
|
2
|
+
module Colors
|
|
3
|
+
private
|
|
4
|
+
|
|
5
|
+
def color(name)
|
|
6
|
+
return "" if ENV.key?("NO_COLOR")
|
|
7
|
+
|
|
8
|
+
case name
|
|
9
|
+
when :red then "\e[31m"
|
|
10
|
+
when :yellow then "\e[33m"
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def reset
|
|
15
|
+
return "" if ENV.key?("NO_COLOR")
|
|
16
|
+
|
|
17
|
+
"\e[0m"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
data/lib/bonchi/config.rb
CHANGED
|
@@ -2,22 +2,61 @@ require "yaml"
|
|
|
2
2
|
|
|
3
3
|
module Bonchi
|
|
4
4
|
class Config
|
|
5
|
-
|
|
5
|
+
include Colors
|
|
6
|
+
|
|
7
|
+
KNOWN_KEYS = %w[copy link ports replace pre_setup setup].freeze
|
|
8
|
+
|
|
9
|
+
attr_reader :copy, :link, :ports, :replace, :pre_setup, :setup
|
|
6
10
|
|
|
7
11
|
def initialize(path)
|
|
8
12
|
data = YAML.load_file(path)
|
|
13
|
+
|
|
14
|
+
unknown = data.keys - KNOWN_KEYS
|
|
15
|
+
unknown.each { |k| warn "#{color(:yellow)}Warning:#{reset} unknown key '#{k}' in .worktree.yml, ignoring" }
|
|
16
|
+
|
|
9
17
|
@copy = Array(data["copy"])
|
|
18
|
+
@link = Array(data["link"])
|
|
10
19
|
@ports = Array(data["ports"])
|
|
20
|
+
@replace = data["replace"] || {}
|
|
11
21
|
@pre_setup = Array(data["pre_setup"])
|
|
12
22
|
@setup = data["setup"] || "bin/setup"
|
|
23
|
+
|
|
24
|
+
validate!
|
|
13
25
|
end
|
|
14
26
|
|
|
15
27
|
def self.from_main_worktree
|
|
16
|
-
|
|
17
|
-
|
|
28
|
+
from_worktree(Git.main_worktree)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.from_worktree(dir)
|
|
32
|
+
path = File.join(dir, ".worktree.yml")
|
|
18
33
|
return nil unless File.exist?(path)
|
|
19
34
|
|
|
20
35
|
new(path)
|
|
21
36
|
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def validate!
|
|
41
|
+
unless @replace.is_a?(Hash)
|
|
42
|
+
abort "#{color(:red)}Error:#{reset} 'replace' must be a mapping of filename to list of replacements"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
@replace.each do |file, entries|
|
|
46
|
+
unless entries.is_a?(Array)
|
|
47
|
+
abort "#{color(:red)}Error:#{reset} 'replace.#{file}' must be a list of replacements"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
entries.each do |entry|
|
|
51
|
+
unless entry.is_a?(Hash)
|
|
52
|
+
abort "#{color(:red)}Error:#{reset} each replacement in 'replace.#{file}' must be a mapping"
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
unless @setup.is_a?(String)
|
|
58
|
+
abort "#{color(:red)}Error:#{reset} 'setup' must be a string"
|
|
59
|
+
end
|
|
60
|
+
end
|
|
22
61
|
end
|
|
23
62
|
end
|
data/lib/bonchi/git.rb
CHANGED
|
@@ -4,6 +4,11 @@ module Bonchi
|
|
|
4
4
|
module Git
|
|
5
5
|
module_function
|
|
6
6
|
|
|
7
|
+
def current_branch(worktree = nil)
|
|
8
|
+
dir = worktree || Dir.pwd
|
|
9
|
+
`git -C #{dir.shellescape} rev-parse --abbrev-ref HEAD`.strip
|
|
10
|
+
end
|
|
11
|
+
|
|
7
12
|
def repo_name
|
|
8
13
|
url = `git remote get-url origin 2>/dev/null`.strip
|
|
9
14
|
base = url.empty? ? `git rev-parse --show-toplevel`.strip : url
|
|
@@ -45,8 +50,11 @@ module Bonchi
|
|
|
45
50
|
system("git", "worktree", "add", path, "-b", branch, base) || abort("Failed to add worktree")
|
|
46
51
|
end
|
|
47
52
|
|
|
48
|
-
def worktree_remove(path)
|
|
49
|
-
|
|
53
|
+
def worktree_remove(path, force: false)
|
|
54
|
+
cmd = ["git", "worktree", "remove"]
|
|
55
|
+
cmd << "--force" if force
|
|
56
|
+
cmd << path
|
|
57
|
+
system(*cmd) || abort("Failed to remove worktree")
|
|
50
58
|
end
|
|
51
59
|
|
|
52
60
|
def worktree_prune
|
data/lib/bonchi/setup.rb
CHANGED
|
@@ -3,6 +3,8 @@ require "shellwords"
|
|
|
3
3
|
|
|
4
4
|
module Bonchi
|
|
5
5
|
class Setup
|
|
6
|
+
include Colors
|
|
7
|
+
|
|
6
8
|
def initialize(worktree: nil)
|
|
7
9
|
@worktree = worktree || Dir.pwd
|
|
8
10
|
@main_worktree = Git.main_worktree
|
|
@@ -10,19 +12,32 @@ module Bonchi
|
|
|
10
12
|
|
|
11
13
|
def run(args = [])
|
|
12
14
|
if @worktree == @main_worktree
|
|
13
|
-
abort "Error
|
|
15
|
+
abort "#{color(:red)}Error:#{reset} already in the main worktree"
|
|
14
16
|
end
|
|
15
17
|
|
|
16
18
|
config = Config.from_main_worktree
|
|
17
|
-
abort "Error
|
|
19
|
+
abort "#{color(:red)}Error:#{reset} .worktree.yml not found in main worktree" unless config
|
|
18
20
|
|
|
19
|
-
ENV["
|
|
20
|
-
ENV["
|
|
21
|
+
ENV["WORKTREE_MAIN"] = @main_worktree
|
|
22
|
+
ENV["WORKTREE_LINKED"] = @worktree
|
|
23
|
+
ENV["WORKTREE_BRANCH"] = Git.current_branch(@worktree)
|
|
24
|
+
ENV["WORKTREE_BRANCH_SLUG"] = ENV["WORKTREE_BRANCH"].gsub(/[^a-zA-Z0-9_]/, "_")
|
|
25
|
+
ENV["WORKTREE_ROOT"] ||= GlobalConfig.new.worktree_root
|
|
21
26
|
|
|
22
27
|
puts "Setting up worktree from: #{@main_worktree}"
|
|
23
28
|
|
|
24
|
-
allocate_ports(config.ports) if config.ports.any?
|
|
25
29
|
copy_files(config.copy)
|
|
30
|
+
link_files(config.link)
|
|
31
|
+
|
|
32
|
+
# Prefer linked worktree's .worktree.yml if it was copied or already exists
|
|
33
|
+
linked_config = Config.from_worktree(@worktree)
|
|
34
|
+
if linked_config
|
|
35
|
+
puts "Using .worktree.yml from linked worktree"
|
|
36
|
+
config = linked_config
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
allocate_ports(config.ports) if config.ports.any?
|
|
40
|
+
replace_in_files(config.replace) if config.replace.any?
|
|
26
41
|
run_pre_setup(config.pre_setup)
|
|
27
42
|
exec_setup(config.setup, args)
|
|
28
43
|
end
|
|
@@ -35,15 +50,71 @@ module Bonchi
|
|
|
35
50
|
ports.each { |name, port| ENV[name] = port.to_s }
|
|
36
51
|
end
|
|
37
52
|
|
|
53
|
+
def link_files(files)
|
|
54
|
+
files.each do |file|
|
|
55
|
+
src = File.join(@main_worktree, file)
|
|
56
|
+
dest = File.join(@worktree, file)
|
|
57
|
+
|
|
58
|
+
unless File.exist?(src)
|
|
59
|
+
puts "#{color(:yellow)}Warning:#{reset} #{file} not found in main worktree, skipping"
|
|
60
|
+
next
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
FileUtils.mkdir_p(File.dirname(dest))
|
|
64
|
+
FileUtils.rm_rf(dest) if File.exist?(dest) || File.symlink?(dest)
|
|
65
|
+
FileUtils.ln_s(src, dest)
|
|
66
|
+
puts "Linked #{file} -> #{src}"
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
38
70
|
def copy_files(files)
|
|
39
71
|
files.each do |file|
|
|
40
72
|
src = File.join(@main_worktree, file)
|
|
73
|
+
dest = File.join(@worktree, file)
|
|
41
74
|
if File.exist?(src)
|
|
42
|
-
FileUtils.
|
|
75
|
+
FileUtils.mkdir_p(File.dirname(dest))
|
|
76
|
+
FileUtils.cp(src, dest)
|
|
43
77
|
puts "Copied #{file}"
|
|
44
78
|
else
|
|
45
|
-
puts "Warning
|
|
79
|
+
puts "#{color(:yellow)}Warning:#{reset} #{file} not found in main worktree, skipping"
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def replace_in_files(replacements)
|
|
85
|
+
replacements.each do |file, entries|
|
|
86
|
+
path = File.join(@worktree, file)
|
|
87
|
+
abort "#{color(:red)}Error:#{reset} #{file} not found" unless File.exist?(path)
|
|
88
|
+
|
|
89
|
+
content = File.read(path)
|
|
90
|
+
entries.each do |entry|
|
|
91
|
+
if entry.is_a?(Hash) && entry.key?("match")
|
|
92
|
+
pattern = entry["match"]
|
|
93
|
+
replacement = entry["with"]
|
|
94
|
+
missing = entry["missing"] || "halt"
|
|
95
|
+
elsif entry.is_a?(Hash)
|
|
96
|
+
pattern, replacement = entry.first
|
|
97
|
+
missing = "halt"
|
|
98
|
+
else
|
|
99
|
+
abort "#{color(:red)}Error:#{reset} invalid replace entry in #{file}: #{entry.inspect}"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
expanded = replacement.gsub(/\$(\w+)/) { ENV[$1] || abort("#{color(:red)}Error:#{reset} $#{$1} not set") }
|
|
103
|
+
regex = Regexp.new(pattern)
|
|
104
|
+
|
|
105
|
+
unless content.match?(regex)
|
|
106
|
+
if missing == "warn"
|
|
107
|
+
puts "#{color(:yellow)}Warning:#{reset} pattern #{pattern} not found in #{file}, skipping"
|
|
108
|
+
next
|
|
109
|
+
else
|
|
110
|
+
abort "#{color(:red)}Error:#{reset} pattern #{pattern} not found in #{file}"
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
content = content.gsub(regex, expanded)
|
|
115
|
+
puts "Replaced #{pattern} in #{file}"
|
|
46
116
|
end
|
|
117
|
+
File.write(path, content)
|
|
47
118
|
end
|
|
48
119
|
end
|
|
49
120
|
|
data/lib/bonchi/version.rb
CHANGED
data/lib/bonchi.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: bonchi
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Gert Goet
|
|
@@ -34,6 +34,7 @@ files:
|
|
|
34
34
|
- exe/bonchi
|
|
35
35
|
- lib/bonchi.rb
|
|
36
36
|
- lib/bonchi/cli.rb
|
|
37
|
+
- lib/bonchi/colors.rb
|
|
37
38
|
- lib/bonchi/config.rb
|
|
38
39
|
- lib/bonchi/git.rb
|
|
39
40
|
- lib/bonchi/global_config.rb
|