squared 0.7.10 → 0.8.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.
@@ -2,10 +2,12 @@
2
2
 
3
3
  module Squared
4
4
  module Workspace
5
+ GIT_PROTO = %r{\A(https?|ssh|git|file)://}i.freeze
6
+ private_constant :GIT_PROTO
7
+
5
8
  module Git
6
9
  GIT_REPO = Support.hashobj
7
- GIT_PROTO = %r{\A(https?|ssh|git|file)://}i.freeze
8
- private_constant :GIT_REPO, :GIT_PROTO
10
+ private_constant :GIT_REPO
9
11
 
10
12
  attr_reader :revfile
11
13
 
@@ -141,6 +143,7 @@ module Squared
141
143
  end
142
144
  File.write(@revfile, JSON.pretty_generate(@revdoc))
143
145
  rescue => e
146
+ log&.debug e
144
147
  warn log_warn(e, pass: true) if warning
145
148
  ensure
146
149
  @revlock = false
@@ -186,6 +189,7 @@ module Squared
186
189
  A|after-context=i B|before-context=i color=b C|context=i m|max-count=n max-depth=n
187
190
  open-files-in-pager=b threads=n].freeze,
188
191
  mv: %w[k f|force n|dry-run v|verbose].freeze,
192
+ remote: %w[f m=b t=b no-tags tags mirror=b].freeze,
189
193
  revert: %w[e S=bm? n|no-commit reference cleanup=b gpg-sign=b? m|mainline=i s|signoff strategy=b
190
194
  X|strategy-option=b].freeze,
191
195
  rm: %w[r cached f|force n|dry-run ignore-unmatch pathspec-file-nul q|quiet sparse
@@ -232,6 +236,16 @@ module Squared
232
236
  f|force-rebase ignore-date ignore-whitespace i|interactive keep-base m|merge no-ff q|quiet quit
233
237
  reset-author-date root show-current-patch signoff v|verbose empty=b x|exec=q gpg-sign=b? onto=b
234
238
  r|rebase-merges=b s|strategy=b X|strategy-option=b trailer=q whitespace=b].freeze,
239
+ remote: {
240
+ rename: %w[no-progress progress].freeze,
241
+ remove: [].freeze,
242
+ 'set-head': %w[a|auto d|delete].freeze,
243
+ 'set-branches': %w[add].freeze,
244
+ 'get-url': %w[all push].freeze,
245
+ 'set-url': %w[add delete push].freeze,
246
+ show: %w[n].freeze,
247
+ prune: %w[n dry-run].freeze
248
+ }.freeze,
235
249
  reset: %w[N pathspec-file-nul q|quiet pathspec-from-file=p].freeze,
236
250
  restore: %w[ignore-skip-worktree-bits ignore-unmerged m|merge ours p|patch pathspec-file-nul q|quiet S|staged
237
251
  theirs W|worktree conflict=b pathspec-from-file=p s|source=b].freeze,
@@ -344,12 +358,13 @@ module Squared
344
358
  'pull' => %i[origin remote all].freeze,
345
359
  'rebase' => %i[branch onto send].freeze,
346
360
  'refs' => %i[heads tags remote].freeze,
361
+ 'remote' => %i[add select].freeze,
347
362
  'reset' => %i[commit index patch mode undo].freeze,
348
363
  'restore' => %i[source staged worktree].freeze,
349
364
  'rev' => %i[commit branch build output].freeze,
350
365
  'show' => %i[format oneline textconv].freeze,
351
366
  'sparse-checkout' => %i[add reapply list clean disable].freeze,
352
- 'stash' => %i[push pop apply branch drop clear list all staged worktree].freeze,
367
+ 'stash' => %i[push pop apply drop command].freeze,
353
368
  'submodule' => %i[status update branch url sync].freeze,
354
369
  'switch' => %i[branch create detach].freeze,
355
370
  'tag' => %i[add sign delete list].freeze
@@ -480,31 +495,37 @@ module Squared
480
495
  when :add, :sign
481
496
  format_desc action, flag, 'name,message?,commit?,remote?'
482
497
  task flag, [:name, :message, :commit, :remote] do |_, args|
483
- remote = if (name = args.name)
484
- message = args.message
485
- commit = commithead args.commit
486
- args.remote
487
- else
488
- commit, name, message = choice_commit(reflog: false, series: true,
489
- values: [
490
- ['Enter tag name', true],
491
- 'Enter message'
492
- ])
493
- choice_remote
494
- end
498
+ if (name = args.name)
499
+ message = args.message
500
+ commit = commithead args.commit
501
+ remote = args.remote
502
+ if !remote && commit && !commit.start_with?('@') && !git_grep('remote', commit).empty?
503
+ remote = commit
504
+ commit = nil
505
+ end
506
+ else
507
+ commit, name, message = choice_commit(reflog: false, series: true,
508
+ values: [['Enter tag name', true], 'Enter message'])
509
+ remote = choice_remote
510
+ end
495
511
  ret = tag(flag, refs: [name], message: message, commit: commit, remote: remote)
496
512
  success?(ret, !remote)
497
513
  end
498
514
  end
499
515
  when 'stash'
500
- format_desc(action, flag, 'opts*', after: case flag
516
+ format_desc(action, flag, 'opts*', before: ('clear|list|all|staged|worktree' if flag == :command),
517
+ after: case flag
501
518
  when :push then 'pathspec*,:'
502
- when :branch then 'name,stash/:'
503
- when :clear, :list, :all then nil
519
+ when :command then nil
504
520
  else 'stash?|:'
505
521
  end)
506
522
  task flag do |_, args|
507
- stash flag, args.to_a
523
+ args = args.to_a
524
+ if flag == :command
525
+ param_guard(action, flag, args: args)
526
+ flag = args.shift.to_sym
527
+ end
528
+ stash flag, args
508
529
  end
509
530
  when 'log', 'diff', 'range-diff'
510
531
  case flag
@@ -530,7 +551,7 @@ module Squared
530
551
  end
531
552
  args = args.drop(index.size)
532
553
  end
533
- log!(flag, args, index: index)
554
+ log_(flag, args, index: index)
534
555
  end
535
556
  next
536
557
  end
@@ -548,7 +569,7 @@ module Squared
548
569
  opts.concat(refs.shellsplit) if refs
549
570
  end
550
571
  if action == 'log'
551
- log!(flag, opts, range: range)
572
+ log_(flag, opts, range: range)
552
573
  else
553
574
  diff(flag, opts, range: range, from: action.to_sym)
554
575
  end
@@ -612,7 +633,7 @@ module Squared
612
633
  end
613
634
  end
614
635
  param_guard(action, flag, args: grep)
615
- log!(flag, opts, grep: grep)
636
+ log_(flag, opts, grep: grep)
616
637
  end
617
638
  end
618
639
  when 'checkout'
@@ -793,10 +814,10 @@ module Squared
793
814
  end
794
815
  success?(reset(flag, args, commit: commit))
795
816
  end
796
- when :index, :undo
797
- format_desc(action, flag, ('opts*,pathspec*' if flag == :index))
817
+ when :index
818
+ format_desc action, flag, 'opts*,pathspec*'
798
819
  task flag do |_, args|
799
- reset(flag, flag == :index ? args.to_a : [])
820
+ reset(flag, args.to_a, refs: [])
800
821
  end
801
822
  when :mode
802
823
  format_desc action, flag, 'mode,ref/:'
@@ -811,6 +832,11 @@ module Squared
811
832
  ref = commithead args.ref
812
833
  reset(flag, refs: args.extras, ref: !ref || ref == ':' ? choice_commit(reflog: false) : ref)
813
834
  end
835
+ when :undo
836
+ format_desc action, flag
837
+ task flag do
838
+ reset flag
839
+ end
814
840
  end
815
841
  when 'show'
816
842
  case flag
@@ -975,6 +1001,51 @@ module Squared
975
1001
  task flag do |_, args|
976
1002
  sparse_checkout flag, args.to_a
977
1003
  end
1004
+ when 'remote'
1005
+ if flag == :add
1006
+ format_desc action, flag, 'name,url,opts*'
1007
+ task flag, [:name, :url] do |_, args|
1008
+ name = param_guard(action, flag, args: args, key: :name)
1009
+ url = param_guard(action, flag, args: args, key: :url)
1010
+ raise ArgumentError, message('invalid url', hint: url) unless url.match?(GIT_PROTO)
1011
+
1012
+ git(:remote, args.extras, extras: [name, url])
1013
+ end
1014
+ else
1015
+ format_desc action, flag, "#{OPT_GIT[:remote].keys.join('|')},opts*" if TASK_METADATA
1016
+ task flag, [:command] do |_, args|
1017
+ command = param_guard(action, flag, args: args, key: :command)
1018
+ cmd, opts = git_session('remote', opts: args.extras)
1019
+ param = case command
1020
+ when 'rename'
1021
+ 'New name'
1022
+ when 'set-branches'
1023
+ 'Branches'
1024
+ when 'set-head'
1025
+ 'Branch' if opts.empty?
1026
+ when 'set-url'
1027
+ 'URL'
1028
+ end
1029
+ remote, target = choice_remote(force: true, values: param ? [[param, true]] : [])
1030
+ cmd << '-v' if has_value!(opts, 'v', 'verbose') && command == 'show'
1031
+ cmd << command << remote
1032
+ op = OptionPartition.new(opts, OPT_GIT[:remote].fetch(command.to_sym, []), cmd,
1033
+ project: self, strict: strict?)
1034
+ case command
1035
+ when 'remove'
1036
+ exit 1 unless confirm_basic('Remove?', remote, 'N')
1037
+ when 'get-url', 'show', 'prune'
1038
+ status = false
1039
+ else
1040
+ raise ArgumentError, message('unrecognized command', command, hint: action) unless param
1041
+ raise ArgumentError, message('missing target', command, hint: action) unless target
1042
+
1043
+ op << target
1044
+ end
1045
+ op.clear
1046
+ success?(source(banner: !quiet?), status)
1047
+ end
1048
+ end
978
1049
  when 'git'
979
1050
  before = case flag
980
1051
  when :blame
@@ -1047,15 +1118,16 @@ module Squared
1047
1118
  op = OptionPartition.new(opts, OPT_GIT[:pull], cmd, project: self, strict: strict?,
1048
1119
  no: OPT_GIT[:no][:pull])
1049
1120
  opts -= op.extras
1050
- reg, neg = matchmap(op, negate: true)
1121
+ reg = matchmap op
1051
1122
  session_done op.target
1052
1123
  heads = []
1053
1124
  cur = nil
1054
1125
  foreachref('heads', format: '%(if)%(HEAD)%(then)* %(end)%(refname:short)').each do |line|
1055
1126
  cur ||= line.delete_prefix!('* ')
1056
- heads << line if matchany?(reg, line) && !matchany?(neg, line, empty: false)
1127
+ heads << line if matchany?(reg, line)
1057
1128
  end
1058
- raise_error 'head not found', hint: 'for-each-ref' unless cur
1129
+ raise message('head not found', hint: 'for-each-ref') unless cur
1130
+
1059
1131
  opts << 'ff-only' if opts.empty? && option('ff-only', notequals: '0')
1060
1132
  (heads.dup << cur).each_with_index do |branch, i|
1061
1133
  next unless (i < heads.size && cur != branch) || i == heads.size
@@ -1218,22 +1290,21 @@ module Squared
1218
1290
  else
1219
1291
  op << out
1220
1292
  end
1221
- elsif !op.empty?
1222
- op.add_first(prefix: ':')
1223
1293
  elsif flag == :branch
1224
- raise ArgumentError, 'no branch name'
1294
+ op.add_first(prefix: ':', expect: 'no branch name')
1225
1295
  end
1226
- op.clear
1296
+ op.add_first
1297
+ .clear
1227
1298
  when :clear
1228
1299
  n = sub_style file.read.lines.size, theme[:inline]
1229
1300
  s = sub_style name, theme[:active]
1230
1301
  source(stdout: true) if confirm("Remove #{n} stash entries from #{s}?", 'N')
1231
1302
  return
1232
- when :list
1303
+ else
1233
1304
  op.clear
1234
1305
  out, banner, from = source(io: true)
1235
1306
  print_item banner
1236
- list_result(write_lines(out), 'objects', banner: banner, from: from)
1307
+ list_result(write_lines(out), 'objects', from: from)
1237
1308
  return
1238
1309
  end
1239
1310
  else
@@ -1285,7 +1356,7 @@ module Squared
1285
1356
  end
1286
1357
  out, banner, from = source(io: true)
1287
1358
  ret = write_lines(out, banner: banner, sub: sub)
1288
- list_result(ret, 'files', action: 'modified', banner: banner, from: from)
1359
+ list_result(ret, 'files', action: 'modified', from: from)
1289
1360
  end
1290
1361
 
1291
1362
  def revbuild(flag = nil, opts = [], **kwargs)
@@ -1320,6 +1391,7 @@ module Squared
1320
1391
 
1321
1392
  sync = kwargs.fetch(:sync) { invoked_sync?('revbuild', flag) }
1322
1393
  kwargs = kwargs.key?(:include) || kwargs.key?(:exclude) ? kw.call : @revbuild || { pass: true }
1394
+ from = symjoin 'git', 'revbuild'
1323
1395
  case flag
1324
1396
  when :build
1325
1397
  op = OptionPartition.new(opts, VAL_GIT[:revbuild].map { |key| "#{key}=b?" }, project: self, strict: strict?)
@@ -1332,7 +1404,7 @@ module Squared
1332
1404
  .compact
1333
1405
  OptionPartition.uniq!(args)
1334
1406
  end
1335
- run_p(*Array(kwargs[:before]), sync: sync, from: :revbuild) if kwargs[:before]
1407
+ run_p(*Array(kwargs[:before]), sync: sync, from: symjoin(from, 'before')) if kwargs[:before]
1336
1408
  force = if (force = env('REVBUILD_FORCE', strict: true))
1337
1409
  force != '0' && force != 'false'
1338
1410
  elsif (force = ENV['GIT_FORCE'] || ENV['REVBUILD_FORCE'])
@@ -1347,7 +1419,7 @@ module Squared
1347
1419
  end
1348
1420
  end
1349
1421
  start = time_epoch
1350
- ret = build(*@output, sync: sync, from: symjoin('git', 'revbuild'))
1422
+ ret = build(*@output, sync: sync, from: from)
1351
1423
  rescue => e
1352
1424
  print_error(e, pass: true)
1353
1425
  else
@@ -1355,7 +1427,7 @@ module Squared
1355
1427
  print_status('revbuild', subject: name, start: start, loglevel: Logger::WARN, from: :failed)
1356
1428
  workspace.rev_timeutc name, 'build'
1357
1429
  else
1358
- run_p(*Array(kwargs[:after]), sync: sync, from: :revbuild) if kwargs[:after] && ret != false
1430
+ run_p(*Array(kwargs[:after]), sync: sync, from: symjoin(from, 'after')) if kwargs[:after] && ret != false
1359
1431
  print_status('revbuild', subject: name, start: start)
1360
1432
  workspace.rev_write(name, { 'revision' => sha, 'files' => status_digest(*args, **kwargs) },
1361
1433
  sync: sync, utc: 'build')
@@ -1453,7 +1525,7 @@ module Squared
1453
1525
  out, banner, from = source(io: true)
1454
1526
  print_item banner
1455
1527
  ret = write_lines(out, grep: op.extras)
1456
- list_result(ret, 'tags', grep: op.extras, banner: banner, from: from)
1528
+ list_result(ret, 'tags', grep: op.extras, from: from)
1457
1529
  return
1458
1530
  end
1459
1531
  if (ret = source) && (remote ||= option('remote'))
@@ -1462,11 +1534,11 @@ module Squared
1462
1534
  ret
1463
1535
  end
1464
1536
 
1465
- def log!(flag, opts = [], range: [], index: [], grep: [])
1537
+ def log_(flag, opts = [], range: [], index: [], grep: [])
1466
1538
  cmd, opts = git_session('log', opts: opts)
1467
- op = OptionPartition.new(opts, collect_hash(OPT_GIT[:log]), cmd,
1468
- project: self, strict: strict?, no: collect_hash(OPT_GIT[:no][:log]),
1469
- first: matchpathspec)
1539
+ op = OptionPartition.new(opts, collect_hash(OPT_GIT[:log]), cmd, project: self, strict: strict?,
1540
+ no: collect_hash(OPT_GIT[:no][:log]),
1541
+ first: matchpathspec)
1470
1542
  case flag
1471
1543
  when :between, :contain
1472
1544
  op.add_quote(range.join(flag == :between ? '..' : '...'))
@@ -1479,7 +1551,6 @@ module Squared
1479
1551
  append_pathspec op.extras
1480
1552
  source(exception: false)
1481
1553
  end
1482
- alias log_ log!
1483
1554
 
1484
1555
  def diff(flag, opts = [], refs: [], branch: nil, range: [], index: [], from: :diff)
1485
1556
  cmd, opts = git_session(from, opts: opts)
@@ -1512,7 +1583,8 @@ module Squared
1512
1583
  op.add_quote(branch) if branch
1513
1584
  if !index.empty?
1514
1585
  if op.arg?('cached')
1515
- raise_error "single commit: #{index.join(', ')}", hint: 'cached' unless index.size == 1
1586
+ raise message('single commit only', index.join(', '), hint: flag) unless index.size == 1
1587
+
1516
1588
  op << index.first
1517
1589
  else
1518
1590
  op.merge(index)
@@ -1575,9 +1647,9 @@ module Squared
1575
1647
  next if line.empty?
1576
1648
 
1577
1649
  branch, origin, hint = line.split('...')
1578
- if hint && !hint.match?(/^\[(\D+0,\D+0)\]$/)
1579
- raise_error 'work tree is not usable', hint: hint[1..-2]
1580
- elsif (!origin || origin.empty?) && !dryrun?
1650
+ raise message('work tree is not usable', hint: hint[1..-2]) if hint && !hint.match?(/^\[(\D+0,\D+0)\]$/)
1651
+
1652
+ if (!origin || origin.empty?) && !dryrun?
1581
1653
  return nil if pass
1582
1654
 
1583
1655
  unless (origin = option('upstream', prefix: 'git', ignore: false))
@@ -1680,7 +1752,8 @@ module Squared
1680
1752
  cmd << shell_quote(target)
1681
1753
  cmd << shell_quote(ref) if ref
1682
1754
  when :track
1683
- raise_error 'invalid upstream', hint: ref unless ref.include?('/')
1755
+ raise message('invalid upstream', hint: ref) unless ref.include?('/')
1756
+
1684
1757
  if ref.delete_prefix!('^')
1685
1758
  cmd << '--unset-upstream' << shell_quote(ref)
1686
1759
  remote = false
@@ -1721,7 +1794,7 @@ module Squared
1721
1794
  opt_style(color(:green), /^(\*\s+)(\S+)(.*)$/, 2),
1722
1795
  opt_style(color(:red), %r{^(\s*)(remotes/\S+)(.*)$}, 2)
1723
1796
  ])
