squared 0.5.13 → 0.6.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.
@@ -41,13 +41,13 @@ module Squared
41
41
  end
42
42
  end
43
43
  data.each do |key, val|
44
- if val.is_a?(Hash)
45
- uri = val.fetch(:uri, '')
46
- opts = val.fetch(:options, {})
47
- else
48
- uri = val.is_a?(String) ? val : key.to_s
49
- opts = options
50
- end
44
+ uri = if val.is_a?(Hash)
45
+ opts = val.fetch(:options, {})
46
+ val.fetch(:uri, '')
47
+ else
48
+ opts = options
49
+ val.is_a?(String) ? val : key.to_s
50
+ end
51
51
  unless uri.match?(GIT_PROTO) || Pathname.new(uri).absolute?
52
52
  if uri.start_with?('.')
53
53
  uri = @root + uri
@@ -57,8 +57,7 @@ module Squared
57
57
  next
58
58
  end
59
59
  end
60
- key = task_name key
61
- GIT_REPO[main][key] = [uri.to_s, opts]
60
+ GIT_REPO[main][key = task_name(key)] = [uri.to_s, opts]
62
61
  @kind[key] << Project::Git
63
62
  end
64
63
  if cache == true
@@ -69,6 +68,16 @@ module Squared
69
68
  self
70
69
  end
71
70
 
71
+ def git_repo(name)
72
+ (ret = GIT_REPO[main]) && ret[name]
73
+ end
74
+
75
+ def git_clone?(path, name = nil)
76
+ return false if name && !git_repo(name)
77
+
78
+ !path.exist? || path.empty?
79
+ end
80
+
72
81
  def revbuild(file: nil)
73
82
  @revfile = @home.join(file || "#{@main}.revb")
74
83
  @revdoc = JSON.parse(@revfile.read) if @revfile.exist?
@@ -81,10 +90,6 @@ module Squared
81
90
  self
82
91
  end
83
92
 
84
- def git_repo(name)
85
- (ret = GIT_REPO[main]) && ret[name]
86
- end
87
-
88
93
  def rev_entry(*keys, val: nil, create: true)
89
94
  return unless @revdoc
90
95
  return @revdoc.dig(*keys) unless val
@@ -138,18 +143,6 @@ module Squared
138
143
  ensure
139
144
  @revlock = false
140
145
  end
141
-
142
- def git_clone?(path, name = nil)
143
- return false if name && !git_repo(name)
144
-
145
- !path.exist? || path.empty?
146
- end
147
-
148
- private
149
-
150
- def rev_timenow
151
- Time.now.utc.strftime('%s%L').to_i
152
- end
153
146
  end
154
147
  Application.include Git
155
148
 
@@ -159,12 +152,11 @@ module Squared
159
152
  common: %w[c=q bare glob-pathspecs icase-pathspecs literal-pathspecs no-optional-locks no-pager
160
153
  no-replace-objects noglob-pathspecs paginate attr-source=b config-env=q exec-path=p
161
154
  namespace=p].freeze,
162
- add: %w[A|all e|edit f|force ignore-errors ignore-missing ignore-removal i|interactive no-all
163
- no-ignore-removal n|dry-run p|patch pathspec-file-nul renormalize sparse u|update v|verbose
164
- chmod=b pathspec-from-file=p].freeze,
165
- branch: %w[a|all create-reflog i|ignore-case omit-empty q|quiet r|remotes v|verbose abbrev=i color=b
166
- column=b contains=b format=q merged=b no-contains=b no-merged=b points-at=b u|set-upstream-to=b
167
- sort=q t|track=b].freeze,
155
+ add: %w[A e|edit f|force ignore-errors ignore-missing i|interactive n|dry-run p|patch pathspec-file-nul
156
+ refresh renormalize sparse u|update v|verbose chmod=b pathspec-from-file=p].freeze,
157
+ branch: %w[a|all create-reflog i|ignore-case omit-empty q|quiet r|remotes v|verbose abbrev=i color=b column=b
158
+ contains=b format=q merged=b no-contains=b no-merged=b points-at=b u|set-upstream-to=b sort=q
159
+ t|track=b].freeze,
168
160
  checkout: %w[l d|detach f|force ignore-other-worktrees ignore-skip-worktree-bits m|merge p|patch
169
161
  pathspec-file-nul q|quiet ours theirs conflict=b orphan=b pathspec-from-file=p t|track=b].freeze,
170
162
  diff: {
@@ -174,17 +166,22 @@ module Squared
174
166
  fetch: {
175
167
  base: %w[multiple porcelain progress P|prune-tags refetch stdin u|update-head-ok
176
168
  recurse-submodules-default=b].freeze,
177
- pull: %w[4 6 n t a|append atomic dry-run f|force k|keep negotiate-only prefetch p|prune q|quiet
178
- set-upstream unshallow update-shallow v|verbose deepen=i depth=i j|jobs=i negotiation-tip=q
179
- recurse-submodules=v refmap=q o|server-option=q shallow-exclude=b shallow-since=v
180
- upload-pack=q].freeze
169
+ pull: %w[4 6 n t a|append atomic dry-run f|force k|keep negotiate-only prefetch p|prune q|quiet set-upstream
170
+ unshallow update-shallow v|verbose deepen=i depth=i j|jobs=i negotiation-tip=q recurse-submodules=v
171
+ refmap=q o|server-option=q shallow-exclude=b shallow-since=v upload-pack=q].freeze
181
172
  }.freeze,
182
173
  git: {
183
174
  add: %w[N|intent-to-add refresh].freeze,
184
175
  blame: %w[b c l s t w C=im? L=q M=im? S=p color-by-age color-lines first-parent incremental line-porcelain
185
- p|porcelain root score-debug f|show-name e|show-email n|show-number show-stats abbrev=i
186
- contents=p date=q encoding=b ignore-rev=b ignore-revs-file=p reverse=q].freeze,
176
+ p|porcelain root score-debug f|show-name e|show-email n|show-number show-stats abbrev=i contents=p
177
+ date=q encoding=b ignore-rev=b ignore-revs-file=p reverse=q].freeze,
187
178
  clean: %w[d x X f|force n|dry-run i|interactive q|quiet e|exclude=q].freeze,
179
+ grep: %w[e f=p h H I O=bm r all-match and G|basic-regexp break cached column c|count E|extended-regexp
180
+ l|files-with-matches L|files-without-match F|fixed-strings full-name W|function-context heading
181
+ i|ignore-case v|invert-match n|line-number name-only no-index not z|null o|only-matching or
182
+ P|perl-regexp q|quiet recurse-submodules p|show-function a|text untracked w|word-regexp
183
+ A|after-context=i B|before-context=i color=b C|context=i m|max-count=n max-depth=i
184
+ open-files-in-pager=b threads=n].freeze,
188
185
  mv: %w[k f|force n|dry-run v|verbose].freeze,
189
186
  revert: %w[e S=bm? abort continue n|no-commit quit reference skip cleanup=b gpg-sign=b? m|mainline=i
190
187
  s|signoff strategy=b X|strategy-option=b].freeze,
@@ -204,26 +201,27 @@ module Squared
204
201
  format: %w[t children combined-all-paths dd oneline left-right no-diff-merges parents relative-date
205
202
  show-notes-by-default show-signature date=q diff-merges=b encoding=b expand-tabs=i format=q
206
203
  notes=b pretty=q? show-linear-break=q?].freeze,
207
- diff: %w[p R u z B=bm? C=bm? l=im G=qm I=qm M=bm? O=qm S=qm U=im binary check compact-summary cumulative
204
+ diff: %w[p R u z B=bm? C=bm? l=im G=qm I=qm M=bm? O=qm S=qm binary check compact-summary cumulative
208
205
  find-copies-harder full-index W|function-context w|ignore-all-space ignore-blank-lines
209
206
  ignore-cr-at-eol ignore-space-at-eol b|ignore-space-change D|irreversible-delete graph
210
207
  ita-invisible-in-index minimal name-only name-status no-color-moved-ws no-prefix no-renames numstat
211
208
  patch-with-raw patch-with-stat patience pickaxe-all pickaxe-regex raw shortstat summary a|text
212
209
  abbrev=i? anchored=q break-rewrites=b? color=b color-moved=b color-moved-ws=b color-words=q?
213
210
  diff-algorithm=b diff-filter=e? X|dirstat=b? dirstat-by-file=b? dst-prefix=q find-copies=i?
214
- find-object=b find-renames=b? ignore-matching-lines=q ignore-submodules=b? inter-hunk-context=i
215
- line-prefix=q output=p output-indicator-context=q output-indicator-new=q output-indicator-old=q
216
- relative=p rotate-to=p skip-to=p src-prefix=q stat=b? stat-count=i stat-width=i stat-name-width=i
217
- submodule=b? unified=i word-diff=b? word-diff-regex=q ws-error-highlight=b].freeze
211
+ find-object=b find-renames=b? ignore-matching-lines=q ignore-submodules=b? line-prefix=q output=p
212
+ output-indicator-context=q output-indicator-new=q output-indicator-old=q relative=p rotate-to=p
213
+ skip-to=p src-prefix=q stat=b? stat-count=i stat-width=i stat-name-width=i submodule=b?
214
+ word-diff=b? word-diff-regex=q ws-error-highlight=b].freeze,
215
+ diff_context: %w[U=im inter-hunk-context=i unified=i].freeze
218
216
  }.freeze,
219
217
  ls_files: %w[f t v z debug deduplicate directory eol error-unmatch exclude-standard full-name i|ignored
220
218
  k|killed no-empty-directory recurse-submodules sparse s|stage u|unmerged abbrev=i x|exclude=q
221
219
  X|exclude-from=p exclude-per-directory=p format=q with-tree=q].freeze,
