squared 0.0.1
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 +11 -0
- data/README.md +1347 -0
- data/README.ruby.md +50 -0
- data/lib/squared/common/class.rb +18 -0
- data/lib/squared/common/format.rb +151 -0
- data/lib/squared/common/shell.rb +40 -0
- data/lib/squared/common/system.rb +55 -0
- data/lib/squared/common/task.rb +27 -0
- data/lib/squared/common.rb +66 -0
- data/lib/squared/config.rb +240 -0
- data/lib/squared/repo/project/base.rb +357 -0
- data/lib/squared/repo/project/git.rb +521 -0
- data/lib/squared/repo/project/node.rb +394 -0
- data/lib/squared/repo/project/python.rb +104 -0
- data/lib/squared/repo/project/ruby.rb +277 -0
- data/lib/squared/repo/project.rb +49 -0
- data/lib/squared/repo/workspace.rb +427 -0
- data/lib/squared/repo.rb +39 -0
- data/lib/squared/version.rb +5 -0
- data/lib/squared.rb +13 -0
- data/squared.gemspec +29 -0
- metadata +95 -0
@@ -0,0 +1,521 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Squared
|
4
|
+
module Repo
|
5
|
+
module Project
|
6
|
+
class Git < Base
|
7
|
+
include Common::Format
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def tasks
|
11
|
+
%i[pull rebase fetch stash status].freeze
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_sym
|
15
|
+
:git
|
16
|
+
end
|
17
|
+
|
18
|
+
def is_a?(path)
|
19
|
+
if path.is_a?(::String) || path.is_a?(::Pathname)
|
20
|
+
Pathname.new(path).join('.git').directory?
|
21
|
+
else
|
22
|
+
super
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
@@tasks[:git] = {
|
28
|
+
checkout: %i[branch detach force merge],
|
29
|
+
commit: %i[add amend amend-orig all no-all],
|
30
|
+
diff: %i[head cached branch],
|
31
|
+
fetch: %i[all submodules unshallow],
|
32
|
+
files: %i[cached modified deleted others],
|
33
|
+
pull: %i[rebase no-rebase commit no-commit submodules],
|
34
|
+
stash: %i[push pop apply list clear],
|
35
|
+
refs: %i[heads tags],
|
36
|
+
reset: %i[hard mixed soft],
|
37
|
+
rev: %i[commit branch]
|
38
|
+
}.freeze
|
39
|
+
|
40
|
+
def populate(*)
|
41
|
+
super
|
42
|
+
return unless vardir.exist?
|
43
|
+
|
44
|
+
namespace @name do
|
45
|
+
@@tasks[:git].each do |action, flags|
|
46
|
+
namespace action do
|
47
|
+
flags.each do |flag|
|
48
|
+
case action
|
49
|
+
when :pull
|
50
|
+
desc format_desc(action, flag, %w[all tags prune ff-only autostash dry-run])
|
51
|
+
task flag, [:opts] do |_, args|
|
52
|
+
opts = collect_args(args, :opts)
|
53
|
+
pull(flag, opts: opts)
|
54
|
+
end
|
55
|
+
when :fetch
|
56
|
+
desc format_desc(action, flag, %w[tags prune prune-tags depth=n dry-run])
|
57
|
+
task flag, [:opts] do |_, args|
|
58
|
+
opts = collect_args(args, :opts)
|
59
|
+
fetch(flag, opts: opts)
|
60
|
+
end
|
61
|
+
when :commit
|
62
|
+
if flag == :all
|
63
|
+
desc format_desc(action, flag, 'message?')
|
64
|
+
task flag, [:message] do |_, args|
|
65
|
+
commit(:all, message: args.fetch(:message, nil))
|
66
|
+
end
|
67
|
+
else
|
68
|
+
desc format_desc(action, flag, 'pathspec+')
|
69
|
+
task flag, [:pathspec] do |_, args|
|
70
|
+
guard_params(action, flag, args: args, key: :pathspec)
|
71
|
+
commit(flag, collect_args(args, :pathspec))
|
72
|
+
end
|
73
|
+
end
|
74
|
+
when :stash
|
75
|
+
if flag == :push
|
76
|
+
desc format_desc(action, flag, 'pathspec*')
|
77
|
+
task :push, [:pathspec] do |_, args|
|
78
|
+
stash(:push, collect_args(args, :pathspec))
|
79
|
+
end
|
80
|
+
else
|
81
|
+
desc format_desc(action, flag, 'commit?')
|
82
|
+
task flag, [:commit] do |_, args|
|
83
|
+
stash(flag, commit: args.commit)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
when :rev
|
87
|
+
desc format_desc(action, flag, "ref?=HEAD#{flag == :commit ? ',size?' : ''}")
|
88
|
+
task flag, [:ref, :size] do |_, args|
|
89
|
+
rev(flag, ref: args.ref, size: args.size)
|
90
|
+
end
|
91
|
+
when :refs, :files
|
92
|
+
desc format_desc(action, flag, 'grep?=pattern')
|
93
|
+
task flag, [:grep] do |_, args|
|
94
|
+
send(action, flag, grep: args.fetch(:grep, nil))
|
95
|
+
end
|
96
|
+
when :diff
|
97
|
+
case flag
|
98
|
+
when :head
|
99
|
+
desc format_desc(action, flag, 'index?=0,pathspec*')
|
100
|
+
task flag, [:pathspec] do |_, args|
|
101
|
+
files = collect_args(args, :pathspec)
|
102
|
+
index = /^\d+$/.match?(files.first) && !option('index') ? files.shift.to_i : 0
|
103
|
+
diff(:head, files, index: index)
|
104
|
+
end
|
105
|
+
when :cached
|
106
|
+
desc format_desc(action, flag, 'pathspec*')
|
107
|
+
task flag, [:pathspec] do |_, args|
|
108
|
+
diff(:cached, collect_args(args, :pathspec))
|
109
|
+
end
|
110
|
+
when :branch
|
111
|
+
desc format_desc(action, flag, 'name,pathspec*')
|
112
|
+
task flag, [:name, :pathspec] do |_, args|
|
113
|
+
guard_params(action, flag, args: args, key: :name)
|
114
|
+
diff(:branch, collect_args(args, :pathspec), branch: args.name)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
when :checkout
|
118
|
+
case flag
|
119
|
+
when :branch
|
120
|
+
desc format_desc(action, flag, 'name,create?=b|B,commit?,detach?=d')
|
121
|
+
task flag, [:name, :create, :commit, :detach] do |_, args|
|
122
|
+
guard_params(action, flag, args: args, key: :name)
|
123
|
+
create = args.create
|
124
|
+
if args.commit == 'd'
|
125
|
+
detach = 'd'
|
126
|
+
commit = nil
|
127
|
+
elsif create == 'd'
|
128
|
+
create = nil
|
129
|
+
commit = nil
|
130
|
+
detach = 'd'
|
131
|
+
elsif create && create.size > 1
|
132
|
+
commit = create
|
133
|
+
create = nil
|
134
|
+
detach = args.commit
|
135
|
+
else
|
136
|
+
detach = args.detach
|
137
|
+
commit = args.commit
|
138
|
+
end
|
139
|
+
guard_params('checkout', :branch, args: args, key: :create, pat: /^[Bb]$/) if create
|
140
|
+
checkout(:branch, branch: args.name, create: create, commit: commit, detach: detach)
|
141
|
+
end
|
142
|
+
when :detach
|
143
|
+
desc format_desc(action, flag, 'branch/commit?')
|
144
|
+
task flag, [:commit] do |_, args|
|
145
|
+
checkout(:detach, commit: args.commit)
|
146
|
+
end
|
147
|
+
else
|
148
|
+
desc format_desc(action, flag, 'pathspec*')
|
149
|
+
task flag, [:pathspec] do |_, args|
|
150
|
+
checkout(flag, collect_args(args, :pathspec))
|
151
|
+
end
|
152
|
+
end
|
153
|
+
when :reset
|
154
|
+
desc format_desc(action, flag, 'commit')
|
155
|
+
task flag, [:commit] do |_, args|
|
156
|
+
guard_params(action, flag, args: args, key: :commit)
|
157
|
+
reset(flag, commit: args.commit)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def pull(flag = nil, sync: invoked_sync?('pull', flag), opts: [])
|
167
|
+
cmd = git_session 'pull'
|
168
|
+
if flag == :'no-rebase'
|
169
|
+
cmd << '--no-rebase'
|
170
|
+
elsif flag == :rebase || option('rebase')
|
171
|
+
cmd << '--rebase'
|
172
|
+
end
|
173
|
+
if flag == :'no-commit'
|
174
|
+
cmd << '--no-commit'
|
175
|
+
elsif flag == :commit || option('commit')
|
176
|
+
cmd << '--commit'
|
177
|
+
end
|
178
|
+
cmd << '--recurse-submodules' if flag == :submodules || option('recurse-submodules')
|
179
|
+
opts.each do |val|
|
180
|
+
case val
|
181
|
+
when 'all', 'tags', 'prune', 'ff-only', 'autostash', 'dry-run'
|
182
|
+
cmd << "--#{val}"
|
183
|
+
end
|
184
|
+
end
|
185
|
+
source(sync: sync, stderr: true, exception: !@workspace.multitask?)
|
186
|
+
end
|
187
|
+
|
188
|
+
def rebase
|
189
|
+
pull(:rebase, sync: invoked_sync?('rebase'))
|
190
|
+
end
|
191
|
+
|
192
|
+
def fetch(flag = nil, opts: [])
|
193
|
+
cmd = git_session 'fetch'
|
194
|
+
cmd << '--all' if flag == :all || option('all')
|
195
|
+
cmd << '--recurse-submodules' if flag == :submodules || option('recurse-submodules')
|
196
|
+
opts.each do |val|
|
197
|
+
case val
|
198
|
+
when 'tags', 'prune', 'prune-tags', 'dry-run'
|
199
|
+
cmd << "--#{val}"
|
200
|
+
else
|
201
|
+
cmd << "--depth=#{$1}" if /^depth=(\d+)$/.match(val)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
source(sync: invoked_sync?('fetch', flag), stderr: true, exception: !@workspace.multitask?)
|
205
|
+
end
|
206
|
+
|
207
|
+
def stash(flag = :push, files = [], commit: nil)
|
208
|
+
cmd = git_session 'stash', flag.to_s
|
209
|
+
case flag
|
210
|
+
when :apply, :pop
|
211
|
+
cmd << '--index' if option('index')
|
212
|
+
cmd << commit if commit
|
213
|
+
else
|
214
|
+
%w[all staged include-untracked].each { |val| cmd << "--#{val}" if option(val) }
|
215
|
+
append_message option('message')
|
216
|
+
append_pathspec files
|
217
|
+
end
|
218
|
+
source(sync: invoked_sync?('stash', flag), exception: @workspace.exception)
|
219
|
+
end
|
220
|
+
|
221
|
+
def status
|
222
|
+
cmd = git_session 'status', option('long') ? '--long' : '--short'
|
223
|
+
if (val = option('ignore-submodules'))
|
224
|
+
cmd << "--ignore-submodules=#{case val
|
225
|
+
when '0', 'none'
|
226
|
+
'none'
|
227
|
+
when '1', 'untracked'
|
228
|
+
'untracked'
|
229
|
+
when '2', 'dirty'
|
230
|
+
'dirty'
|
231
|
+
else
|
232
|
+
'all'
|
233
|
+
end}"
|
234
|
+
end
|
235
|
+
if (val = option('pathspec'))
|
236
|
+
append_pathspec split_escape(val)
|
237
|
+
end
|
238
|
+
out, banner = source(io: true)
|
239
|
+
if banner && invoked_sync?('status')
|
240
|
+
print_item banner
|
241
|
+
banner = nil
|
242
|
+
end
|
243
|
+
ret = write_lines(out, banner: banner, sub: [
|
244
|
+
{ pat: /^(.)([A-Z])(.+)$/, styles: :red, index: 2 },
|
245
|
+
{ pat: /^([A-Z])(.+)$/, styles: :green },
|
246
|
+
{ pat: /^(\?\?)(.+)$/, styles: :red },
|
247
|
+
{ pat: /^(## )(.+)(\.{3})(.+)$/, styles: [nil, :green, nil, :red], index: -1 }
|
248
|
+
])
|
249
|
+
list_result(ret, 'files', action: 'modified')
|
250
|
+
end
|
251
|
+
|
252
|
+
def reset(flag, commit: nil)
|
253
|
+
source "git reset --#{flag} #{commit}"
|
254
|
+
end
|
255
|
+
|
256
|
+
def checkout(flag, files = [], branch: nil, create: nil, commit: nil, detach: nil)
|
257
|
+
cmd = git_session 'checkout'
|
258
|
+
case flag
|
259
|
+
when :branch
|
260
|
+
cmd << '--detach' if detach == 'd' || option('detach')
|
261
|
+
if create
|
262
|
+
cmd << "-#{create} #{branch}"
|
263
|
+
if (val = option('start-point'))
|
264
|
+
cmd << val
|
265
|
+
end
|
266
|
+
else
|
267
|
+
cmd << branch
|
268
|
+
end
|
269
|
+
cmd << commit if commit
|
270
|
+
when :detach
|
271
|
+
cmd << "--#{flag}"
|
272
|
+
cmd << commit if commit
|
273
|
+
else
|
274
|
+
cmd << "--#{flag}"
|
275
|
+
if option('ours')
|
276
|
+
cmd << '--ours'
|
277
|
+
elsif option('theirs')
|
278
|
+
cmd << '--theirs'
|
279
|
+
end
|
280
|
+
if (val = option('tree-ish'))
|
281
|
+
cmd << val
|
282
|
+
end
|
283
|
+
append_pathspec files
|
284
|
+
end
|
285
|
+
source
|
286
|
+
end
|
287
|
+
|
288
|
+
def rev(flag, ref: nil, size: nil)
|
289
|
+
opt = if flag == :branch
|
290
|
+
'--abbrev-ref'
|
291
|
+
else
|
292
|
+
(n = size.to_i) > 0 ? "--short=#{[n, 5].max}" : '--verify'
|
293
|
+
end
|
294
|
+
git_session 'rev-parse', opt, ref || 'HEAD'
|
295
|
+
source
|
296
|
+
end
|
297
|
+
|
298
|
+
def refs(flag, grep: nil)
|
299
|
+
git_session 'ls-remote', " --#{flag}"
|
300
|
+
out, banner = source(io: true)
|
301
|
+
print_item banner
|
302
|
+
ret = write_lines(out, grep: grep)
|
303
|
+
list_result(ret, flag.to_s, grep: grep)
|
304
|
+
end
|
305
|
+
|
306
|
+
def files(flag, grep: nil)
|
307
|
+
git_session 'ls-files', " --#{flag}"
|
308
|
+
out, banner = source(io: true)
|
309
|
+
print_item banner
|
310
|
+
ret = write_lines(out, grep: grep)
|
311
|
+
list_result(ret, 'files', grep: grep)
|
312
|
+
end
|
313
|
+
|
314
|
+
def diff(flag, files = [], branch: nil, index: 0)
|
315
|
+
cmd = git_session 'diff'
|
316
|
+
if /^#[0-9a-f]{5,40}\#$/.match?(sha = files.first)
|
317
|
+
files.shift
|
318
|
+
else
|
319
|
+
sha = nil
|
320
|
+
end
|
321
|
+
if (val = option('unified')).to_i > 0
|
322
|
+
cmd << "--unified=#{val}"
|
323
|
+
end
|
324
|
+
append_nocolor
|
325
|
+
case flag
|
326
|
+
when :cached
|
327
|
+
cmd << '--cached'
|
328
|
+
cmd << '--merge-base' if option('merge-base')
|
329
|
+
when :branch
|
330
|
+
cmd << branch
|
331
|
+
else
|
332
|
+
if (val = option('index')) || index > 0
|
333
|
+
cmd << "HEAD~#{val || index}"
|
334
|
+
elsif sha && option('merge-base')
|
335
|
+
cmd << '--merge-base'
|
336
|
+
end
|
337
|
+
end
|
338
|
+
cmd << sha if sha
|
339
|
+
append_pathspec files
|
340
|
+
source
|
341
|
+
end
|
342
|
+
|
343
|
+
def commit(flag, files = [], message: nil, pass: false)
|
344
|
+
message = option('message') if message.nil?
|
345
|
+
amend = flag.to_s.start_with?('amend')
|
346
|
+
if !message && !amend
|
347
|
+
return if pass
|
348
|
+
|
349
|
+
raise ArgumentError, message('commit', 'GIT_MESSAGE="description"', hint: 'missing')
|
350
|
+
end
|
351
|
+
pathspec = if flag == :all || (amend && files.size == 1 && files.first == '*')
|
352
|
+
'--all'
|
353
|
+
else
|
354
|
+
files = source_path(as_a(files))
|
355
|
+
raise ArgumentError, message('commit', 'pathspec', hint: 'missing') if files.empty?
|
356
|
+
|
357
|
+
"-- #{files.join(' ')}"
|
358
|
+
end
|
359
|
+
if !push?
|
360
|
+
source('git branch -vv --list', io: true, banner: false)[0].each do |val|
|
361
|
+
origin = %r{^\* [^\[]+(?<= )\[([\w.-/]+?)/([\w.-]+)\] }.match(val)
|
362
|
+
next unless origin
|
363
|
+
|
364
|
+
@origin = origin[1]
|
365
|
+
@branch = origin[2]
|
366
|
+
break
|
367
|
+
end
|
368
|
+
end
|
369
|
+
raise ArgumentError, message('commit', 'work tree is not usable') unless push?
|
370
|
+
|
371
|
+
cmd = git_session 'commit'
|
372
|
+
cmd << '--dry-run' if option('dry-run')
|
373
|
+
if amend
|
374
|
+
cmd << '--amend'
|
375
|
+
else
|
376
|
+
cmd.delete('--amend')
|
377
|
+
end
|
378
|
+
if message
|
379
|
+
append_message message
|
380
|
+
elsif flag == :'amend-orig' || option('no-edit')
|
381
|
+
cmd << '--no-edit'
|
382
|
+
end
|
383
|
+
a = ['git add --verbose']
|
384
|
+
b = ['git push']
|
385
|
+
if dry_run?
|
386
|
+
a << '--dry-run'
|
387
|
+
b << '--dry-run'
|
388
|
+
end
|
389
|
+
a << pathspec
|
390
|
+
b << '--force' if amend
|
391
|
+
b << @origin << @branch
|
392
|
+
puts if pass
|
393
|
+
source a.join(' ')
|
394
|
+
source
|
395
|
+
source b.join(' ')
|
396
|
+
end
|
397
|
+
|
398
|
+
protected
|
399
|
+
|
400
|
+
def source(cmd = @session, exception: true, banner: true, io: false, sync: true, stdout: false, stderr: false)
|
401
|
+
cmd = close_session(cmd)
|
402
|
+
info cmd
|
403
|
+
begin
|
404
|
+
banner = format_banner(cmd.gsub(trailing_slash(@path), ''), banner: banner, multitask: true)
|
405
|
+
cmd = cmd.sub(/^git\b/, "git --work-tree #{shell_quote(@path)} --git-dir #{shell_quote(vardir)}")
|
406
|
+
if io
|
407
|
+
[IO.popen(cmd), banner]
|
408
|
+
elsif pipe? ? sync : stdout
|
409
|
+
print_item banner
|
410
|
+
puts `#{cmd}`
|
411
|
+
elsif sync || (!exception && !stderr)
|
412
|
+
print_item banner
|
413
|
+
shell(cmd, exception: exception)
|
414
|
+
else
|
415
|
+
require 'open3'
|
416
|
+
if stderr
|
417
|
+
Open3.popen3(cmd) do |_, out, err|
|
418
|
+
ret = write_lines(out, banner: banner, pass: true)
|
419
|
+
if ret == 0
|
420
|
+
write_lines(err, banner: banner)
|
421
|
+
else
|
422
|
+
write_lines(err, loglevel: Logger::DEBUG)
|
423
|
+
end
|
424
|
+
end
|
425
|
+
else
|
426
|
+
Open3.popen2e(cmd) { |_, out| write_lines(out, banner: banner) }
|
427
|
+
end
|
428
|
+
end
|
429
|
+
rescue StandardError => e
|
430
|
+
error e
|
431
|
+
raise if exception
|
432
|
+
|
433
|
+
emphasize e, message('git', cmd)
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
def write_lines(iter, banner: nil, loglevel: nil, grep: nil, sub: nil, pass: false)
|
438
|
+
grep = Regexp.new(grep == '*' ? '.+' : grep) if grep.is_a?(::String)
|
439
|
+
sub = nil if pipe?
|
440
|
+
found = 0
|
441
|
+
lines = []
|
442
|
+
iter.each do |line|
|
443
|
+
next if grep && !grep.match?(line)
|
444
|
+
|
445
|
+
if loglevel
|
446
|
+
log loglevel, line
|
447
|
+
else
|
448
|
+
sub&.each { |h| line = sub_style(line, **h) }
|
449
|
+
if banner
|
450
|
+
lines << line
|
451
|
+
else
|
452
|
+
puts line
|
453
|
+
end
|
454
|
+
end
|
455
|
+
found += 1
|
456
|
+
end
|
457
|
+
print_item banner, lines if banner && (found > 0 || !pass)
|
458
|
+
found
|
459
|
+
end
|
460
|
+
|
461
|
+
def list_result(size, type, action: 'found', grep: nil)
|
462
|
+
return unless verbose?
|
463
|
+
|
464
|
+
if size > 0
|
465
|
+
args = styles[:banner]&.reject { |s| s.to_s.end_with?('!') } || []
|
466
|
+
args << :bold if args.size <= 1
|
467
|
+
puts Project.footer "#{sub_style(size, *args)} #{size == 1 ? type.sub(/s$/, '') : type}"
|
468
|
+
else
|
469
|
+
puts empty_status("No #{type} were #{action}", 'grep', grep)
|
470
|
+
end
|
471
|
+
end
|
472
|
+
|
473
|
+
def source_path(files)
|
474
|
+
files.select { |val| source_path?(val) }.map { |val| shell_quote(base_path(val.strip)) }
|
475
|
+
end
|
476
|
+
|
477
|
+
def source_path?(path)
|
478
|
+
return path.to_s.start_with?("#{@path}#{::File::SEPARATOR}") if Pathname.new(path).absolute?
|
479
|
+
|
480
|
+
!%r{^\.\.[/\\]}.match?(path)
|
481
|
+
end
|
482
|
+
|
483
|
+
private
|
484
|
+
|
485
|
+
def append_pathspec(files)
|
486
|
+
files = source_path(files)
|
487
|
+
@session << "-- #{files.join(' ')}" unless files.empty?
|
488
|
+
end
|
489
|
+
|
490
|
+
def append_message(val)
|
491
|
+
@session << "--message \"#{double_quote(val)}\"" if val
|
492
|
+
end
|
493
|
+
|
494
|
+
def invoked_sync?(action, flag = nil)
|
495
|
+
!flag.nil? || super(action)
|
496
|
+
end
|
497
|
+
|
498
|
+
def option(val)
|
499
|
+
ret = ENV.fetch("GIT_#{val.gsub(/\W/, '_').upcase}", '')
|
500
|
+
ret unless ret.empty? || ret == '0'
|
501
|
+
end
|
502
|
+
|
503
|
+
def git_session(*cmd)
|
504
|
+
session('git', *cmd)
|
505
|
+
end
|
506
|
+
|
507
|
+
def vardir
|
508
|
+
@vardir ||= base_path('.git')
|
509
|
+
end
|
510
|
+
|
511
|
+
def push?
|
512
|
+
!@origin.nil? && !@branch.nil?
|
513
|
+
end
|
514
|
+
|
515
|
+
def dry_run?
|
516
|
+
@session.include?('--dry-run')
|
517
|
+
end
|
518
|
+
end
|
519
|
+
end
|
520
|
+
end
|
521
|
+
end
|