1724
- list_result(ret, 'branches', banner: banner, from: from)
1797
+ list_result(ret, 'branches', from: from)
1725
1798
  return
1726
1799
  else
1727
1800
  if (head = git_spawn('rev-parse --abbrev-ref HEAD').chomp).empty?
@@ -1730,7 +1803,7 @@ module Squared
1730
1803
  git_spawn 'fetch --all --prune --quiet' if option('sync')
1731
1804
  cmd << '-vv --no-abbrev --list'
1732
1805
  out, banner, from = source(io: true)
1733
- first = from_base?('branch')
1806
+ first = workspace.size > 1
1734
1807
  grep = first ? [/^\*\s+#{Regexp.escape(head)}\s/] : []
1735
1808
  ret = write_lines(out, grep: grep, banner: banner, first: first) do |line, index|
1736
1809
  next line if stdin?
@@ -1738,15 +1811,15 @@ module Squared
1738
1811
  data = line.sub(/^\*?\s+/, '').split(/\s+/, 3)
1739
1812
  a = sub_style(data[0], theme[:inline], styles: (:underline if !first && line.start_with?('*')))
1740
1813
  b = commitstyle data[1]
1741
- r = /\A(?:\[((?~\]\s))\]\s)?(.+)\z/m.match(data[2])
1742
- if (r1 = r[1]) && r1 =~ /^(.+):(?: ([a-z]+) (\d+),)? ([a-z]+) (\d+)$/
1814
+ md = /\A(?:\[((?~\]\s))\]\s)?(.+)\z/m.match(data[2])
1815
+ if (r1 = md[1]) && r1 =~ /^(.+):(?: ([a-z]+) (\d+),)? ([a-z]+) (\d+)$/
1743
1816
  write = ->(s1, s2) { "#{s1.capitalize.rjust(7)}: #{sub_style(s2, theme[:warn])}" }