222
220
  ls_remote: %w[exit-code get-url q|quiet symref o|server-option=q sort=q upload-pack=q].freeze,
223
- merge: %w[e n S=bm? allow-unrelated-histories ff-only m=q q|quiet v|verbose cleanup=b F|file=p gpg-sign=b?
224
- into-name=b log=i s|strategy=b X|strategy-option=b].freeze,
225
- pull: %w[e n S=bm? allow-unrelated-histories ff-only cleanup=b gpg-sign=b? log=i r|rebase=v? s|strategy=b
226
- X|strategy-option=b].freeze,
221
+ merge: %w[e n S=bm? allow-unrelated-histories compact-summary ff-only m=q q|quiet v|verbose cleanup=b F|file=p
222
+ gpg-sign=b? into-name=b log=i s|strategy=b X|strategy-option=b].freeze,
223
+ pull: %w[e n S=bm? allow-unrelated-histories compact-summary ff-only cleanup=b gpg-sign=b? log=i r|rebase=v?
224
+ s|strategy=b X|strategy-option=b].freeze,
227
225
  rebase: %w[n C=im S=bm? allow-empty-message apply committer-date-is-author-date edit-todo empty=b
228
226
  f|force-rebase ignore-date ignore-whitespace i|interactive keep-base m|merge no-ff q|quiet quit
229
227
  reset-author-date root show-current-patch signoff v|verbose empty=b x|exec=q gpg-sign=b? onto=b
@@ -242,8 +240,8 @@ module Squared
242
240
  encoding=b expand-tabs=i notes=q show-notes=q?].freeze,
243
241
  stash: {
244
242
  common: %w[q|quiet].freeze,
245
- push: %w[a|all u|include-untracked k|keep-index no-keep-index no-include-untracked pathspec-file-nul
246
- p|patch S|staged m|message=q pathspec-from-file=p].freeze,
243
+ push: %w[a|all u|include-untracked k|keep-index no-keep-index no-include-untracked pathspec-file-nul p|patch
244
+ S|staged m|message=q pathspec-from-file=p].freeze,
247
245
  pop: %w[index].freeze,
248
246
  apply: %w[index].freeze
249
247
  }.freeze,
@@ -261,6 +259,7 @@ module Squared
261
259
  tag: %w[n=im cleanup=b create-reflog i|ignore-case omit-empty color=b? column=b contains=b? format=q merged=b?
262
260
  no-contains=b? no-merged=b? points-at=q sort=q trailer=q].freeze,
263
261
  no: {
262
+ add: %w[all ignore-removal].freeze,
264
263
  blame: %w[progress].freeze,
265
264
  branch: %w[color color-moved column track].freeze,
266
265
  checkout: %w[overwrite-ignore guess overlay progress recurse-submodules track].freeze,
@@ -268,6 +267,7 @@ module Squared
268
267
  base: %w[auto-gc auto-maintenance write-commit-graph write-fetch-head].freeze,
269
268
  pull: %w[all ipv4 ipv6 recurse-submodules show-forced-updates tags].freeze
270
269
  },
