workroom 0.1.0 → 1.1.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/README.md +39 -10
- data/bin/workroom +2 -2
- data/lib/workroom/commands.rb +229 -46
- data/lib/workroom/config.rb +78 -0
- data/lib/workroom/name_generator.rb +42 -0
- data/lib/workroom/version.rb +1 -1
- metadata +17 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ba934ec40dd2fd562407526db171825603588166a1cdcbd757115815f2ef463a
|
|
4
|
+
data.tar.gz: f2b210d1f30f5efa1e8d00c9c0f06dc555439fae28e1decd1e40365fd58b84d5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e26cfda013486738f9b16ee329048185be9b4dfc16d391b30ee36427c673b594e21327626c55de188dd619e12eeca11c5087dc2c54a94b592a3db0610acc67fc
|
|
7
|
+
data.tar.gz: '02567952050efaf0862f589097a5808d37c2bb48733cf20c06d57381f92491c7bcc1170b4f36895b461d79849bb4cfcd5cd2f60b2450761c756c3761aad59264'
|
data/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Workroom
|
|
2
2
|
|
|
3
|
-
Create and manage local development workrooms using [
|
|
3
|
+
Create and manage local development workrooms using [Git](https://git-scm.com/) worktrees or [Jujutsu](https://martinvonz.github.io/jj/) workspaces.
|
|
4
4
|
|
|
5
|
-
A workroom is an isolated copy of your project
|
|
5
|
+
A workroom is an isolated copy of your project, allowing you to work on multiple branches or features simultaneously without stashing or switching contexts. Workrooms are created under a centralized directory (`~/workrooms` by default, configurable via `workrooms_dir` in `~/.config/workroom/config.json`).
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
@@ -23,17 +23,29 @@ gem install workroom
|
|
|
23
23
|
## Requirements
|
|
24
24
|
|
|
25
25
|
- Ruby >= 3.1
|
|
26
|
-
- [JJ (Jujutsu)](https://martinvonz.github.io/jj/) or Git
|
|
26
|
+
- [JJ (Jujutsu)](https://martinvonz.github.io/jj/) or [Git](https://git-scm.com/)
|
|
27
27
|
|
|
28
28
|
## Usage
|
|
29
29
|
|
|
30
30
|
### Create a workroom
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
|
-
workroom create
|
|
33
|
+
workroom create
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
A random friendly name (e.g. `swift-meadow`) is auto-generated. Workroom automatically detects whether you're using JJ or Git and uses the appropriate mechanism (JJ workspace or git worktree).
|
|
37
|
+
|
|
38
|
+
Alias: `workroom c`
|
|
39
|
+
|
|
40
|
+
### List workrooms
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
workroom list
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Lists all workrooms for the current project. When run from outside a known project, lists all workrooms grouped by parent project. When run from inside a workroom, shows the parent project path.
|
|
47
|
+
|
|
48
|
+
Aliases: `workroom ls`, `workroom l`
|
|
37
49
|
|
|
38
50
|
### Delete a workroom
|
|
39
51
|
|
|
@@ -43,14 +55,25 @@ workroom delete my-feature
|
|
|
43
55
|
|
|
44
56
|
Removes the workspace/worktree and cleans up the directory. You'll be prompted for confirmation before deletion.
|
|
45
57
|
|
|
58
|
+
When run without a name, an interactive multi-select menu is shown, allowing you to pick one or more workrooms to delete:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
workroom delete
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
To skip the confirmation prompt (useful for scripting), pass `--confirm` with the workroom name:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
workroom delete my-feature --confirm my-feature
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Alias: `workroom d`
|
|
71
|
+
|
|
46
72
|
### Options
|
|
47
73
|
|
|
48
74
|
- `-v`, `--verbose` - Print detailed output
|
|
49
75
|
- `-p`, `--pretend` - Run through the command without making changes (dry run)
|
|
50
|
-
|
|
51
|
-
### Naming rules
|
|
52
|
-
|
|
53
|
-
Workroom names must be alphanumeric (dashes and underscores allowed) and must not start or end with a dash or underscore.
|
|
76
|
+
- `--confirm NAME` - Skip delete confirmation when NAME matches the workroom being deleted
|
|
54
77
|
|
|
55
78
|
## Setup and teardown scripts
|
|
56
79
|
|
|
@@ -62,7 +85,13 @@ Place an executable script at `scripts/workroom_setup` in your project. It will
|
|
|
62
85
|
|
|
63
86
|
### Teardown script
|
|
64
87
|
|
|
65
|
-
Place an executable script at `scripts/workroom_teardown` in your project. It will run
|
|
88
|
+
Place an executable script at `scripts/workroom_teardown` in your project. It will run inside the workroom directory before it is deleted.
|
|
89
|
+
|
|
90
|
+
### Environment variables
|
|
91
|
+
|
|
92
|
+
The following environment variables are available to setup and teardown scripts:
|
|
93
|
+
|
|
94
|
+
- `WORKROOM_PARENT_DIR` - The absolute path to the parent project directory. Since scripts run inside the workroom directory, this lets you reference files in the original project root.
|
|
66
95
|
|
|
67
96
|
## Rails integration
|
|
68
97
|
|
data/bin/workroom
CHANGED
data/lib/workroom/commands.rb
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
require 'open3'
|
|
4
4
|
require 'thor'
|
|
5
|
+
require 'tty-prompt'
|
|
6
|
+
require 'pathname'
|
|
5
7
|
|
|
6
8
|
module Workroom
|
|
7
9
|
class Commands < Thor
|
|
@@ -19,15 +21,19 @@ module Workroom
|
|
|
19
21
|
true
|
|
20
22
|
end
|
|
21
23
|
|
|
22
|
-
|
|
24
|
+
map 'c' => :create
|
|
25
|
+
map 'd' => :delete
|
|
26
|
+
map %w[ls l] => :list
|
|
27
|
+
|
|
28
|
+
desc 'create|c', 'Create a new workroom'
|
|
23
29
|
long_desc <<-DESC, wrap: false
|
|
24
|
-
Create a new workroom
|
|
25
|
-
|
|
30
|
+
Create a new workroom at the same level as your main project directory, using JJ workspaces
|
|
31
|
+
if available, otherwise falling back to git worktrees. A random friendly name is
|
|
32
|
+
auto-generated.
|
|
26
33
|
DESC
|
|
27
|
-
def create
|
|
28
|
-
@name = name
|
|
34
|
+
def create
|
|
29
35
|
check_not_in_workroom!
|
|
30
|
-
|
|
36
|
+
@name = generate_unique_name
|
|
31
37
|
|
|
32
38
|
if !options[:pretend]
|
|
33
39
|
if workroom_exists?
|
|
@@ -36,26 +42,76 @@ module Workroom
|
|
|
36
42
|
end
|
|
37
43
|
|
|
38
44
|
if workroom_path.exist?
|
|
39
|
-
raise_error DirExistsError,
|
|
45
|
+
raise_error DirExistsError,
|
|
46
|
+
"Workroom directory '#{display_path(workroom_path)}' already exists!"
|
|
40
47
|
end
|
|
41
48
|
end
|
|
42
49
|
|
|
43
50
|
create_workroom
|
|
51
|
+
update_config(:add)
|
|
44
52
|
run_setup_script
|
|
45
53
|
|
|
46
54
|
say
|
|
47
|
-
say "Workroom '#{name}' created successfully at #{workroom_path}.", :green
|
|
55
|
+
say "Workroom '#{name}' created successfully at #{display_path(workroom_path)}.", :green
|
|
48
56
|
|
|
49
|
-
|
|
57
|
+
if @setup_result
|
|
58
|
+
say 'Setup script output:', :blue
|
|
59
|
+
say @setup_result
|
|
60
|
+
end
|
|
50
61
|
|
|
51
|
-
say
|
|
52
|
-
say @setup_result
|
|
62
|
+
say
|
|
53
63
|
end
|
|
54
64
|
|
|
55
|
-
desc '
|
|
56
|
-
def
|
|
57
|
-
|
|
65
|
+
desc 'list|l|ls', 'List all workrooms for the current project'
|
|
66
|
+
def list
|
|
67
|
+
project_path, project = config.find_current_project
|
|
68
|
+
|
|
69
|
+
# Inside a workroom
|
|
70
|
+
if project && Pathname.pwd.to_s != project_path
|
|
71
|
+
say 'You are already in a workroom.', :yellow
|
|
72
|
+
say "Parent project is at #{display_path(project_path)}"
|
|
73
|
+
return
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Inside a parent project
|
|
77
|
+
if project
|
|
78
|
+
workrooms = project['workrooms']
|
|
79
|
+
if !workrooms || workrooms.empty?
|
|
80
|
+
say 'No workrooms found for this project.'
|
|
81
|
+
return
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
list_workrooms(workrooms, project['vcs'])
|
|
85
|
+
return
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Neither — list all workrooms grouped by parent
|
|
89
|
+
if config.projects_with_workrooms.empty?
|
|
90
|
+
say 'No workrooms found.'
|
|
91
|
+
return
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
config.projects_with_workrooms.each do |path, proj|
|
|
95
|
+
say "#{display_path(path)}:"
|
|
96
|
+
inside path do
|
|
97
|
+
list_workrooms(proj['workrooms'], proj['vcs'])
|
|
98
|
+
end
|
|
99
|
+
say
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
desc 'delete|d [NAME]', 'Delete an existing workroom'
|
|
104
|
+
method_option :confirm, type: :string,
|
|
105
|
+
desc: 'Skip confirmation if value matches the workroom name'
|
|
106
|
+
def delete(name = nil)
|
|
58
107
|
check_not_in_workroom!
|
|
108
|
+
|
|
109
|
+
if !name
|
|
110
|
+
interactive_delete
|
|
111
|
+
return
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
@name = name
|
|
59
115
|
validate_name!
|
|
60
116
|
|
|
61
117
|
if !options[:pretend]
|
|
@@ -64,30 +120,19 @@ module Workroom
|
|
|
64
120
|
raise_error exception, "#{vcs_label} '#{name}' does not exist!"
|
|
65
121
|
end
|
|
66
122
|
|
|
67
|
-
if
|
|
123
|
+
if options[:confirm]
|
|
124
|
+
if options[:confirm] != name
|
|
125
|
+
raise_error ArgumentError,
|
|
126
|
+
"--confirm value '#{options[:confirm]}' does not match " \
|
|
127
|
+
"workroom name '#{name}'."
|
|
128
|
+
end
|
|
129
|
+
elsif !yes?("Are you sure you want to delete workroom '#{name}'?")
|
|
68
130
|
say_error "Aborting. Workroom '#{name}' was not deleted.", :yellow
|
|
69
131
|
return
|
|
70
132
|
end
|
|
71
133
|
end
|
|
72
134
|
|
|
73
|
-
|
|
74
|
-
cleanup_directory if jj?
|
|
75
|
-
run_teardown_script
|
|
76
|
-
|
|
77
|
-
say
|
|
78
|
-
say "Workroom '#{name}' deleted successfully.", :green
|
|
79
|
-
|
|
80
|
-
if !jj?
|
|
81
|
-
say
|
|
82
|
-
say "Note: Git branch '#{name}' was not deleted."
|
|
83
|
-
say " Delete manually with `git branch -D #{name}` if needed."
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
return if !@teardown_result
|
|
87
|
-
|
|
88
|
-
say
|
|
89
|
-
say 'Teardown script output:', :blue
|
|
90
|
-
say @teardown_result
|
|
135
|
+
delete_by_name(name)
|
|
91
136
|
end
|
|
92
137
|
|
|
93
138
|
private
|
|
@@ -95,13 +140,14 @@ module Workroom
|
|
|
95
140
|
def run_setup_script
|
|
96
141
|
return if !setup_script.exist?
|
|
97
142
|
|
|
143
|
+
parent_dir = Pathname.pwd.to_s
|
|
98
144
|
inside workroom_path do
|
|
99
|
-
run_user_script :setup, setup_script_to_run.to_s
|
|
145
|
+
run_user_script :setup, setup_script_to_run.to_s, parent_dir
|
|
100
146
|
end
|
|
101
147
|
end
|
|
102
148
|
|
|
103
149
|
def setup_script
|
|
104
|
-
@setup_script ||=
|
|
150
|
+
@setup_script ||= Pathname.pwd.join('scripts', 'workroom_setup')
|
|
105
151
|
end
|
|
106
152
|
|
|
107
153
|
def setup_script_to_run
|
|
@@ -111,7 +157,10 @@ module Workroom
|
|
|
111
157
|
def run_teardown_script
|
|
112
158
|
return if !teardown_script.exist?
|
|
113
159
|
|
|
114
|
-
|
|
160
|
+
parent_dir = Pathname.pwd.to_s
|
|
161
|
+
inside workroom_path do
|
|
162
|
+
run_user_script :teardown, teardown_script_to_run.to_s, parent_dir
|
|
163
|
+
end
|
|
115
164
|
end
|
|
116
165
|
|
|
117
166
|
def teardown_script
|
|
@@ -122,7 +171,7 @@ module Workroom
|
|
|
122
171
|
teardown_script
|
|
123
172
|
end
|
|
124
173
|
|
|
125
|
-
def run_user_script(type, command)
|
|
174
|
+
def run_user_script(type, command, parent_dir)
|
|
126
175
|
return if behavior != :invoke
|
|
127
176
|
|
|
128
177
|
destination = relative_to_original_destination_root(destination_root, false)
|
|
@@ -131,7 +180,7 @@ module Workroom
|
|
|
131
180
|
|
|
132
181
|
return if options[:pretend]
|
|
133
182
|
|
|
134
|
-
result, status = Open3.capture2e(command)
|
|
183
|
+
result, status = Open3.capture2e({ 'WORKROOM_PARENT_DIR' => parent_dir }, command)
|
|
135
184
|
|
|
136
185
|
instance_variable_set :"@#{type}_result", result
|
|
137
186
|
|
|
@@ -147,8 +196,20 @@ module Workroom
|
|
|
147
196
|
raise exception_class, message
|
|
148
197
|
end
|
|
149
198
|
|
|
199
|
+
def config
|
|
200
|
+
@config ||= Config.new
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def workrooms_dir
|
|
204
|
+
@workrooms_dir ||= config.workrooms_dir
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def vcs_name
|
|
208
|
+
"workroom/#{name}"
|
|
209
|
+
end
|
|
210
|
+
|
|
150
211
|
def workroom_path
|
|
151
|
-
@workroom_path ||=
|
|
212
|
+
@workroom_path ||= workrooms_dir.join(name)
|
|
152
213
|
end
|
|
153
214
|
|
|
154
215
|
def jj?
|
|
@@ -163,9 +224,9 @@ module Workroom
|
|
|
163
224
|
say_status :repo, 'Detected Git'
|
|
164
225
|
:git
|
|
165
226
|
else
|
|
166
|
-
say_status :repo, 'No supported VCS detected', :red
|
|
227
|
+
say_status :repo, 'No supported VCS detected in this directory.', :red
|
|
167
228
|
raise_error UnsupportedVCSError, <<~_
|
|
168
|
-
No supported VCS detected. Workroom requires either
|
|
229
|
+
No supported VCS detected in this directory. Workroom requires either Git or Jujutsu to manage workspaces.
|
|
169
230
|
_
|
|
170
231
|
end
|
|
171
232
|
end
|
|
@@ -179,7 +240,7 @@ module Workroom
|
|
|
179
240
|
end
|
|
180
241
|
|
|
181
242
|
def jj_workspace_exists?
|
|
182
|
-
jj_workspaces.include?(
|
|
243
|
+
jj_workspaces.include?(vcs_name)
|
|
183
244
|
end
|
|
184
245
|
|
|
185
246
|
def git_worktree_exists?
|
|
@@ -230,22 +291,106 @@ module Workroom
|
|
|
230
291
|
def check_not_in_workroom!
|
|
231
292
|
return if !Pathname.pwd.join('.Workroom').exist?
|
|
232
293
|
|
|
233
|
-
say_status :create, name, :red
|
|
234
294
|
raise_error InWorkroomError, <<~_
|
|
235
295
|
Looks like you are already in a workroom. Run this command from the root of your main development directory, not from within an existing workroom.
|
|
236
296
|
_
|
|
237
297
|
end
|
|
238
298
|
|
|
299
|
+
def interactive_delete
|
|
300
|
+
_, project = config.find_current_project
|
|
301
|
+
|
|
302
|
+
if !project || !project['workrooms'] || project['workrooms'].empty?
|
|
303
|
+
say 'No workrooms found for this project.'
|
|
304
|
+
return
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
workrooms = project['workrooms']
|
|
308
|
+
prompt = TTY::Prompt.new
|
|
309
|
+
selected = prompt.multi_select(
|
|
310
|
+
'Select workrooms to delete:',
|
|
311
|
+
workrooms.keys
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
if selected.empty?
|
|
315
|
+
say_error 'Aborting. No workrooms were selected.', :yellow
|
|
316
|
+
return
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
names_list = selected.map { |n| "'#{n}'" }.join(', ')
|
|
320
|
+
if !yes?("Are you sure you want to delete #{selected.size} workroom(s): #{names_list}?")
|
|
321
|
+
say_error 'Aborting. No workrooms were deleted.', :yellow
|
|
322
|
+
return
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
selected.each { |n| delete_by_name(n) }
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
def delete_by_name(selected_name)
|
|
329
|
+
@name = selected_name
|
|
330
|
+
@workroom_path = nil
|
|
331
|
+
|
|
332
|
+
run_teardown_script
|
|
333
|
+
delete_workroom
|
|
334
|
+
cleanup_directory if jj?
|
|
335
|
+
update_config(:remove)
|
|
336
|
+
|
|
337
|
+
say "Workroom '#{name}' deleted successfully.", :green
|
|
338
|
+
|
|
339
|
+
if !jj?
|
|
340
|
+
say
|
|
341
|
+
say "Note: Git branch '#{vcs_name}' was not deleted."
|
|
342
|
+
say " Delete manually with `git branch -D #{vcs_name}` if needed."
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
return if !@teardown_result
|
|
346
|
+
|
|
347
|
+
say
|
|
348
|
+
say 'Teardown script output:', :blue
|
|
349
|
+
say @teardown_result
|
|
350
|
+
say
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
def generate_unique_name
|
|
354
|
+
generator = NameGenerator.new
|
|
355
|
+
last_name = nil
|
|
356
|
+
|
|
357
|
+
5.times do
|
|
358
|
+
last_name = generator.generate
|
|
359
|
+
if !workroom_exists_for?(last_name) && !workroom_path_for(last_name).exist?
|
|
360
|
+
return last_name
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
loop do
|
|
365
|
+
candidate = "#{last_name}-#{rand(10..99)}"
|
|
366
|
+
if !workroom_exists_for?(candidate) && !workroom_path_for(candidate).exist?
|
|
367
|
+
return candidate
|
|
368
|
+
end
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
def workroom_exists_for?(candidate)
|
|
373
|
+
@name = candidate
|
|
374
|
+
@workroom_path = nil
|
|
375
|
+
workroom_exists?
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
def workroom_path_for(candidate)
|
|
379
|
+
workrooms_dir.join(candidate)
|
|
380
|
+
end
|
|
381
|
+
|
|
239
382
|
def create_workroom
|
|
383
|
+
FileUtils.mkdir_p(workrooms_dir) if !workrooms_dir.exist?
|
|
384
|
+
|
|
240
385
|
if testing?
|
|
241
386
|
FileUtils.copy('./', workroom_path)
|
|
242
387
|
return
|
|
243
388
|
end
|
|
244
389
|
|
|
245
390
|
if jj?
|
|
246
|
-
run "jj workspace add #{workroom_path}"
|
|
391
|
+
run "jj workspace add #{workroom_path} --name #{vcs_name}"
|
|
247
392
|
else
|
|
248
|
-
run "git worktree add -b #{
|
|
393
|
+
run "git worktree add -b #{vcs_name} #{workroom_path}"
|
|
249
394
|
end
|
|
250
395
|
end
|
|
251
396
|
|
|
@@ -256,7 +401,7 @@ module Workroom
|
|
|
256
401
|
end
|
|
257
402
|
|
|
258
403
|
if jj?
|
|
259
|
-
run "jj workspace forget #{
|
|
404
|
+
run "jj workspace forget #{vcs_name}"
|
|
260
405
|
else
|
|
261
406
|
run "git worktree remove #{workroom_path} --force"
|
|
262
407
|
end
|
|
@@ -268,6 +413,16 @@ module Workroom
|
|
|
268
413
|
remove_dir(workroom_path, verbose:)
|
|
269
414
|
end
|
|
270
415
|
|
|
416
|
+
def update_config(action)
|
|
417
|
+
return if options[:pretend]
|
|
418
|
+
|
|
419
|
+
if action == :add
|
|
420
|
+
config.add_workroom Pathname.pwd.to_s, name, workroom_path.to_s, vcs
|
|
421
|
+
else
|
|
422
|
+
config.remove_workroom Pathname.pwd.to_s, name
|
|
423
|
+
end
|
|
424
|
+
end
|
|
425
|
+
|
|
271
426
|
def run(command, config = {})
|
|
272
427
|
if !config[:force] && testing?
|
|
273
428
|
raise TestError, "Command execution blocked during testing: `#{command}`"
|
|
@@ -290,5 +445,33 @@ module Workroom
|
|
|
290
445
|
def testing?
|
|
291
446
|
ENV['WORKROOM_TEST'] == '1'
|
|
292
447
|
end
|
|
448
|
+
|
|
449
|
+
def display_path(path)
|
|
450
|
+
path.to_s.sub(/\A#{Regexp.escape(Dir.home)}/, '~')
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
def list_workrooms(workrooms, vcs)
|
|
454
|
+
rows = workrooms.map do |name, info|
|
|
455
|
+
warnings = workroom_warnings(name, info, vcs)
|
|
456
|
+
row = [shell.set_color(name, :bold), shell.set_color(display_path(info['path']), :black)]
|
|
457
|
+
row << shell.set_color("[#{warnings.join(', ')}]", :yellow) if warnings.any?
|
|
458
|
+
row
|
|
459
|
+
end
|
|
460
|
+
print_table rows, indent: 2
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
def workroom_warnings(name, info, stored_vcs)
|
|
464
|
+
warnings = []
|
|
465
|
+
warnings << 'directory not found' if !Dir.exist?(info['path'])
|
|
466
|
+
if !testing?
|
|
467
|
+
vcs_missing = if stored_vcs == 'jj'
|
|
468
|
+
!jj_workspaces.include?("workroom/#{name}")
|
|
469
|
+
elsif stored_vcs == 'git'
|
|
470
|
+
git_worktrees.none? { |path| File.basename(path) == name }
|
|
471
|
+
end
|
|
472
|
+
warnings << "#{stored_vcs} workspace not found" if vcs_missing
|
|
473
|
+
end
|
|
474
|
+
warnings
|
|
475
|
+
end
|
|
293
476
|
end
|
|
294
477
|
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
module Workroom
|
|
6
|
+
class Config
|
|
7
|
+
CONFIG_DIR = File.expand_path('~/.config/workroom')
|
|
8
|
+
DEFAULT_WORKROOMS_DIR = '~/workrooms'
|
|
9
|
+
|
|
10
|
+
def config_path
|
|
11
|
+
@config_path ||= File.join(CONFIG_DIR, 'config.json')
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def read
|
|
15
|
+
return {} if !File.exist?(config_path)
|
|
16
|
+
|
|
17
|
+
JSON.parse(File.read(config_path))
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def write(data)
|
|
21
|
+
dir = File.dirname(config_path)
|
|
22
|
+
FileUtils.mkdir_p(dir)
|
|
23
|
+
File.write(config_path, JSON.pretty_generate(data))
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def add_workroom(parent_path, name, workroom_path, vcs)
|
|
27
|
+
update do |data|
|
|
28
|
+
data[parent_path] ||= { 'vcs' => vcs.to_s, 'workrooms' => {} }
|
|
29
|
+
data[parent_path]['vcs'] = vcs.to_s
|
|
30
|
+
data[parent_path]['workrooms'][name] = { 'path' => workroom_path }
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def remove_workroom(parent_path, name)
|
|
35
|
+
update do |data|
|
|
36
|
+
return if !data[parent_path]
|
|
37
|
+
|
|
38
|
+
data[parent_path]['workrooms'].delete(name)
|
|
39
|
+
data.delete(parent_path) if data[parent_path]['workrooms'].empty?
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Find the project for the current directory. If pwd is a project in the config, return it
|
|
44
|
+
# directly. Otherwise, check if pwd is a workroom path under any project.
|
|
45
|
+
def find_current_project
|
|
46
|
+
data = read
|
|
47
|
+
pwd = Pathname.pwd.to_s
|
|
48
|
+
return [pwd, data[pwd]] if data.key?(pwd)
|
|
49
|
+
|
|
50
|
+
data.each do |project_path, project|
|
|
51
|
+
workrooms = project['workrooms'] || {}
|
|
52
|
+
return [project_path, project] if workrooms.any? { |_, info| info['path'] == pwd }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
[pwd, nil]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def projects_with_workrooms
|
|
59
|
+
@projects_with_workrooms ||= read.select { |_, p| p['workrooms']&.any? }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def workrooms_dir
|
|
63
|
+
Pathname.new(File.expand_path(read['workrooms_dir'] || DEFAULT_WORKROOMS_DIR))
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def workrooms_dir=(path)
|
|
67
|
+
update { |data| data['workrooms_dir'] = path }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def update
|
|
73
|
+
data = read
|
|
74
|
+
yield data
|
|
75
|
+
write data
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Workroom
|
|
4
|
+
class NameGenerator
|
|
5
|
+
ADJECTIVES = %w[
|
|
6
|
+
agile amber apt azure bold brave bright brisk calm cedar clear cold cool
|
|
7
|
+
coral crisp cyan damp dark dawn deep deft dry dusk dusty easy even fair
|
|
8
|
+
fast firm flat fond free fresh full glad gold good gray green hale happy
|
|
9
|
+
hazy high idle jade keen kind lark last lean left light lime long lost
|
|
10
|
+
loud lush mild mint misty mossy neat nice noble north novel oak odd opal
|
|
11
|
+
open pale peak pine pink plum proud pure quick quiet rapid rare red rich
|
|
12
|
+
ripe rosy ruby safe sage salt sharp shy silk slim slow smart snowy soft
|
|
13
|
+
solid south stark steel still stout sunny sure swift tall tame teal thin
|
|
14
|
+
tidy trim true vivid warm west wide wild wise young
|
|
15
|
+
].freeze
|
|
16
|
+
|
|
17
|
+
NOUNS = %w[
|
|
18
|
+
acre arch aspen badge bank bark basin bay beach bear birch blade blaze
|
|
19
|
+
bloom bolt bone bow brace brass breeze brick bridge brook brush canopy
|
|
20
|
+
cape cave cedar chain chime cliff cloud clover colt coop coral core
|
|
21
|
+
cove crane creek crest crow curve dale dawn deer delta dew dock dove
|
|
22
|
+
drake drift dune dusk eagle edge elm ember fawn feather fern field finch
|
|
23
|
+
fjord flame flask flint float flora flute fog font forge fox frost gate
|
|
24
|
+
glade glen globe gorge grain grove gulf gust hare haven hawk hazel
|
|
25
|
+
heath hedge heron hill hollow horn inlet isle ivy jade jewel knoll
|
|
26
|
+
lake larch lark latch laurel leaf ledge light lilac lily linen lodge
|
|
27
|
+
loft lynx maple marsh meadow mesa mint mirror mist moon moss mound
|
|
28
|
+
nest north oak opal orbit orion otter palm pass path peak pearl
|
|
29
|
+
pebble perch petal pine pixel plume pond pool porch prism quail
|
|
30
|
+
quarry quartz rail rain raven reef ridge river robin rock root rose
|
|
31
|
+
ruby sage sand scope seal seed shade shell shore silk sky slate
|
|
32
|
+
slope smoke snow south spark spire spoke spring spruce star stem
|
|
33
|
+
stone stork storm strand surf swift tern thyme tide timber tower
|
|
34
|
+
trail tree vale vault vine vista wand ward wave west wheat willow
|
|
35
|
+
wind wing wolf wren yard
|
|
36
|
+
].freeze
|
|
37
|
+
|
|
38
|
+
def generate
|
|
39
|
+
"#{ADJECTIVES.sample}-#{NOUNS.sample}"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
data/lib/workroom/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: workroom
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Joel Moss
|
|
@@ -23,6 +23,20 @@ dependencies:
|
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
25
|
version: '1.5'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: tty-prompt
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0.23'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0.23'
|
|
26
40
|
- !ruby/object:Gem::Dependency
|
|
27
41
|
name: zeitwerk
|
|
28
42
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -51,7 +65,9 @@ files:
|
|
|
51
65
|
- bin/workroom
|
|
52
66
|
- lib/workroom.rb
|
|
53
67
|
- lib/workroom/commands.rb
|
|
68
|
+
- lib/workroom/config.rb
|
|
54
69
|
- lib/workroom/engine.rb
|
|
70
|
+
- lib/workroom/name_generator.rb
|
|
55
71
|
- lib/workroom/version.rb
|
|
56
72
|
homepage: https://github.com/joelmoss/workroom
|
|
57
73
|
licenses:
|