1744
1817
  r1 = $1
1745
1818
  r2 = $2 && write.call($2, $3)
1746
1819
  r3 = write.call($4, $5)
1747
1820
  end
1748
1821
  r1 = nil if r1 == "origin/#{data[0]}"
1749
- ["#{"\n" unless index == 0} Branch: #{a.subhint(r1)}", r2, r3, " Commit: #{b}", "Message: #{r[2]}"]
1822
+ ["#{"\n" unless index == 0} Branch: #{a.subhint(r1)}", r2, r3, " Commit: #{b}", "Message: #{md[2]}"]
1750
1823
  .compact
1751
1824
  .join("\n")
1752
1825
  end
@@ -1803,7 +1876,9 @@ module Squared
1803
1876
 
1804
1877
  def restore(flag, opts = [], commit: nil, files: nil)
1805
1878
  cmd, opts = git_session('restore', shell_option(flag, commit, escape: false, force: false), opts: opts)
1806
- op = OptionPartition.new(opts, OPT_GIT[:restore] + OPT_GIT[:log][:diff_context], cmd,
1879
+ op = OptionPartition.new(opts,
1880
+ OPT_GIT[:restore] + OPT_GIT[:log][:diff_context],
1881
+ cmd,
1807
1882
  project: self, strict: strict?, no: OPT_GIT[:no][:restore], first: matchpathspec)
1808
1883
  append_pathspec(op.extras + (files || []), pass: false)
1809
1884
  source(sync: false, stderr: true)
@@ -1832,7 +1907,8 @@ module Squared
1832
1907
  end
1833
1908
  list = OPT_GIT[:show] + OPT_GIT[:diff][:show] + OPT_GIT[:log][:diff] + OPT_GIT[:log][:diff_context]
1834
1909
  op = OptionPartition.new(opts, list, cmd,
1835
- project: self, strict: strict?,
1910
+ project: self,
1911
+ strict: strict?,
1836
1912
  no: OPT_GIT[:no][:show] + collect_hash(OPT_GIT[:no][:log], pass: [:base]))
1837
1913
  op.append(delim: true)
1838
1914
  source(exception: false, banner: flag != :oneline)
@@ -1857,8 +1933,8 @@ module Squared
1857
1933
  cmd << '--sq-quote'
1858
1934
  args = true
1859
1935
  end
1860
- OptionPartition.new(opts, OPT_GIT[:rev_parse], cmd, project: self, strict: strict?, args: args,
1861
- no: OPT_GIT[:no][:rev_parse])
1936
+ OptionPartition.new(opts, OPT_GIT[:rev_parse], cmd,
1937
+ project: self, strict: strict?, no: OPT_GIT[:no][:rev_parse], args: args)
1862
1938
  .append(escape: args)
1863
1939
  end
1864
1940
  source(banner: verbose?)
@@ -1886,7 +1962,7 @@ module Squared
1886
1962
  out, banner, from = source(io: true)
1887
1963
  print_item banner
1888
1964
  ret = write_lines(out, grep: op.extras, prefix: "refs/#{flag}/")
1889
- list_result(ret, flag.to_s, grep: op.extras, banner: banner, from: from)
1965
+ list_result(ret, flag.to_s, grep: op.extras, from: from)
1890
1966
  end
1891
1967
 
1892
1968
  def ls_files(flag, opts = [])
@@ -1896,12 +1972,12 @@ module Squared
1896
1972
  out, banner, from = source(io: true)
1897
1973
  print_item banner
1898
1974
  ret = write_lines(out, grep: op.extras)
1899
- list_result(ret, 'files', grep: op.extras, banner: banner, from: from)
1975
+ list_result(ret, 'files', grep: op.extras, from: from)
1900
1976
  end
1901
1977
 
1902
- def git(flag, opts = [])
1978
+ def git(flag, opts = [], extras: [])
1903
1979
  cmd, opts = git_session(flag, opts: opts)
1904
- list = OPT_GIT[:git].fetch(flag, []) + OPT_GIT.fetch(flag, [])
1980
+ list = OPT_GIT[:git].fetch(flag, []) + OPT_GIT.fetch(flag, []).yield_self { |a| a.is_a?(Array) ? a : [] }
1905
1981
  case flag
1906
1982
  when :add
1907
1983
  list.concat(OPT_GIT[:log][:diff_context])
@@ -1950,6 +2026,12 @@ module Squared
1950
2026
  end
1951
2027
  end
1952
2028
  return source(git_session('status -s'), banner: false) unless append_pathspec(op.extras)
2029
+ when :remote
2030
+ op.unshift(*extras)
2031
+ op.adjoin('add')
2032
+ .add_first(expect: 'missing name')
2033
+ .add_first(expect: 'missing URL', quote: true)
2034
+ .clear
1953
2035
  when :mv
1954
2036
  refs = projectmap op.extras
1955
2037
  raise 'no source/destination' unless refs.size > 1
@@ -1978,7 +2060,7 @@ module Squared
1978
2060
  when :revert, :mv, :rm
1979
2061
  source(sync: false, stderr: true)
1980
2062
  else
1981
- success?(source, flag == :'sparse-checkout' || (flag == :add && !op.arg?('verbose')))
2063
+ success?(source, %i[remote sparse-checkout].include?(flag) || (flag == :add && !op.arg?('verbose')))
1982
2064
  end
1983
2065
  end
1984
2066
 
@@ -1990,7 +2072,7 @@ module Squared
1990
2072
  build? && !!workspace.revfile
1991
2073
  end
1992
2074
 
1993
- def enabled?(*, **kwargs)
2075
+ def enabled?(**kwargs)
1994
2076
  super || (kwargs[:base] == false && !!clone?)
1995
2077
  end
1996
2078
 
@@ -2065,13 +2147,13 @@ module Squared
2065
2147
  end
2066
2148
  end
2067
2149
 
2068
- def list_result(size, type, action: 'found', grep: [], banner: nil, from: nil)
2150
+ def list_result(size, type, action: 'found', grep: [], from: nil)
2069
2151
  if size == 0
2070
2152
  puts empty_status("No #{type} were #{action}", 'grep', grep.join(', '))
2071
- elsif stdout? && (banner.nil? || (banner.is_a?(String) && !banner.empty?))
2153
+ elsif stdout?
2072
2154
  styles = theme.fetch(:banner, []).reject { |val| val.to_s.end_with?('!') }
2073
2155
  styles << :bold if styles.size <= 1
2074
- puts print_footer("#{size} #{size == 1 ? type.sub(/(?:(?<!l)e)?s\z/, '') : type}",
2156
+ puts print_footer("#{size} #{size == 1 ? type.sub(/((?<!l)e)?s\z/, '') : type}",
2075
2157
  sub: opt_style(styles, /^(\d+)(.+)$/))
2076
2158
  end
2077
2159
  on :last, from
@@ -2100,7 +2182,7 @@ module Squared
2100
2182
  ret = choice_index('Choose a commit', git_spawn(cmd, stdout: false), column: /^(\S+)/, force: force, **kwargs)
2101
2183
  case ret
2102
2184
  when Array
2103
- ret.map { |val| val&.stripstyle }
2185
+ ret.map(&:stripstyle)
2104
2186
  when String
2105
2187
  ret.stripstyle
2106
2188
  else
@@ -2137,8 +2219,8 @@ module Squared
2137
2219
  append_submodules(target: target, from: from)
2138
2220
  return if !remote && opts.empty?
2139
2221
 
2140
- op = OptionPartition.new(opts, remote ? list + ['refspec=v'] : list, target,
2141
- project: self, strict: strict?, no: no)
2222
+ op = OptionPartition.new(opts, remote ? list + ['refspec=v'] : list, target, project: self, strict: strict?,
2223
+ no: no)
2142
2224
  refspec = op.each_with_object([]) do |opt, out|
2143
2225
  if opt =~ op.values
2144
2226
  case $1
@@ -2278,7 +2360,21 @@ module Squared
2278
2360
  stdout: stdout, banner: banner, **kwargs)
2279
2361
  end
2280
2362
 
2281
- def quiet?(*, target: @session, **)
2363
+ def git_grep(cmd, *grep, equals: true, **kwargs)
2364
+ ret = []
2365
+ git_spawn(cmd, **kwargs).lines(chomp: true).each do |line|
2366
+ if grep.any? { |val| val.is_a?(Regexp) ? val.match?(line) : equals ? line == val : line.include?(val) }
2367
+ ret << line
2368
+ end
2369
+ end
2370
+ ret
2371
+ end
2372
+
2373
+ def dryrun?(target: @session, prefix: target&.first, **)
2374
+ Array(target).include?('--dry-run') || !option('dry-run', target: target, prefix: prefix).nil?
2375
+ end
2376
+
2377
+ def quiet?(target: @session, **)
2282
2378
  return silent? unless target
2283
2379
 
2284
2380
  silent? || target.include?('--quiet') || (target.include?('-q') && target.first.stripext == 'git')
@@ -2290,7 +2386,8 @@ module Squared
2290
2386
 
2291
2387
  def repotrack(origin, branch, quote: true)
2292
2388
  n = origin&.index('/')
2293
- raise_error "missing #{origin ? 'branch' : 'remote'} name", hint: origin unless n && branch
2389
+ raise message("missing #{origin ? 'branch' : 'remote'} name", hint: origin) unless n && branch
2390
+
2294
2391
  branch = "#{branch}:#{origin[n.succ..-1]}" unless origin.end_with?("/#{branch}")
2295
2392
  ret = [origin[0..n.pred], branch]
2296
2393
  ret.quote! if quote