270
+ grep: %w[color exclude-standard recursive textconv].freeze,
271
271
  log: {
272
272
  base: %w[decorate mailmap merges use-mailmap].freeze,
273
273
  diff: %w[color color-moved ext-diff indent-heuristic patch relative rename-empty textconv].freeze,
@@ -303,33 +303,6 @@ module Squared
303
303
  class << self
304
304
  include Rake::DSL
305
305
 
306
- def populate(ws, **)
307
- return if ws.series.exclude?(:pull, true) || ws.size == 1
308
-
309
- namespace ws.task_name('git') do |ns|
310
- ws.format_desc(all = ws.task_join(ns.scope.path, 'all'), 'stash|rebase|autostash?,depend?')
311
- task 'all' do |_, args|
312
- args = args.to_a
313
- cmd = if args.include?('stash')
314
- ['stash', 'pull']
315
- elsif args.include?('rebase')
316
- ['rebase']
317
- elsif args.include?('autostash')
318
- ['autostash']
319
- else
320
- ['pull']
321
- end
322
- cmd.map! { |val| ws.task_sync(val) }
323
- cmd << ws.task_sync('depend') if args.include?('depend') && !ws.series.exclude?(:depend, true)
324
- cmd << ws.task_sync('build')
325
- Common::Utils.task_invoke(*cmd, **ws.invokeargs)
326
- end
327
-
328
- ws.series.sync << all
329
- ws.series.multiple << all
330
- end
331
- end
332
-
333
306
  def tasks
334
307
  %i[pull rebase autostash fetch clone stash status branch revbuild].freeze
335
308
  end
@@ -348,7 +321,7 @@ module Squared
348
321
  'diff' => %i[head branch files view between contain].freeze,
349
322
  'fetch' => %i[origin remote all].freeze,
350
323
  'files' => %i[cached modified deleted others].freeze,
351
- 'git' => %i[add blame clean mv revert rm status].freeze,
324
+ 'git' => %i[add blame clean grep mv revert rm status].freeze,
352
325
  'log' => %i[view between contain].freeze,
353
326
  'merge' => %i[commit no-commit send].freeze,
354
327
  'pull' => %i[origin remote all].freeze,
@@ -358,7 +331,7 @@ module Squared
358
331
  'restore' => %i[source staged worktree].freeze,
359
332
  'rev' => %i[commit build output].freeze,
360
333
  'show' => %i[format oneline textconv].freeze,
361
- 'stash' => %i[push pop apply branch drop clear list all].freeze,
334
+ 'stash' => %i[push pop apply branch drop clear list all staged worktree].freeze,
362
335
  'submodule' => %i[status update branch url sync].freeze,
363
336
  'switch' => %i[branch create detach].freeze,
364
337
  'tag' => %i[add sign delete list].freeze
@@ -366,7 +339,7 @@ module Squared
366
339
 
367
340
  def initialize(*, **)
368
341
  super
369
- @submodule = basepath('.gitmodules').exist?
342
+ @submodule = exist?('.gitmodules')
370
343
  initialize_ref Git.ref if gitpath.exist?
371
344
  end
372
345
 
@@ -389,16 +362,16 @@ module Squared
389
362
  if flag == :remote
390
363
  format_desc action, flag, 'remote?,opts*'
391
364
  task flag, [:remote] do |_, args|
392
- if (remote = args.remote)
393
- args = args.extras
394
- else
395
- remote = choice_remote
396
- args = args.to_a
397
- end
365
+ args = if (remote = args.remote)
366
+ args.extras
367
+ else
368
+ remote = choice_remote
369
+ args.to_a
370
+ end
398
371
  __send__(action, flag, args, remote: remote)
399
372
  end
400
373
  else
401
- format_desc(action, flag, 'opts*', after: flag == :all && action == 'pull' ? 'pattern*' : nil)
374
+ format_desc(action, flag, 'opts*', after: ('pattern*' if flag == :all && action == 'pull'))
402
375
  task flag do |_, args|
403
376
  __send__ action, flag, args.to_a
404
377
  end
@@ -435,11 +408,11 @@ module Squared
435
408
  commit(flag, message: args.message)
436
409
  end
437
410
  else
438
- format_desc(action, flag, 'pathspec+', before: flag == :add ? 'opts*' : nil)
411
+ format_desc(action, flag, 'pathspec+', before: ('opts*' if flag == :add))
439
412
  task flag do |_, args|
440
413
  if flag == :fixup
441
- ref, squash, pick = choice_commit(accept: [['Auto squash?', true]], reflog: false,
442
- values: ['Pick [amend|reword]'])
414
+ ref, squash, pick = choice_commit(reflog: false, accept: [accept_b('Auto squash?')],
415
+ values: 'Pick [amend|reword]')
443
416
  pick &&= case pick.downcase
444
417
  when 'a', 'amend'
445
418
  'amend'
@@ -481,7 +454,7 @@ module Squared
481
454
  task flag do |_, args|
482
455
  refs = args.to_a
483
456
  if refs.empty?
484
- refs = choice_refs('Choose a tag', 'tags', multiple: true, accept: 'Delete?', series: true)
457
+ refs = choice_refs('Choose a tag', 'tags', multiple: true, series: true, accept: 'Delete?')
485
458
  remote = choice_remote
486
459
  end
487
460
  tag(flag, refs: refs, remote: remote)
@@ -489,17 +462,21 @@ module Squared
489
462
  when :add, :sign
490
463
  format_desc action, flag, 'name,message?,commit?,remote?'
491
464
  task flag, [:name, :message, :commit, :remote] do |_, args|
492
- if (name = args.name)
493
- message = args.message
494
- commit = commithead args.commit
495
- remote = args.remote
496
- else
497
- commit, name, message = choice_commit(values: [['Enter tag name', true], 'Enter message'],
498
- series: true, reflog: false)
499
- remote = choice_remote
465
+ remote = if (name = args.name)
466
+ message = args.message
467
+ commit = commithead args.commit
468
+ args.remote
469
+ else
470
+ commit, name, message = choice_commit(reflog: false, series: true,
471
+ values: [
472
+ ['Enter tag name', true],
473
+ 'Enter message'
474
+ ])
475
+ choice_remote
476
+ end
477
+ tag(flag, refs: [name], message: message, commit: commit, remote: remote).tap do |ret|
478
+ success?(ret, !remote)
500
479
  end
501
- ret = tag(flag, refs: [name], message: message, commit: commit, remote: remote)
502
- print_success if success?(ret, !remote)
503
480
  end
504
481
  end
505
482
  when 'stash'
@@ -515,9 +492,8 @@ module Squared
515
492
  when 'log', 'diff'
516
493
  case flag
517
494
  when :view, :between, :contain
518
- view = flag == :view
519
- if view && action == 'log'
520
- format_desc action, flag, '(^)commit*|:,opts*,ref?,pathspec*'
495
+ if action == 'log' && flag == :view
496
+ format_desc action, flag, '(^)commit*|:,opts*,pathspec*'
521
497
  task flag do |_, args|
522
498
  args = args.to_a
523
499
  if args.first == ':'
@@ -544,16 +520,17 @@ module Squared
544
520
  format_desc action, flag, 'commit1,commit2,opts*,pathspec*'
545
521
  task flag, [:commit1, :commit2] do |_, args|
546
522
  commit1 = commithead args.commit1
547
- if commit1
548
- commit2 = commithead param_guard(action, flag, args: args, key: :commit2)
549
- args = args.extras
550
- range = [commit1, commit2]
551
- else
552
- range, opts, refs = choice_commit(multiple: view ? true : 2, values: %w[Options Pathspec])
553
- range = range.reverse
554
- args = OptionPartition.strip(opts)
555
- args.concat(refs.shellsplit) if refs
556
- end
523
+ range = if commit1
524
+ commit2 = commithead param_guard(action, flag, args: args, key: :commit2)
525
+ args = args.extras
526
+ [commit1, commit2]
527
+ else
528
+ range, opts, refs = choice_commit(multiple: flag == :view ? true : 2,
529
+ values: %w[Options Pathspec])
530
+ args = OptionPartition.strip(opts)
531
+ args.concat(refs.shellsplit) if refs
532
+ range.reverse
533
+ end
557
534
  __send__(action == 'log' ? :log! : :diff, flag, args, range: range)
558
535
  end
559
536
  end
@@ -566,7 +543,11 @@ module Squared
566
543
  index = choice_commit(multiple: true)
567
544
  else
568
545
  index = []
569
- args.each { |val| index << (commithead(val) || commithash(val) || break) }
546
+ args.each do |val|
547
+ break unless (sha = commithead(val) || commithash(val))
548
+
549
+ index << sha
550
+ end
570
551
  args = args.drop(index.size)
571
552
  end
572
553
  diff(flag, args, index: index)
@@ -578,36 +559,36 @@ module Squared
578
559
  diff(flag, args.extras, branch: branch)
579
560
  end
580
561
  when :files
581
- format_desc action, flag, 'path1,path2'
582
- task flag, [:path1, :path2] do |_, args|
562
+ format_desc action, flag, 'path1,path2,patch?'
563
+ task flag, [:path1, :path2, :patch] do |_, args|
583
564
  path1 = param_guard(action, flag, args: args, key: :path1)
584
565
  path2 = param_guard(action, flag, args: args, key: :path2)
585
- diff(flag, refs: [path1, path2])
566
+ diff(flag, refs: [path1, path2, args.patch])
586
567
  end
587
568
  end
588
569
  when 'checkout'
589
570
  case flag
590
571
  when :branch
591
- format_desc action, flag, 'name,create?=[bB],commit?,detach?=d'
572
+ format_desc action, flag, 'name,create?=[bB],commit?,d/etach?'
592
573
  task flag, [:name, :create, :commit, :detach] do |_, args|
593
574
  if (branch = args.name)
594
575
  branch = param_guard(action, flag, args: args, key: :name)
595
576
  create = args.create
596
- if args.commit == 'd'
597
- detach = 'd'
598
- commit = nil
599
- elsif create == 'd'
600
- create = nil
601
- commit = nil
602
- detach = 'd'
603
- elsif create && create.size > 1
604
- commit = commithead create
605
- create = nil
606
- detach = args.commit
607
- else
608
- detach = args.detach
609
- commit = commithead args.commit
610
- end
577
+ detach = if args.commit == 'd'
578
+ commit = nil
579
+ 'd'
580
+ elsif create == 'd'
581
+ create = nil
582
+ commit = nil
583
+ 'd'
584
+ elsif create && create.size > 1
585
+ commit = commithead create
586
+ create = nil
587
+ args.commit
588
+ else
589
+ commit = commithead args.commit
590
+ args.detach
591
+ end
611
592
  param_guard(action, flag, args: { create: create }, key: :create, pat: /\A[Bb]\z/) if create
612
593
  else
613
594
  branch = choice_refs 'Choose a branch to switch'
@@ -620,7 +601,7 @@ module Squared
620
601
  if (origin = args.origin)
621
602
  branch = args.name
622
603
  else
623
- origin, branch = choice_refs('Choose a remote', 'remotes', values: ['Enter branch name'])
604
+ origin, branch = choice_refs('Choose a remote', 'remotes', values: 'Enter branch name')
624
605
  end
625
606
  checkout(flag, branch: branch, origin: origin)
626
607
  end
@@ -631,7 +612,7 @@ module Squared
631
612
  args = if commit
632
613
  args.extras
633
614
  else
634
- commit, opts = choice_commit(values: ['Options'])
615
+ commit, opts = choice_commit(values: 'Options')
635
616
  OptionPartition.strip(opts)
636
617
  end
637
618
  checkout(flag, args, commit: commit)
@@ -641,7 +622,7 @@ module Squared
641
622
  task flag, [:commit] do |_, args|
642
623
  commit = commithead args.commit
643
624
  unless commit
644
- commit, merge = choice_commit(values: ['Merge? [y/N]'])
625
+ commit, merge = choice_commit(values: 'Merge? [y/N]')
645
626
  merge = merge&.upcase == 'Y'
646
627
  end
647
628
  checkout(flag, commit: commit, merge: merge)
@@ -655,11 +636,13 @@ module Squared
655
636
  when 'branch'
656
637
  case flag
657
638
  when :create
658
- format_desc action, flag, 'name,ref?=HEAD|:'
639
+ format_desc action, flag, 'name,ref?|:'
659
640
  task flag, [:name, :ref] do |_, args|
660
641
  target = param_guard(action, flag, args: args, key: :name)
661
642
  ref = commithead args.ref
662
- ref, remote = choice_refs('Choose a remote', 'remotes', accept: [['Push?', true]]) if ref == ':'
643
+ if ref == ':'
644
+ ref, remote = choice_refs('Choose a remote', 'remotes', accept: [accept_b('Push?')])
645
+ end
663
646
  branch(flag, target: target, ref: ref, remote: remote)
664
647
  end
665
648
  when :track
@@ -669,8 +652,8 @@ module Squared
669
652
  target = args.name
670
653
  remote = true if ref.delete_prefix!('~')
671
654
  else
672
- ref, remote, target = choice_refs('Choose a remote', 'remotes', accept: [['Push?', true]],
673
- values: ['Enter branch name'])
655
+ ref, remote, target = choice_refs('Choose a remote', 'remotes', accept: [accept_b('Push?')],
656
+ values: 'Enter branch name')
674
657
  end
675
658
  branch(flag, target: target, ref: ref, remote: remote)
676
659
  end
@@ -680,7 +663,7 @@ module Squared
680
663
  refs = args.to_a
681
664
  if refs.empty? || (r = refs.last == ':')
682
665
  accept = ['Delete?']
683
- accept << ['Force?', true] unless r
666
+ accept << accept_b('Force?') unless r
684
667
  remote = choice_refs('Choose a branch', r ? 'remotes' : 'heads', multiple: true,
685
668
  accept: accept)
686
669
  if r
@@ -718,15 +701,15 @@ module Squared
718
701
  when 'switch'
719
702
  case flag
720
703
  when :create
721
- format_desc action, flag, '(^)name,ref?=HEAD|:'
704
+ format_desc action, flag, '(^)name,ref?|:'
722
705
  task flag, [:name, :commit] do |_, args|
723
706
  branch = param_guard(action, flag, args: args, key: :name)
724
707
  commit = commithead args.commit
725
- commit, track = choice_commit(values: ['Track? [Y/n]'], force: false) if commit == ':'
708
+ commit, track = choice_commit(force: false, values: 'Track? [Y/n]') if commit == ':'
726
709
  switch(flag, branch: branch, commit: commit, track: track)
727
710
  end
728
711
  when :detach
729
- format_desc action, flag, 'ref?=HEAD'
712
+ format_desc action, flag, 'ref?'
730
713
  task flag, [:commit] do |_, args|
731
714
  commit = commithead(args.commit) || choice_commit(force: false)
732
715
  switch(flag, commit: commit)
@@ -734,12 +717,12 @@ module Squared
734
717
  when :branch
735
718
  format_desc action, flag, 'name|:,opts*'
736
719
  task flag, [:name] do |_, args|
737
- if (branch = args.name)
738
- args = args.extras
739
- branch = nil if branch == ':'
740
- else
741
- args = []
742
- end
720
+ args = if (branch = args.name)
721
+ branch = nil if branch == ':'
722
+ args.extras
723
+ else
724
+ []
725
+ end
743
726
  switch(flag, args, branch: branch || choice_refs('Choose a branch'))
744
727
  end
745
728
  end
@@ -749,26 +732,26 @@ module Squared
749
732
  format_desc action, flag, 'ref|:,opts*'
750
733
  task flag, [:commit] do |_, args|
751
734
  commit = commithead args.commit
752
- if commit && commit != ':'
753
- args = args.extras
754
- else
755
- commit, mode = choice_commit(values: ['Mode [mixed|soft|hard|N]'])
756
- args = args.extras.concat(case mode&.downcase
735
+ args = if commit && commit != ':'
736
+ args.extras
737
+ else
738
+ commit, mode = choice_commit(values: ['Mode [mixed|soft|hard|N]'])
739
+ args.extras.concat(case mode&.downcase
757
740
  when 'h', 'hard' then ['hard']
758
741
  when 's', 'soft' then ['soft']
759
- when 'n', 'N' then ['mixed', 'N']
742
+ when 'n', 'N' then %w[mixed N]
760
743
  else ['mixed']
761
744
  end)
762
- end
763
- print_success if success?(reset(flag, args, commit: commit))
745
+ end
746
+ success?(reset(flag, args, commit: commit))
764
747
  end
765
748
  when :index, :undo
766
- format_desc(action, flag, flag == :index ? 'opts*,pathspec*' : nil)
749
+ format_desc(action, flag, ('opts*,pathspec*' if flag == :index))
767
750
  task flag do |_, args|
768
751
  reset(flag, flag == :index ? args.to_a : [])
769
752
  end
770
753
  when :mode
771
- format_desc action, flag, 'mode,ref?=HEAD|:'
754
+ format_desc action, flag, 'mode,ref?|:'
772
755
  task flag, [:mode, :ref] do |_, args|
773
756
  mode = param_guard(action, flag, args: args, key: :mode)
774
757
  ref = commithead args.ref
@@ -776,7 +759,7 @@ module Squared
776
759
  reset(flag, mode: mode, ref: ref)
777
760
  end
778
761
  when :patch
779
- format_desc action, flag, 'ref?=HEAD|:,pathspec*'
762
+ format_desc action, flag, 'ref?|:,pathspec*'
780
763
  task flag, [:ref] do |_, args|
781
764
  ref = commithead args.ref
782
765
  ref = choice_commit(reflog: false) unless ref && ref != ':'
@@ -805,18 +788,18 @@ module Squared
805
788
  when 'rebase', 'merge'
806
789
  case flag
807
790
  when :branch
808
- format_desc action, flag, 'upstream,branch?=HEAD,opts*'
791
+ format_desc action, flag, 'upstream,branch?,opts*'
809
792
  task flag, [:upstream] do |_, args|
810
793
  args = if (upstream = args.upstream)
811
794
  args.extras
812
795
  else
813
- upstream, opts = choice_refs('Choose upstream branch', values: ['Options'])
796
+ upstream, opts = choice_refs('Choose upstream branch', values: 'Options')
814
797
  OptionPartition.strip(opts)
815
798
  end
816
799
  rebase(flag, args, upstream: upstream)
817
800
  end
818
801
  when :onto
819
- format_desc action, flag, 'ref,upstream,branch?=HEAD'
802
+ format_desc action, flag, 'ref,upstream,branch?'
820
803
  task flag, [:commit, :upstream, :branch] do |_, args|
821
804
  commit = commithead args.commit
822
805
  args = if commit
@@ -825,7 +808,7 @@ module Squared
825
808
  []
826
809
  else
827
810
  commit = choice_refs 'Choose "onto" branch'
828
- target, opts = choice_commit(reflog: false, multiple: 2, values: ['Options'])
811
+ target, opts = choice_commit(reflog: false, multiple: 2, values: 'Options')
829
812
  branch, upstream = target
830
813
  OptionPartition.strip(opts)
831
814
  end
@@ -837,7 +820,7 @@ module Squared
837
820
  args = args.to_a
838
821
  if args.empty?
839
822
  accept = "Merge with #{`#{git_output('branch --show-current')}`.chomp}?"
840
- branch, opts = choice_refs('Choose a branch', values: ['Options'], accept: accept)
823
+ branch, opts = choice_refs('Choose a branch', values: 'Options', accept: accept)
841
824
  args = OptionPartition.strip(opts)
842
825
  end
843
826
  merge(flag, args, branch: branch)
@@ -853,7 +836,7 @@ module Squared
853
836
  when 'rev'
854
837
  case flag
855
838
  when :commit
856
- format_desc action, flag, 'ref?=HEAD,size?'
839
+ format_desc action, flag, 'ref?,size?'
857
840
  task flag, [:ref, :size] do |_, args|
858
841
  ref = commithead args.ref
859
842
  size = args.size
@@ -881,7 +864,7 @@ module Squared
881
864
  ls_remote(flag, args.extras, remote: args.remote)
882
865
  end
883
866
  else
884
- format_desc(action, flag, 'opts*,pattern*', after: action == 'files' ? 'pathspec*' : nil)
867
+ format_desc(action, flag, 'opts*,pattern*', after: ('pathspec*' if action == 'files'))
885
868
  task flag do |_, args|
886
869
  __send__(action == 'refs' ? :ls_remote : :ls_files, flag, args.to_a)
887
870
  end
@@ -922,16 +905,22 @@ module Squared
922
905
  end
923
906
  when 'git'
924
907
  before = case flag
925
- when :blame then 'file'
926
- when :mv then 'source+,destination'
927
- when :revert then 'commit+'
908
+ when :blame
909
+ 'file'
910
+ when :mv
911
+ 'source+,destination'
912
+ when :revert
913
+ 'commit+'
928
914
  end
929
- format_desc(action, flag, 'opts*', before: before, after: case flag
930
- when :add
931
- 'pathspec*,pattern*'
932
- when :clean, :rm, :status
933
- 'pathspec*'
934
- end)
915
+ after = case flag
916
+ when :add
917
+ 'pathspec*,pattern*'
918
+ when :grep
919
+ 'tree*,pathspec*'
920
+ when :clean, :rm, :status
921
+ 'pathspec*'
922
+ end
923
+ format_desc(action, flag, 'opts*', before: before, after: after)
935
924
  task flag do |_, args|
936
925
  __send__(flag == :status ? :status : :git, flag, args.to_a)
937
926
  end
@@ -996,10 +985,10 @@ module Squared
996
985
  cur ||= line.delete_prefix!('* ')
997
986
  heads << line if matchany?(line, reg)
998
987
  end
999
- raise_error('head not found', hint: 'for-each-ref') unless cur
988
+ raise_error 'head not found', hint: 'for-each-ref' unless cur
1000
989
  opts << 'ff-only' if opts.empty? && !option('ff-only', equals: '0')
1001
- (heads.dup << cur).each_with_index do |branch, index|
1002
- next unless (index < heads.size && cur != branch) || index == heads.size
990
+ (heads.dup << cur).each_with_index do |branch, i|
991
+ next unless (i < heads.size && cur != branch) || i == heads.size
1003
992
 
1004
993
  git_spawn 'switch --quiet', force && '--force', shell_quote(branch)
1005
994
  pull(nil, opts, sync: false, hint: branch) if heads.include?(branch)
@@ -1010,11 +999,11 @@ module Squared
1010
999
  end
1011
1000
  end
1012
1001
  append_pull(opts, OPT_GIT[:pull] + OPT_GIT[:fetch][:pull],
1013
- no: OPT_GIT[:no][:pull] + OPT_GIT[:no][:fetch][:pull], remote: remote, flag: flag, from: :pull)
1002
+ flag: flag, from: :pull, remote: remote, no: OPT_GIT[:no][:pull] + OPT_GIT[:no][:fetch][:pull])
1014
1003
  source(sync: sync, sub: if stdout?
1015
1004
  [
1016
- { pat: /^(.+)(\|\s+\d+\s+)([^-]*)(-+)(.*)$/, styles: color(:red), index: 4 },
1017
- { pat: /^(.+)(\|\s+\d+\s+)(\++)(.*)$/, styles: color(:green), index: 3 }
1005
+ opt_style(color(:red), /^(.+)(\|\s+\d+\s+)([^-]*)(-+)(.*)$/, 4),
1006
+ opt_style(color(:green), /^(.+)(\|\s+\d+\s+)(\++)(.*)$/, 3)
1018
1007
  ]
1019
1008
  end, hint: hint, **threadargs)
1020
1009
  end
@@ -1030,7 +1019,7 @@ module Squared
1030
1019
 
1031
1020
  op = OptionPartition.new(opts, OPT_GIT[:rebase], cmd, project: self, no: OPT_GIT[:no][:rebase])
1032
1021
  op << upstream
1033
- append_head op.shift
1022
+ append_head op.shift&.delete_prefix(':')
1034
1023
  op.clear(pass: false)
1035
1024
  when :onto
1036
1025
  return unless upstream
@@ -1042,7 +1031,7 @@ module Squared
1042
1031
  else
1043
1032
  unless gitpath('REBASE_HEAD').exist?
1044
1033
  puts log_message(Logger::INFO, name, 'no rebase in progress', hint: command) if stdout?
1045
- return
1034
+ exit 1
1046
1035
  end
1047
1036
  return unless VAL_GIT[:rebase][:send].include?(command)
1048
1037
 
@@ -1058,8 +1047,8 @@ module Squared
1058
1047
  def fetch(flag = nil, opts = [], sync: invoked_sync?('fetch', flag), remote: nil)
1059
1048
  opts = git_session('fetch', opts: opts).last
1060
1049
  opts << 'all' if flag == :all || option('all')
1061
- append_pull(opts, collect_hash(OPT_GIT[:fetch]), no: collect_hash(OPT_GIT[:no][:fetch]),
1062
- remote: remote, flag: flag, from: :fetch)
1050
+ append_pull(opts, collect_hash(OPT_GIT[:fetch]), flag: flag, from: :fetch, remote: remote,
1051
+ no: collect_hash(OPT_GIT[:no][:fetch]))
1063
1052
  source(sync: sync, **threadargs)
1064
1053
  end
1065
1054
 
@@ -1097,17 +1086,28 @@ module Squared
1097
1086
 
1098
1087
  def stash(flag = nil, opts = [], sync: invoked_sync?('stash', flag))
1099
1088
  if flag
1100
- if flag == :all
1089
+ case flag
1090
+ when :all
1101
1091
  opts << 'include-untracked'
1102
1092
  flag = :push
1093
+ when :staged
1094
+ opts << 'staged'
1095
+ flag = :push
1096
+ when :worktree
1097
+ opts << 'keep-index'
1098
+ flag = :push
1099
+ end
1100
+ unless (file = gitpath('logs/refs/stash')).exist? || flag == :push
1101
+ puts log_message(Logger::INFO, name, 'no stashes were found', hint: flag) if stdout?
1102
+ exit 1
1103
1103
  end
1104
1104
  cmd, opts = git_session('stash', flag, opts: opts)
1105
1105
  list = OPT_GIT[:stash][:common] + OPT_GIT[:stash].fetch(flag, [])
1106
1106
  if flag == :list
1107
- list += collect_hash OPT_GIT[:log]
1107
+ list.concat(collect_hash(OPT_GIT[:log]))
1108
1108
  no = collect_hash OPT_GIT[:no][:log]
1109
1109
  end
1110
- op = OptionPartition.new(opts, list, cmd, project: self, no: no, first: flag == :push ? matchpathspec : nil)
1110
+ op = OptionPartition.new(opts, list, cmd, project: self, no: no, first: (matchpathspec if flag == :push))
1111
1111
  case flag
1112
1112
  when :push
1113
1113
  op.append?('message', readline('Enter message', force: true), force: true) if op.remove(':')
@@ -1118,24 +1118,26 @@ module Squared
1118
1118
  if op.empty?
1119
1119
  values = [['Branch name', true]]
1120
1120
  else
1121
- op << op.shift
1121
+ op.add_first(prefix: ':')
1122
1122
  end
1123
1123
  end
1124
1124
  out = choice_index('Choose a stash', git_spawn('stash list', stdout: false),
1125
- values: values, column: /^[^@]+@\{(\d+)\}/, force: true)
1125
+ values: values, column: /^[^@]+@\{(\d+)}/, force: true)
1126
1126
  if values
1127
1127
  op.merge(out.reverse)
1128
1128
  else
1129
1129
  op << out
1130
1130
  end
1131
1131
  elsif !op.empty?
1132
- op << op.shift
1132
+ op.add_first(prefix: ':')
1133
1133
  elsif flag == :branch
1134
- raise_error 'no branch name'
1134
+ raise_error ArgumentError, 'no branch name'
1135
1135
  end
1136
1136
  op.clear
1137
1137
  when :clear
1138
- source(stdout: true) if confirm("Remove #{sub_style('all', styles: theme[:active])} stash entries?", 'N')
1138
+ n = sub_style(file.read.lines.size, styles: theme[:inline])
1139
+ s = sub_style(name, styles: theme[:active])
1140
+ source(stdout: true) if confirm("Remove #{n} stash entries from #{s}?", 'N')
1139
1141
  return
1140
1142
  when :list
1141
1143
  op.clear
@@ -1179,13 +1181,13 @@ module Squared
1179
1181
  g = color(:green)
1180
1182
  sub = if session_arg?('short')
1181
1183
  [
1182
- { pat: /^(.)([A-Z?!])(.+)$/, styles: r, index: 2 },
1183
- { pat: /^([A-Z?!])(.+)$/, styles: g },
1184
- { pat: /^(\?\?)(.+)$/, styles: r },
1185
- { pat: /^(## )((?~\.{3}))(\.{3})(.+)$/, styles: [nil, g, nil, r], index: -1 }
1184
+ opt_style(r, /^(.)([A-Z?!])(.+)$/, 2),
1185
+ opt_style(g, /^([A-Z?!])(.+)$/),
1186
+ opt_style(r, /^(\?\?)(.+)$/),
1187
+ opt_style([nil, g, nil, r], /^(## )((?~\.{3}))(\.{3})(.+)$/, -1)
1186
1188
  ]
1187
1189
  else
1188
- [pat: /^(\t+)([a-z]+: +.+)$/, styles: r, index: 2]
1190
+ opt_style(r, /^(\t+)([a-z]+: +.+)$/, 2)
1189
1191
  end
1190
1192
  end
1191
1193
  out, banner, from = source(io: true)
@@ -1219,20 +1221,18 @@ module Squared
1219
1221
  op.clear(append: true)
1220
1222
  args = op.to_a
1221
1223
  else
1222
- args = []
1223
- option('untracked-files', prefix: 'git') { |val| args << basic_option('untracked-files', val) }
1224
- option('ignore-submodules', prefix: 'git') { |val| args << basic_option('ignore-submodules', val) }
1225
- option('ignored', prefix: 'git') { |val| args << basic_option('ignored', val) }
1224
+ args = [
1225
+ option('untracked-files', prefix: 'git') { |val| basic_option('untracked-files', val) },
1226
+ option('ignore-submodules', prefix: 'git') { |val| basic_option('ignore-submodules', val) },
1227
+ option('ignored', prefix: 'git') { |val| basic_option('ignored', val) }
1228
+ ].compact
1226
1229
  end
1227
1230
  if (cur = workspace.rev_entry(name)) && cur['revision'] == sha && !env('REVBUILD_FORCE')
1228
1231
  files = status_digest(*args, **kwargs)
1229
1232
  if cur['files'].size == files.size && cur['files'].find { |key, val| files[key] != val }.nil?
1233
+ workspace.rev_timeutc(name, 'build') unless (since = workspace.rev_timesince(name, 'build'))
1230
1234
  if stdout?
1231
- if (since = workspace.rev_timesince(name, 'build'))
1232
- puts log_message(Logger::INFO, name, 'no changes', subject: 'revbuild', hint: "#{since} ago")
1233
- else
1234
- workspace.rev_timeutc(name, 'build')
1235
- end
1235
+ puts log_message(Logger::INFO, name, 'no changes', subject: 'revbuild', hint: since && "#{since} ago")
1236
1236
  end
1237
1237
  return
1238
1238
  end
@@ -1251,9 +1251,8 @@ module Squared
1251
1251
  cmd, opts = git_session('reset', opts: opts)
1252
1252
  case flag
1253
1253
  when :commit, :index
1254
- op = OptionPartition.new(opts, OPT_GIT[:reset] + VAL_GIT[:reset], cmd,
1255
- project: self, no: OPT_GIT[:no][:reset],
1256
- first: flag == :index ? matchpathspec : nil)
1254
+ op = OptionPartition.new(opts, OPT_GIT[:reset] + VAL_GIT[:reset] + OPT_GIT[:log][:diff_context], cmd,
1255
+ project: self, no: OPT_GIT[:no][:reset], first: (matchpathspec if flag == :index))
1257
1256
  if flag == :commit
1258
1257
  op.append(commit)
1259
1258
  .clear(pass: false)
@@ -1284,7 +1283,7 @@ module Squared
1284
1283
 
1285
1284
  def checkout(flag, opts = [], branch: nil, origin: nil, create: nil, commit: nil, detach: nil, merge: false)
1286
1285
  cmd, opts = git_session('checkout', opts: opts)
1287
- append_option 'force', 'f', 'merge'
1286
+ append_option 'f', 'force', 'merge'
1288
1287
  case flag
1289
1288
  when :branch
1290
1289
  cmd << '--detach' if detach == 'd' || option('detach')
@@ -1297,13 +1296,13 @@ module Squared
1297
1296
  cmd << '-m' if merge
1298
1297
  cmd << '--detach' << commit
1299
1298
  else
1300
- op = OptionPartition.new(opts, OPT_GIT[:checkout], cmd, project: self, no: OPT_GIT[:no][:checkout],
1301
- first: flag == :path ? matchpathspec : nil)
1299
+ list = OPT_GIT[:checkout] + OPT_GIT[:log][:diff_context]
1300
+ op = OptionPartition.new(opts, list, cmd, project: self, no: OPT_GIT[:no][:checkout],
1301
+ first: (matchpathspec if flag == :path))
1302
1302
  if flag == :path
1303
1303
  append_head
1304
1304
  append_pathspec(op.extras, pass: false)
1305
- print_success if success?(source)
1306
- return
1305
+ return success?(source)
1307
1306
  end
1308
1307
  op.append(commit)
1309
1308
  .clear(pass: false)
@@ -1320,7 +1319,7 @@ module Squared
1320
1319
  elsif !session_arg?('s', 'sign', 'u', 'local-user')
1321
1320
  cmd << '--annotate'
1322
1321
  end
1323
- cmd << '--force' if option('force', 'f')
1322
+ cmd << '--force' if option('f', 'force')
1324
1323
  if !commit && message && (sha = commithash(message))
1325
1324
  commit = sha
1326
1325
  message = nil
@@ -1339,9 +1338,12 @@ module Squared
1339
1338
  list_result(ret, 'tags', from: from, grep: op.extras)
1340
1339
  return
1341
1340
  end
1342
- remote ||= option 'remote'
1343
- source
1344
- git_spawn('push', flag == :delete ? '-d' : nil, remote, *refs.map { |val| shell_quote(val) }) if remote
1341
+ remote ||= option('remote')
1342
+ source.tap do |ret|
1343
+ next unless ret && remote
1344
+
1345
+ git_spawn('push', ('-d' if flag == :delete), remote, *refs.map! { |val| shell_quote(val) })
1346
+ end
1345
1347
  end
1346
1348
 
1347
1349
  def log!(flag, opts = [], range: [], index: [])
@@ -1362,9 +1364,11 @@ module Squared
1362
1364
 
1363
1365
  def diff(flag, opts = [], refs: [], branch: nil, range: [], index: [])
1364
1366
  cmd, opts = git_session('diff', opts: opts)
1365
- op = OptionPartition.new(opts, collect_hash(OPT_GIT[:diff]) + OPT_GIT[:log][:diff], cmd,
1367
+ op = OptionPartition.new(opts,
1368
+ collect_hash(OPT_GIT[:diff]) + OPT_GIT[:log][:diff] + OPT_GIT[:log][:diff_context],
1369
+ cmd,
1366
1370
  project: self, no: OPT_GIT[:no][:log][:diff],
1367
- first: flag == :files ? nil : matchpathspec)
1371
+ first: (matchpathspec unless flag == :files))
1368
1372
  case flag
1369
1373
  when :files, :view, :between, :contain
1370
1374
  op.delete('--cached')
@@ -1372,7 +1376,16 @@ module Squared
1372
1376
  append_nocolor
1373
1377
  if flag == :files
1374
1378
  op << '--no-index'
1379
+ patch = refs.pop
1375
1380
  append_pathspec(refs, parent: true)
1381
+ if patch
1382
+ patch = basepath patch
1383
+ exit 1 if patch.exist? && !confirm_basic('Overwrite?', patch)
1384
+ op << '>' << shell_quote(patch)
1385
+ source(banner: false)
1386
+ puts patch.read if patch.exist? && (stdin? || verbose?)
1387
+ return
1388
+ end
1376
1389
  else
1377
1390
  op << '--merge-base' if option('merge-base')
1378
1391
  case flag
@@ -1385,7 +1398,7 @@ module Squared
1385
1398
  op.add_quote(branch) if branch
1386
1399
  if !index.empty?
1387
1400
  if op.arg?('cached')
1388
- raise_error("one commit only: #{index.join(', ')}", hint: 'cached') if index.size > 1
1401
+ raise_error "single commit: #{index.join(', ')}", hint: 'cached' unless index.size == 1
1389
1402
  op << index.first
1390
1403
  else
1391
1404
  op.merge(index)
@@ -1401,7 +1414,7 @@ module Squared
1401
1414
 
1402
1415
  def commit(flag, opts = [], refs: [], ref: nil, squash: nil, pick: nil, message: nil, pass: false)
1403
1416
  fixup = flag == :fixup
1404
- amend = !fixup && flag.to_s.start_with?('amend')
1417
+ amend = flag.match?(/^amend/) && !fixup
1405
1418
  unless flag == :add || pick == 'reword'
1406
1419
  pathspec = if flag == :all || ((fixup || amend) && refs.size == 1 && refs.first == '*')
1407
1420
  '--all'
@@ -1412,12 +1425,12 @@ module Squared
1412
1425
  end
1413
1426
  end
1414
1427
  if fixup
1415
- source git_session('commit', basic_option('fixup', "#{pick ? "#{pick}:" : ''}#{ref}"), pathspec)
1416
- source git_output('rebase --autosquash', squash) if squash.is_a?(String)
1417
- return
1428
+ ret = source(git_session('commit', basic_option('fixup', pick ? "#{pick}:#{ref}" : ref), pathspec))
1429
+ source git_output('rebase --autosquash', squash) if ret && squash.is_a?(String)
1430
+ return ret
1418
1431
  end
1419
1432
  message ||= messageopt
1420
- if !message && !amend
1433
+ unless message || amend
1421
1434
  return if pass
1422
1435
 
1423
1436
  message = readline('Enter message', force: true)
@@ -1426,7 +1439,8 @@ module Squared
1426
1439
  origin = nil
1427
1440
  upstream = nil
1428
1441
  cmd, opts = git_session('add', opts: opts)
1429
- op = OptionPartition.new(opts, OPT_GIT[:add], cmd, project: self, first: matchpathspec)
1442
+ op = OptionPartition.new(opts, OPT_GIT[:add] + OPT_GIT[:log][:diff_context], cmd,
1443
+ project: self, no: OPT_GIT[:no][:add], first: matchpathspec)
1430
1444
  op << '--verbose' if verbose
1431
1445
  format = '%(if)%(HEAD)%(then)%(refname:short)...%(upstream:short)...%(upstream:track)%(end)'
1432
1446
  git_spawn 'fetch --no-tags --quiet'
@@ -1435,10 +1449,9 @@ module Squared
1435
1449
 
1436
1450
  branch, origin, hint = line.split('...')
1437
1451
  if hint && !hint.match?(/^\[(\D+0,\D+0)\]$/)
1438
- raise_error('work tree is not usable', hint: hint[1..-2])
1439
- elsif !origin || origin.empty?
1452
+ raise_error 'work tree is not usable', hint: hint[1..-2]
1453
+ elsif (!origin || origin.empty?) && !dryrun?
1440
1454
  return nil if pass
1441
- break if dryrun?
1442
1455
 
1443
1456
  unless (origin = option('upstream', prefix: 'git', ignore: false))
1444
1457
  if (origin = choice_refs('Choose an upstream', 'remotes', attempts: 1, force: false))
@@ -1447,7 +1460,7 @@ module Squared
1447
1460
  end
1448
1461
  origin = readline('Enter an upstream', force: true)
1449
1462
  end
1450
- raise_error('missing remote name', hint: origin) unless origin.include?('/')
1463
+ raise_error ArgumentError, 'missing remote name', hint: origin unless origin.include?('/')
1451
1464
  upstream = true
1452
1465
  end
1453
1466
  break
@@ -1482,10 +1495,13 @@ module Squared
1482
1495
  end
1483
1496
  source co
1484
1497
  source pu
1485
- elsif banner?
1486
- puts 'Nothing to commit'
1487
- elsif stdout?
1488
- puts log_message(Logger::INFO, name, 'nothing to commit', hint: flag)
1498
+ else
1499
+ if banner?
1500
+ puts 'Nothing to commit'
1501
+ elsif stdout?
1502
+ puts log_message(Logger::INFO, name, 'nothing to commit', hint: flag)
1503
+ end
1504
+ exit 1
1489
1505
  end
1490
1506
  end
1491
1507
 
@@ -1500,20 +1516,20 @@ module Squared
1500
1516
  op << branch
1501
1517
  op.clear(pass: false)
1502
1518
  else
1503
- raise_error 'no branch/commit' if op.empty?
1519
+ raise_error ArgumentError, 'no branch/commit' if op.empty?
1504
1520
  append_commit(*op.extras)
1505
1521
  end
1506
1522
  else
1507
1523
  unless gitpath('MERGE_HEAD').exist?
1508
1524
  puts log_message(Logger::INFO, name, 'no merge in progress', hint: command) if stdout?
1509
- return
1525
+ exit 1
1510
1526
  end
1511
1527
  return unless VAL_GIT[:merge][:send].include?(command)
1512
1528
 
1513
1529
  cmd << "--#{command}"
1514
1530
  display = command == 'abort'
1515
1531
  end
1516
- print_success if success?(source, display)
1532
+ success?(source, display)
1517
1533
  end
1518
1534
 
1519
1535
  def branch(flag = nil, opts = [], refs: [], ref: nil, target: nil, remote: nil)
@@ -1531,11 +1547,11 @@ module Squared
1531
1547
  '--track'
1532
1548
  end
1533
1549
  end
1534
- cmd << '--force' if option('force', 'f')
1550
+ cmd << '--force' if option('f', 'force')
1535
1551
  cmd << shell_quote(target)
1536
1552
  cmd << shell_quote(ref) if ref
1537
1553
  when :track
1538
- raise_error('invalid upstream', hint: ref) unless ref.include?('/')
1554
+ raise_error 'invalid upstream', hint: ref unless ref.include?('/')
1539
1555
  if ref.delete_prefix!('^')
1540
1556
  cmd << '--unset-upstream' << shell_quote(ref)
1541
1557
  remote = false
@@ -1546,7 +1562,7 @@ module Squared
1546
1562
  end
1547
1563
  when :delete
1548
1564
  remote&.each do |val|
1549
- source git_output('push --delete', *val.split('/', 2).map { |s| shell_quote(s) })
1565
+ source git_output('push --delete', *val.split('/', 2).map! { |s| shell_quote(s) })
1550
1566
  end
1551
1567
  force, list = refs.partition { |val| val.start_with?(/[~^]/) }
1552
1568
  force.each do |val|
@@ -1560,13 +1576,13 @@ module Squared
1560
1576
  remote = nil
1561
1577
  when :move, :copy
1562
1578
  s = +"-#{flag.to_s[0]}"
1563
- s.upcase! if option('force', 'f')
1579
+ s.upcase! if option('f', 'force')
1564
1580
  cmd << s
1565
1581
  refs.compact.each { |val| cmd << shell_quote(val) }
1566
1582
  stdout = true
1567
1583
  when :current
1568
1584
  cmd << '--show-current'
1569
- source(banner: verbosetype > 1, stdout: true)
1585
+ source(banner: verbose?, stdout: true)
1570
1586
  return
1571
1587
  when :list
1572
1588
  op = OptionPartition.new(opts, OPT_GIT[:branch], cmd << '--list',
@@ -1575,8 +1591,8 @@ module Squared
1575
1591
  out, banner, from = source(io: true)
1576
1592
  print_item banner
1577
1593
  ret = write_lines(out, sub: [
1578
- { pat: /^(\*\s+)(\S+)(.*)$/, styles: color(:green), index: 2 },
1579
- { pat: %r{^(\s*)(remotes/\S+)(.*)$}, styles: color(:red), index: 2 }
1594
+ opt_style(color(:green), /^(\*\s+)(\S+)(.*)$/, 2),
1595
+ opt_style(color(:red), %r{^(\s*)(remotes/\S+)(.*)$}, 2)
1580
1596
  ])
1581
1597
  list_result(ret, 'branches', from: from)
1582
1598
  return
@@ -1601,7 +1617,7 @@ module Squared
1601
1617
  r3 = write.call($4, $5)
1602
1618
  end
1603
1619
  r1 = nil if r1 == "origin/#{data[0]}"
1604
- [" Branch: #{a + (r1 ? " (#{r1})" : '')}", r2, r3, " Commit: #{b}", "Message: #{r[2]}"]
1620
+ [" Branch: #{a.subhint(r1)}", r2, r3, " Commit: #{b}", "Message: #{r[2]}"]
1605
1621
  .compact
1606
1622
  .join("\n")
1607
1623
  end
@@ -1610,29 +1626,25 @@ module Squared
1610
1626
  print_error(name, 'no ref found', subject: 'branch', hint: 'head', pass: true) if ret == 0
1611
1627
  return
1612
1628
  end
1613
- return unless success?(source(stdout: stdout))
1629
+ return unless success?(source(stdout: stdout), !ref && flag == :create) && !ref && remote && target
1614
1630
 
1615
- if !ref
1616
- print_success if flag == :create
1617
- elsif remote && target
1618
- source git_output('push -u', shell_quote(ref.split('/', 2).first), shell_quote(target))
1619
- end
1631
+ source git_output('push -u', shell_quote(ref.split('/', 2).first), shell_quote(target))
1620
1632
  end
1621
1633
 
1622
1634
  def switch(flag, opts = [], branch: nil, commit: nil, track: nil)
1623
1635
  cmd, opts = git_session('switch', opts: opts)
1624
- cmd << '--force' if option('force', 'f')
1636
+ cmd << '--force' if option('f', 'force')
1625
1637
  if flag == :branch
1626
- op = OptionPartition.new(opts, OPT_GIT[:switch], cmd, project: self, no: OPT_GIT[:no][:switch])
1627
- op.add_quote(branch)
1638
+ OptionPartition.new(opts, OPT_GIT[:switch], cmd, project: self, no: OPT_GIT[:no][:switch])
1639
+ .add_quote(branch)
1628
1640
  else
1629
1641
  case flag
1630
1642
  when :create
1631
1643
  cmd << quote_option(branch.delete_prefix!('^') ? 'C' : 'c', branch)
1632
- cmd << case (track ||= option('track', ignore: false))
1633
- when 'n', 'N', '0', 'false'
1644
+ cmd << case (track ||= option('track', ignore: false))&.downcase
1645
+ when 'n', '0', 'false'
1634
1646
  '--no-track'
1635
- when 'y', 'Y', '1', 'true'
1647
+ when 'y', '1', 'true'
1636
1648
  '--track'
1637
1649
  when 'direct', 'inherit'
1638
1650
  basic_option 'track', track
@@ -1654,16 +1666,18 @@ module Squared
1654
1666
  op.add_quote(branch, '--', path, url)
1655
1667
  else
1656
1668
  op.adjoin(flag)
1657
- op << '--recursive' if option('recursive', 'r')
1669
+ op << '--recursive' if option('r', 'recursive')
1658
1670
  op.splice(path: true)
1659
1671
  end
1660
- print_success if success?(source, flag == :branch)
1672
+ source.tap { |ret| success?(ret, flag == :branch) }
1661
1673
  end
1662
1674
 
1663
1675
  def restore(flag, opts = [], commit: nil, files: nil)
1664
1676
  cmd, opts = git_session('restore', shell_option(flag, commit, escape: false, force: false), opts: opts)
1665
- op = OptionPartition.new(opts, OPT_GIT[:restore], cmd, project: self, no: OPT_GIT[:no][:restore],
1666
- first: matchpathspec)
1677
+ op = OptionPartition.new(opts,
1678
+ OPT_GIT[:restore] + OPT_GIT[:log][:diff_context],
1679
+ cmd,
1680
+ project: self, no: OPT_GIT[:no][:restore], first: matchpathspec)
1667
1681
  append_pathspec(op.extras + (files || []), pass: false)
1668
1682
  source(sync: false, stderr: true)
1669
1683
  end
@@ -1689,9 +1703,9 @@ module Squared
1689
1703
  else
1690
1704
  opts << format if format
1691
1705
  end
1692
- op = OptionPartition.new(opts, OPT_GIT[:show] + OPT_GIT[:diff][:show] + OPT_GIT[:log][:diff], cmd,
1693
- project: self,
1694
- no: OPT_GIT[:no][:show] + collect_hash(OPT_GIT[:no][:log], pass: [:base]))
1706
+ list = OPT_GIT[:show] + OPT_GIT[:diff][:show] + OPT_GIT[:log][:diff] + OPT_GIT[:log][:diff_context]
1707
+ op = OptionPartition.new(opts, list, cmd, project: self, pass: [:base],
1708
+ no: OPT_GIT[:no][:show] + collect_hash(OPT_GIT[:no][:log]))
1695
1709
  op.append(delim: true)
1696
1710
  source(exception: false, banner: flag != :oneline)
1697
1711
  end
@@ -1710,15 +1724,14 @@ module Squared
1710
1724
  cmd << '--sq-quote'
1711
1725
  args = true
1712
1726
  end
1713
- op = OptionPartition.new(opts, OPT_GIT[:rev_parse], cmd, project: self, no: OPT_GIT[:no][:rev_parse],
1714
- args: args)
1715
- op.append(escape: args)
1727
+ OptionPartition.new(opts, OPT_GIT[:rev_parse], cmd, project: self, no: OPT_GIT[:no][:rev_parse], args: args)
1728
+ .append(escape: args)
1716
1729
  end
1717
- source(banner: verbosetype > 1)
1730
+ source(banner: verbose?)
1718
1731
  end
1719
1732
 
1720
1733
  def ls_remote(flag, opts = [], remote: nil)
1721
- cmd, opts = git_session('ls-remote', '--refs', opts: opts)
1734
+ cmd, opts = git_session('ls-remote --refs', opts: opts)
1722
1735
  cmd << "--#{flag}" unless flag == :remote
1723
1736
  op = OptionPartition.new(opts, OPT_GIT[:ls_remote], cmd, project: self)
1724
1737
  op.add_quote(remote) if remote
@@ -1729,7 +1742,7 @@ module Squared
1729
1742
  end
1730
1743
 
1731
1744
  def ls_files(flag, opts = [])
1732
- cmd, opts = git_session('ls-files', "--#{flag}", opts: opts)
1745
+ cmd, opts = git_session("ls-files --#{flag}", opts: opts)
1733
1746
  op = OptionPartition.new(opts, OPT_GIT[:ls_files], cmd, project: self)
1734
1747
  op.splice(path: true, pattern: true)
1735
1748
  out, banner, from = source(io: true)
@@ -1740,29 +1753,30 @@ module Squared
1740
1753
 
1741
1754
  def git(flag, opts = [])
1742
1755
  cmd, opts = git_session(flag, opts: opts)
1743
- op = OptionPartition.new(opts, OPT_GIT[:git].fetch(flag, []) + OPT_GIT.fetch(flag, []), cmd,
1744
- project: self, no: OPT_GIT[:no][flag], first: case flag
1745
- when :blame, :revert
1746
- nil
1747
- else matchpathspec
1748
- end)
1756
+ list = OPT_GIT[:git].fetch(flag, []) + OPT_GIT.fetch(flag, [])
1757
+ list.concat(OPT_GIT[:log][:diff_context]) if flag == :add
1758
+ op = OptionPartition.new(opts, list, cmd, project: self, no: OPT_GIT[:no][flag], single: /\A\d+\z/,
1759
+ first: case flag
1760
+ when :blame, :revert then nil
1761
+ else matchpathspec
1762
+ end)
1749
1763
  case flag
1750
1764
  when :blame
1751
- raise_error 'no file found' unless (n = op.index { |s| basepath(s).file? })
1752
- op << '--' << shell_quote(basepath(op.delete_at(n)))
1753
- op.clear
1765
+ raise_error Errno::ENOENT, 'no file target' unless (n = op.index { |s| basepath(s).file? })
1766
+ op.append(basepath(op.remove_at(n)), delim: true)
1767
+ .clear
1754
1768
  when :revert
1755
1769
  if VAL_GIT[:rebase][:send].any? { |val| op.arg?(val) }
1756
1770
  op.clear
1757
1771
  elsif op.empty?
1758
- raise_error 'no commit given'
1772
+ raise_error 'no commit target'
1759
1773
  else
1760
1774
  append_commit(*op.extras)
1761
1775
  end
1762
1776
  when :add
1763
1777
  if flag == :add && !op.arg?('pathspec-from-file')
1764
- grep, list = op.partition { |val| OptionPartition.pattern?(val) }
1765
- unless grep.empty? && !list.empty?
1778
+ grep, pathspec = op.partition { |val| OptionPartition.pattern?(val) }
1779
+ unless grep.empty? && !pathspec.empty?
1766
1780
  grep.map! { |val| Regexp.new(val[1..-2]) }
1767
1781
  files = [].tap do |out|
1768
1782
  status_data.each do |a, b|
@@ -1773,23 +1787,37 @@ module Squared
1773
1787
  end
1774
1788
  unless files.empty?
1775
1789
  files = choice_index('Select files', files, multiple: true, force: true, trim: /^\S+\s/,
1776
- accept: [['Add?', false, true]])
1790
+ accept: [accept_y('Add?')])
1777
1791
  end
1778
- op.swap(list + files)
1792
+ op.swap(pathspec + files)
1779
1793
  end
1780
1794
  end
1781
1795
  return source(git_session('status -s'), banner: false) unless append_pathspec(op.extras)
1782
-
1783
- print_success if success?(source, flag == :add && !op.arg?('verbose'))
1784
- return
1796
+ return success?(source) if flag == :add && !op.arg?('verbose')
1785
1797
  when :mv
1786
1798
  refs = projectmap op.extras
1787
1799
  raise_error 'no source/destination' unless refs.size > 1
1788
1800
  op.merge(refs)
1789
1801
  when :rm, :clean
1790
1802
  append_pathspec(op.extras, expect: flag == :rm)
1803
+ when :grep
1804
+ op.each do |val|
1805
+ if op.include?('--')
1806
+ op.add_path(val)
1807
+ elsif op.exist?(val, glob: true)
1808
+ op << '--'
1809
+ op.add_path(val)
1810
+ else
1811
+ op.add_quote(val)
1812
+ end
1813
+ end
1814
+ end
1815
+ case flag
1816
+ when :revert, :mv, :rm
1817
+ source(sync: false, stderr: true)
1818
+ else
1819
+ source
1791
1820
  end
1792
- source(sync: false, stderr: true)
1793
1821
  end
1794
1822
 
1795
1823
  def clone?
@@ -1817,20 +1845,16 @@ module Squared
1817
1845
  banner = nil unless banner? && !multiple
1818
1846
  args = true
1819
1847
  end
1820
- if cmd.respond_to?(:done)
1821
- if from.nil? && (from = cmd.drop(1).find { |val| val.match?(/\A[a-z]{1,2}[a-z-]*\z/) })
1822
- from = :"git:#{from}"
1823
- end
1824
- banner &&= cmd.temp { |val| val.start_with?(/--(?:work-tree|git-dir)/) }
1848
+ if from.nil? && (from = cmd.drop(1).find { |val| val.match?(/\A[a-z]{1,2}[a-z-]*\z/) })
1849
+ from = :"git:#{from}"
1850
+ elsif from == false
1851
+ from = nil
1825
1852
  end
1826
- from = nil if from == false
1853
+ banner &&= cmd.temp { |val| val.start_with?(/--(?:work-tree|git-dir)/) } if cmd.respond_to?(:temp)
1827
1854
  end
1828
1855
  cmd = session_done cmd
1829
1856
  log&.info cmd
1830
- banner = if banner
1831
- banner = (banner.is_a?(String) ? banner : cmd).gsub(File.join(path, ''), '')
1832
- format_banner(hint ? "#{banner} (#{hint})" : banner)
1833
- end
1857
+ banner = format_banner(banner.is_a?(String) ? banner : cmd, hint: hint, strip: true) if banner
1834
1858
  on :first, from
1835
1859
  begin
1836
1860
  if io
@@ -1840,10 +1864,12 @@ module Squared
1840
1864
  elsif stdin? ? sync : stdout
1841
1865
  print_item banner unless multiple
1842
1866
  ret = `#{cmd}`
1843
- if !ret.empty?
1867
+ raise(ret.chomp.empty? ? $?.to_s : ret) unless $?.success?
1868
+
1869
+ if ret.empty?
1870
+ success?(nil, !banner.nil?)
1871
+ else
1844
1872
  puts ret
1845
- elsif success?(!banner.nil?)
1846
- print_success
1847
1873
  end
1848
1874
  elsif !kwargs[:sub] && (sync || (!exception && !stderr))
1849
1875
  print_item banner unless multiple
@@ -1855,7 +1881,7 @@ module Squared
1855
1881
  n = write_lines(out, banner: banner, pass: true, **kwargs)
1856
1882
  if n == 0
1857
1883
  n = write_lines(err, banner: banner)
1858
- print_success if success?(n == 0 && !banner.nil?)
1884
+ success?(nil, n == 0 && !banner.nil?)
1859
1885
  else
1860
1886
  write_lines(err, loglevel: Logger::DEBUG)
1861
1887
  end
@@ -1874,17 +1900,14 @@ module Squared
1874
1900
  end
1875
1901
 
1876
1902
  def write_lines(data, grep: [], prefix: nil, sub: nil, banner: nil, loglevel: nil, pass: false, first: false)
1877
- grep = grep.empty? ? nil : matchmap(grep, prefix)
1878
- sub = nil if stdin?
1903
+ grep = (matchmap(grep, prefix) unless grep.empty?)
1904
+ sub = (as_a sub unless stdin?)
1879
1905
  ret = 0
1880
1906
  out = []
1881
1907
  data.each do |line|
1882
1908
  next if grep&.none? { |pat| pat.match?(line) }
1909
+ next if block_given? && !(line = yield line)
1883
1910
 
1884
- if block_given?
1885
- line = yield line
1886
- next unless line
1887
- end
1888
1911
  if loglevel
1889
1912
  log&.add loglevel, line
1890
1913
  else
@@ -1909,7 +1932,7 @@ module Squared
1909
1932
  styles = theme.fetch(:banner, []).reject { |s| s.to_s.end_with?('!') }
1910
1933
  styles << :bold if styles.size <= 1
1911
1934
  puts print_footer("#{size} #{size == 1 ? type.sub(/(?:(?<!l)e)?s\z/, '') : type}",
1912
- sub: [pat: /^(\d+)(.+)$/, styles: styles])
1935
+ sub: opt_style(styles, /^(\d+)(.+)$/))
1913
1936
  end
1914
1937
  on :last, from
1915
1938
  end
@@ -1950,7 +1973,7 @@ module Squared
1950
1973
  ret = {}
1951
1974
  status_data(*args).each do |file,|
1952
1975
  next if !glob.empty? && glob.none? { |val| File.fnmatch?(val, file, File::FNM_DOTMATCH) }
1953
- next if !pass.empty? && pass.any? { |val| File.fnmatch?(val, file, File::FNM_DOTMATCH) }
1976
+ next if pass.any? { |val| File.fnmatch?(val, file, File::FNM_DOTMATCH) }
1954
1977
 
1955
1978
  ret[file] = algorithm.hexdigest(File.read(basepath(file)))
1956
1979
  end
@@ -1967,8 +1990,8 @@ module Squared
1967
1990
  end
1968
1991
  end
1969
1992
 
1970
- def append_pull(opts, list, target: @session, flag: nil, no: nil, remote: nil, from: nil)
1971
- target << '--force' if option('force', 'f', target: target)
1993
+ def append_pull(opts, list, flag:, from:, target: @session, no: nil, remote: nil)
1994
+ target << '--force' if option('f', 'force', target: target)
1972
1995
  append_submodules(target: target, from: from)
1973
1996
  return if !remote && opts.empty?
1974
1997
 
@@ -1980,6 +2003,7 @@ module Squared
1980
2003
  when 'rebase'
1981
2004
  op << basic_option($1, $2) if VAL_GIT[:rebase][:value].include?($2)
1982
2005
  when 'shallow-since'
2006
+ require 'date'
1983
2007
  op.append?($1) { Date.parse($2).strftime('%F %T') }
1984
2008
  when 'recurse-submodules'
1985
2009
  op.append?($1, $2, type: :basic)
@@ -2057,9 +2081,7 @@ module Squared
2057
2081
  when '1', 'true'
2058
2082
  target << '--recurse-submodules'
2059
2083
  else
2060
- projectmap(split_escape(val)).each do |path|
2061
- target << basic_option('recurse-submodules', path)
2062
- end
2084
+ projectmap(split_escape(val)).each { |path| target << basic_option('recurse-submodules', path) }
2063
2085
  end
2064
2086
  else
2065
2087
  target << case val
@@ -2106,26 +2128,24 @@ module Squared
2106
2128
  stdout: stdout, banner: banner, **kwargs)
2107
2129
  end
2108
2130
 
2109
- def dryrun?(*, target: @session, **)
2110
- return false unless target
2111
-
2112
- target.include?('--dry-run') || !option('dry-run', target: target).nil?
2131
+ def dryrun?(*, target: @session, prefix: target&.first)
2132
+ Array(target).include?('--dry-run') || !option('dry-run', target: target, prefix: prefix).nil?
2113
2133
  end
2114
2134
 
2115
2135
  def quiet?(*, target: @session, **)
2116
2136
  return false unless target
2117
2137
 
2118
- target.include?('--quiet') || (target.include?('-q') && stripext(target.first) == 'git')
2138
+ target.include?('--quiet') || (target.include?('-q') && target.first.stripext == 'git')
2119
2139
  end
2120
2140
 
2121
2141
  def gitpath(*args)
2122
- path.join('.git', *args)
2142
+ basepath('.git', *args)
2123
2143
  end
2124
2144
 
2125
2145
  def repotrack(origin, branch, quote: true)
2126
2146
  i = origin.index('/')
2127
- branch = "#{branch}:#{origin[(i + 1)..-1]}" unless origin.end_with?("/#{branch}")
2128
- [origin[0..(i - 1)], branch].tap { |ret| ret.map! { |val| shell_quote(val) } if quote }
2147
+ branch = "#{branch}:#{origin[i.succ..-1]}" unless origin.end_with?("/#{branch}")
2148
+ [origin[0..i.pred], branch].tap { |ret| ret.map! { |val| shell_quote(val) } if quote }
2129
2149
  end
2130
2150
 
2131
2151
  def commithash(val)