squared 0.6.9 → 0.7.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.
@@ -84,7 +84,7 @@ module Squared
84
84
  def revbuild(file: nil)
85
85
  @revfile = @home.join(file || "#{@main}.revb")
86
86
  @revdoc = JSON.parse(@revfile.read) if @revfile.exist?
87
- rescue StandardError => e
87
+ rescue => e
88
88
  @revfile = nil
89
89
  warn log_warn(e, pass: true)
90
90
  self
@@ -117,7 +117,7 @@ module Squared
117
117
 
118
118
  def rev_timesince(*keys, clock: false)
119
119
  epoch = time_epoch - rev_entry(*keys).to_i
120
- rescue StandardError
120
+ rescue
121
121
  nil
122
122
  else
123
123
  time_format(epoch, clock: clock)
@@ -140,7 +140,7 @@ module Squared
140
140
  rev_timeutc(name, utc) if utc
141
141
  end
142
142
  File.write(@revfile, JSON.pretty_generate(@revdoc))
143
- rescue StandardError => e
143
+ rescue => e
144
144
  log&.debug e
145
145
  warn log_warn(e, pass: true) if warning
146
146
  ensure
@@ -190,7 +190,8 @@ module Squared
190
190
  revert: %w[e S=bm? n|no-commit reference cleanup=b gpg-sign=b? m|mainline=i s|signoff strategy=b
191
191
  X|strategy-option=b].freeze,
192
192
  rm: %w[r cached f|force n|dry-run ignore-unmatch pathspec-file-nul q|quiet sparse
193
- pathspec-from-file=p].freeze
193
+ pathspec-from-file=p].freeze,
194
+ 'sparse-checkout': %w[cone sparse-index no-cone no-sparse-index skip-checks].freeze
194
195
  }.freeze,
195
196
  log: {
196
197
  base: %w[L=qm all all-match alternate-refs author-date-order basic-regexp bisect boundary cherry cherry-mark
@@ -242,6 +243,10 @@ module Squared
242
243
  show-object-format=b? since=q tags=q? until=q].freeze,
243
244
  show: %w[t combined-all-paths no-diff-merges remerge-diff show-notes-by-default show-signature diff-merges=b
244
245
  encoding=b expand-tabs=i notes=q show-notes=q?].freeze,
246
+ sparse_checkout: {
247
+ add: %w[skip-checks].freeze,
248
+ clean: %w[n f v].freeze
249
+ }.freeze,
245
250
  stash: {
246
251
  common: %w[q|quiet].freeze,
247
252
  push: %w[a|all u|include-untracked k|keep-index no-keep-index no-include-untracked pathspec-file-nul p|patch
@@ -287,6 +292,10 @@ module Squared
287
292
  rev_parse: %w[flags].freeze,
288
293
  revert: %w[edit gpg-sign rerere-autoupdate].freeze,
289
294
  show: %w[standard-notes].freeze,
295
+ sparse_checkout: {
296
+ reapply: %w[cone sparse-index].freeze,
297
+ clean: %w[dry-run force verbose].freeze
298
+ }.freeze,
290
299
  status: %w[ahead-behind column renames].freeze,
291
300
  switch: %w[guess progress recurse-submodules track].freeze,
292
301
  tag: %w[column].freeze
@@ -326,7 +335,7 @@ module Squared
326
335
  'diff' => %i[head branch files view between contain].freeze,
327
336
  'fetch' => %i[origin remote all].freeze,
328
337
  'files' => %i[cached modified deleted others].freeze,
329
- 'git' => %i[add blame clean grep mv revert rm status].freeze,
338
+ 'git' => %i[add blame clean grep mv revert rm sparse-checkout status].freeze,
330
339
  'log' => %i[view grep between contain].freeze,
331
340
  'merge' => %i[commit no-commit send].freeze,
332
341
  'pull' => %i[origin remote all].freeze,
@@ -334,8 +343,9 @@ module Squared
334
343
  'refs' => %i[heads tags remote].freeze,
335
344
  'reset' => %i[commit index patch mode undo].freeze,
336
345
  'restore' => %i[source staged worktree].freeze,
337
- 'rev' => %i[commit build output].freeze,
346
+ 'rev' => %i[commit branch build output].freeze,
338
347
  'show' => %i[format oneline textconv].freeze,
348
+ 'sparse-checkout' => %i[add reapply list clean disable].freeze,
339
349
  'stash' => %i[push pop apply branch drop clear list all staged worktree].freeze,
340
350
  'submodule' => %i[status update branch url sync].freeze,
341
351
  'switch' => %i[branch create detach].freeze,
@@ -479,9 +489,8 @@ module Squared
479
489
  ])
480
490
  choice_remote
481
491
  end
482
- tag(flag, refs: [name], message: message, commit: commit, remote: remote).tap do |ret|
483
- success?(ret, !remote)
484
- end
492
+ ret = tag(flag, refs: [name], message: message, commit: commit, remote: remote)
493
+ success?(ret, !remote)
485
494
  end
486
495
  end
487
496
  when 'stash'
@@ -505,16 +514,15 @@ module Squared
505
514
  args.shift
506
515
  index = choice_commit(multiple: true)
507
516
  else
508
- index = []
509
- args.each do |val|
517
+ index = args.each_with_object([]) do |val, out|
510
518
  if matchhead(val)
511
- index << commithead(val)
519
+ out << commithead(val)
512
520
  elsif (sha = commithash(val))
513
- index << sha
521
+ out << sha
514
522
  elsif val.start_with?('^')
515
- index << shell_quote(val)
523
+ out << shell_quote(val)
516
524
  else
517
- break
525
+ break out
518
526
  end
519
527
  end
520
528
  args = args.drop(index.size)
@@ -547,11 +555,10 @@ module Squared
547
555
  args.shift
548
556
  index = choice_commit(multiple: true)
549
557
  else
550
- index = []
551
- args.each do |val|
552
- break unless (sha = commithead(val) || commithash(val))
558
+ index = args.each_with_object([]) do |val, out|
559
+ break out unless (sha = commithead(val) || commithash(val))
553
560
 
554
- index << sha
561
+ out << sha
555
562
  end
556
563
  args = args.drop(index.size)
557
564
  end
@@ -697,12 +704,12 @@ module Squared
697
704
  format_desc action, flag, '[^~]name*,:?'
698
705
  task flag do |_, args|
699
706
  refs = args.to_a
700
- if refs.empty? || (r = refs.last == ':')
707
+ if refs.empty? || (i = refs.last == ':')
701
708
  accept = ['Delete?']
702
- accept << accept_b('Force?') unless r
703
- remote = choice_refs('Choose a branch', r ? 'remotes' : 'heads', multiple: true,
709
+ accept << accept_b('Force?') unless i
710
+ remote = choice_refs('Choose a branch', i ? 'remotes' : 'heads', multiple: true,
704
711
  accept: accept)
705
- if r
712
+ if i
706
713
  refs.pop
707
714
  else
708
715
  refs = remote.first
@@ -882,6 +889,12 @@ module Squared
882
889
  end
883
890
  rev_parse(flag, ref: ref, size: size)
884
891
  end
892
+ when :branch
893
+ format_desc action, flag, 'ref?=HEAD|upstream|push'
894
+ task flag, [:ref] do |_, args|
895
+ ret = rev_parse(flag, ref: args.ref || 'upstream')
896
+ puts ret unless ret.empty?
897
+ end
885
898
  when :build
886
899
  next unless build?
887
900
 
@@ -927,20 +940,28 @@ module Squared
927
940
  task flag do |_, args|
928
941
  args = args.to_a
929
942
  if args.empty? || args.last == ':'
930
- files = []
931
- status_data.each { |row| files << row[0] if row[flag == :staged ? 2 : 1].match?(/[AMDRTC]/) }
943
+ files = status_data.each_with_object([]) do |row, out|
944
+ out << row[0] if row[flag == :staged ? 2 : 1].match?(/[AMDRTC]/)
945
+ end
932
946
  unless files.empty?
933
947
  files = choice_index('Select a file', files, multiple: true, force: false,
934
948
  accept: 'Restore?')
935
949
  end
936
950
  args.pop
937
- args, glob = args.partition { |val| val.match?(/^(?:[a-z-]+=|[^*]+$)/) }
951
+ args, glob = args.partition { |val| val.match?(/^([a-z-]+=|[^*]+$)/) }
938
952
  files.concat(glob)
939
953
  next if args.empty? && files.empty?
940
954
  end
941
955
  restore(flag, args, files: files)
942
956
  end
943
957
  end
958
+ when 'sparse-checkout'
959
+ break unless matchfile(gitpath('config.worktree'), /\bsparseCheckout\s+=\s+true/)
960
+
961
+ format_desc(action, flag, 'opts*', after: ('dir+' if flag == :add))
962
+ task flag do |_, args|
963
+ sparse_checkout flag, args.to_a
964
+ end
944
965
  when 'git'
945
966
  before = case flag
946
967
  when :blame
@@ -955,6 +976,8 @@ module Squared
955
976
  'pathspec*,pattern*'
956
977
  when :grep
957
978
  'tree*,pathspec*'
979
+ when :'sparse-checkout'
980
+ 'dir+'
958
981
  when :clean, :rm, :status
959
982
  'pathspec*'
960
983
  end
@@ -1019,12 +1042,11 @@ module Squared
1019
1042
  heads = []
1020
1043
  cur = nil
1021
1044
  foreachref('heads', format: '%(if)%(HEAD)%(then)* %(end)%(refname:short)').each do |line|
1022
- line.chomp!
1023
1045
  cur ||= line.delete_prefix!('* ')
1024
- heads << line if matchany?(line, reg)
1046
+ heads << line if matchany?(reg, line)
1025
1047
  end
1026
1048
  raise_error 'head not found', hint: 'for-each-ref' unless cur
1027
- opts << 'ff-only' if opts.empty? && !option('ff-only', equals: '0')
1049
+ opts << 'ff-only' if opts.empty? && option('ff-only', notequals: '0')
1028
1050
  (heads.dup << cur).each_with_index do |branch, i|
1029
1051
  next unless (i < heads.size && cur != branch) || i == heads.size
1030
1052
 
@@ -1038,12 +1060,16 @@ module Squared
1038
1060
  end
1039
1061
  append_pull(opts, OPT_GIT[:pull] + OPT_GIT[:fetch][:pull],
1040
1062
  flag: flag, from: :pull, remote: remote, no: OPT_GIT[:no][:pull] + OPT_GIT[:no][:fetch][:pull])
1041
- source(sync: sync, sub: if stdout?
1042
- [
1043
- opt_style(color(:red), /^(.+)(\|\s+\d+\s+)([^-]*)(-+)(.*)$/, 4),
1044
- opt_style(color(:green), /^(.+)(\|\s+\d+\s+)(\++)(.*)$/, 3)
1045
- ]
1046
- end, hint: hint, **threadargs)
1063
+ if sync
1064
+ source(hint: hint)
1065
+ else
1066
+ source(sync: false, sub: if stdout?
1067
+ [
1068
+ opt_style(color(:red), /^(.+)(\|\s+\d+\s+)([^-]*)(-+)(.*)$/, 4),
1069
+ opt_style(color(:green), /^(.+)(\|\s+\d+\s+)(\++)(.*)$/, 3)
1070
+ ]
1071
+ end, hint: hint, **threadargs)
1072
+ end
1047
1073
  end
1048
1074
 
1049
1075
  def rebase(flag = nil, opts = [], sync: invoked_sync?('rebase', flag), commit: nil, upstream: nil, branch: nil,
@@ -1087,7 +1113,11 @@ module Squared
1087
1113
  opts << 'all' if flag == :all || option('all')
1088
1114
  append_pull(opts, collect_hash(OPT_GIT[:fetch]), flag: flag, from: :fetch, remote: remote,
1089
1115
  no: collect_hash(OPT_GIT[:no][:fetch]))
1090
- source(sync: sync, **threadargs)
1116
+ if sync
1117
+ source
1118
+ else
1119
+ source(sync: false, **threadargs)
1120
+ end
1091
1121
  end
1092
1122
 
1093
1123
  def clone(*, sync: invoked_sync?('clone'), **)
@@ -1195,10 +1225,14 @@ module Squared
1195
1225
  end
1196
1226
  else
1197
1227
  git_session('stash', 'push', opts: opts)
1198
- append_option(OptionPartition.select(OPT_GIT[:stash][:push], no: false), no: true, ignore: false)
1228
+ append_option(*OptionPartition.select(OPT_GIT[:stash][:push], no: false), no: true, ignore: false)
1199
1229
  append_message
1200
1230
  end
1201
- source(sync: sync, banner: !quiet?, **threadargs)
1231
+ if sync
1232
+ source(banner: !quiet?)
1233
+ else
1234
+ source(sync: false, banner: !quiet?, **threadargs)
1235
+ end
1202
1236
  end
1203
1237
 
1204
1238
  def status(flag = nil, opts = [])
@@ -1211,12 +1245,10 @@ module Squared
1211
1245
  cmd << '--branch' if option('branch')
1212
1246
  option('ignore-submodules', ignore: false) do |val|
1213
1247
  cmd << basic_option('ignore-submodules', case val
1214
- when '0', 'none'
1248
+ when 'none', '0', 'false'
1215
1249
  'none'
1216
- when '1', 'untracked'
1217
- 'untracked'
1218
- when '2', 'dirty'
1219
- 'dirty'
1250
+ when 'untracked', 'dirty'
1251
+ val
1220
1252
  else
1221
1253
  'all'
1222
1254
  end)
@@ -1242,16 +1274,28 @@ module Squared
1242
1274
  list_result(ret, 'files', action: 'modified', from: from)
1243
1275
  end
1244
1276
 
1245
- def revbuild(flag = nil, opts = [], sync: nil, **kwargs)
1277
+ def revbuild(flag = nil, opts = [], **kwargs)
1246
1278
  kw = lambda do
1247
1279
  {
1248
1280
  include: relativepath(*Array(kwargs[:include]), all: true),
1249
- exclude: relativepath(*Array(kwargs[:exclude]), all: true)
1281
+ exclude: relativepath(*Array(kwargs[:exclude]), all: true),
1282
+ before: kwargs[:before],
1283
+ after: kwargs[:after],
1284
+ pass: kwargs.fetch(:pass, true)
1250
1285
  }
1251
1286
  end
1252
1287
  unless workspace.closed
1253
1288
  if @revbuild
1254
- kw.call.each { |key, val| @revbuild[key] += val }
1289
+ kw.call.each do |key, val|
1290
+ case @revbuild[key]
1291
+ when Array
1292
+ @revbuild[key].concat(val)
1293
+ when Hash
1294
+ @revbuild[key].update(val)
1295
+ else
1296
+ @revbuild[key] = val
1297
+ end
1298
+ end
1255
1299
  else
1256
1300
  @revbuild = kw.call
1257
1301
  end
@@ -1260,8 +1304,8 @@ module Squared
1260
1304
  sha = git_spawn('rev-parse --verify HEAD').chomp
1261
1305
  return if sha.empty?
1262
1306
 
1263
- sync = invoked_sync?('revbuild', flag) if sync.nil?
1264
- kwargs = kwargs.key?(:include) || kwargs.key?(:exclude) ? kw.call : @revbuild || {}
1307
+ sync = kwargs.fetch(:sync) { invoked_sync?('revbuild', flag) }
1308
+ kwargs = kwargs.key?(:include) || kwargs.key?(:exclude) ? kw.call : @revbuild || { pass: true }
1265
1309
  case flag
1266
1310
  when :build
1267
1311
  op = OptionPartition.new(opts, VAL_GIT[:revbuild].map { |key| "#{key}=b?" }, project: self)
@@ -1274,6 +1318,7 @@ module Squared
1274
1318
  .compact
1275
1319
  OptionPartition.uniq!(args)
1276
1320
  end
1321
+ run_p(*Array(kwargs[:before]), sync: sync, from: :revbuild) if kwargs[:before]
1277
1322
  if (cur = workspace.rev_entry(name)) && cur['revision'] == sha && !env('REVBUILD_FORCE')
1278
1323
  files = status_digest(*args, **kwargs)
1279
1324
  if cur['files'].size == files.size && cur['files'].find { |key, val| files[key] != val }.nil?
@@ -1283,13 +1328,19 @@ module Squared
1283
1328
  end
1284
1329
  end
1285
1330
  start = time_epoch
1286
- build(*@output, sync: sync, from: :'git:revbuild')
1287
- rescue StandardError => e
1331
+ ret = build(*@output, sync: sync, from: symjoin('git', 'revbuild'))
1332
+ rescue => e
1288
1333
  print_error(e, pass: true)
1289
1334
  else
1290
- print_status('revbuild', subject: name, start: start, from: :completed)
1291
- workspace.rev_write(name, { 'revision' => sha, 'files' => status_digest(*args, **kwargs) },
1292
- sync: sync, utc: 'build')
1335
+ if ret == false && !kwargs[:pass]
1336
+ print_status('revbuild', subject: name, start: start, loglevel: Logger::WARN, from: :failed)
1337
+ workspace.rev_timeutc name, 'build'
1338
+ else
1339
+ run_p(*Array(kwargs[:after]), sync: sync, from: :revbuild) if kwargs[:after] && ret != false
1340
+ print_status('revbuild', subject: name, start: start)
1341
+ workspace.rev_write(name, { 'revision' => sha, 'files' => status_digest(*args, **kwargs) },
1342
+ sync: sync, utc: 'build')
1343
+ end
1293
1344
  end
1294
1345
 
1295
1346
  def reset(flag, opts = [], refs: nil, ref: nil, mode: nil, commit: nil)
@@ -1341,9 +1392,9 @@ module Squared
1341
1392
  cmd << '-m' if merge
1342
1393
  cmd << '--detach' << commit
1343
1394
  else
1344
- list = OPT_GIT[:checkout] + OPT_GIT[:log][:diff_context]
1345
- op = OptionPartition.new(opts, list, cmd, project: self, no: OPT_GIT[:no][:checkout],
1346
- first: (matchpathspec if flag == :path))
1395
+ op = OptionPartition.new(opts, OPT_GIT[:checkout] + OPT_GIT[:log][:diff_context], cmd,
1396
+ project: self, no: OPT_GIT[:no][:checkout],
1397
+ first: (matchpathspec if flag == :path))
1347
1398
  if flag == :path
1348
1399
  append_head
1349
1400
  append_pathspec(op.extras, pass: false)
@@ -1383,8 +1434,10 @@ module Squared
1383
1434
  list_result(ret, 'tags', grep: op.extras, from: from)
1384
1435
  return
1385
1436
  end
1386
- remote ||= option('remote')
1387
- source.tap { |ret| git_spawn('push', ('-d' if flag == :delete), remote, *refs.quote!) if ret && remote }
1437
+ if (ret = source) && (remote ||= option('remote'))
1438
+ git_spawn('push', ('-d' if flag == :delete), remote, *refs.quote!)
1439
+ end
1440
+ ret
1388
1441
  end
1389
1442
 
1390
1443
  def log!(flag, opts = [], range: [], index: [], grep: [])
@@ -1488,7 +1541,7 @@ module Squared
1488
1541
  format = '%(if)%(HEAD)%(then)%(refname:short)...%(upstream:short)...%(upstream:track)%(end)'
1489
1542
  git_spawn 'fetch --no-tags --quiet'
1490
1543
  foreachref('heads', format: format).each do |line|
1491
- next if (line = line.chomp).empty?
1544
+ next if line.empty?
1492
1545
 
1493
1546
  branch, origin, hint = line.split('...')
1494
1547
  if hint && !hint.match?(/^\[(\D+0,\D+0)\]$/)
@@ -1533,7 +1586,7 @@ module Squared
1533
1586
  cached = git_spawn 'diff --cached --name-only --no-color'
1534
1587
  if amend || !cached.empty? || dryrun?
1535
1588
  if adding.empty? && !cached.empty? && banner?
1536
- puts(cached.lines(chomp: true).map! { |val| "cached #{shell_quote(val)}" })
1589
+ puts(cached.lines(chomp: true).map { |val| "cached #{shell_quote(val)}" })
1537
1590
  end
1538
1591
  source co
1539
1592
  source pu
@@ -1605,7 +1658,7 @@ module Squared
1605
1658
  end
1606
1659
  when :delete
1607
1660
  remote&.each { |val| source git_output('push --delete', *val.split('/', 2).quote!) }
1608
- force, list = refs.partition { |val| val.start_with?(/[~^]/) }
1661
+ force, list = refs.partition { |val| val.start_with?('~', '^') }
1609
1662
  force.each do |val|
1610
1663
  r = '-r' if val.delete!('~')
1611
1664
  source git_output('branch', val.delete!('^') ? '-D' : '-d', r, shell_quote(val))
@@ -1712,7 +1765,7 @@ module Squared
1712
1765
  op << '--recursive' if option('r', 'recursive')
1713
1766
  op.splice(path: true)
1714
1767
  end
1715
- source.tap { |ret| success?(ret, flag == :branch) }
1768
+ success?(source, flag == :branch)
1716
1769
  end
1717
1770
 
1718
1771
  def restore(flag, opts = [], commit: nil, files: nil)
@@ -1732,7 +1785,7 @@ module Squared
1732
1785
  cmd << '--textconv'
1733
1786
  append_value(files.flat_map { |val| Dir[val] }
1734
1787
  .select { |val| projectpath?(val) }
1735
- .map! { |val| shell_quote("HEAD:#{val}") })
1788
+ .map { |val| shell_quote("HEAD:#{val}") })
1736
1789
  source(banner: false)
1737
1790
  return
1738
1791
  when :oneline
@@ -1747,8 +1800,9 @@ module Squared
1747
1800
  opts << format if format
1748
1801
  end
1749
1802
  list = OPT_GIT[:show] + OPT_GIT[:diff][:show] + OPT_GIT[:log][:diff] + OPT_GIT[:log][:diff_context]
1750
- op = OptionPartition.new(opts, list, cmd, project: self, pass: [:base],
1751
- no: OPT_GIT[:no][:show] + collect_hash(OPT_GIT[:no][:log]))
1803
+ op = OptionPartition.new(opts, list, cmd,
1804
+ project: self,
1805
+ no: OPT_GIT[:no][:show] + collect_hash(OPT_GIT[:no][:log], pass: [:base]))
1752
1806
  op.append(delim: true)
1753
1807
  source(exception: false, banner: flag != :oneline)
1754
1808
  end
@@ -1760,8 +1814,13 @@ module Squared
1760
1814
  cmd << (size.to_i.zero? ? '--verify' : basic_option('short', [size.to_i, 5].max))
1761
1815
  append_commit(ref, head: true)
1762
1816
  when :branch
1763
- cmd << '--abbrev-ref'
1764
- append_commit(ref, head: true)
1817
+ cmd << '--abbrev-ref' << '--symbolic-full-name' << shell_quote(case ref
1818
+ when 'upstream', 'push'
1819
+ "@{#{ref}}"
1820
+ else
1821
+ ref
1822
+ end)
1823
+ return git_spawn(cmd, exception: false).chomp
1765
1824
  when :output
1766
1825
  if opts.delete('sq-quote')
1767
1826
  cmd << '--sq-quote'
@@ -1773,6 +1832,20 @@ module Squared
1773
1832
  source(banner: verbose?)
1774
1833
  end
1775
1834
 
1835
+ def sparse_checkout(flag, opts = [])
1836
+ cmd, opts = git_session('sparse-checkout', flag, opts: opts)
1837
+ op = OptionPartition.new(opts, OPT_GIT[:sparse_checkout].fetch(flag, []), cmd,
1838
+ project: self, no: OPT_GIT[:no][:sparse_checkout][flag])
1839
+ case flag
1840
+ when :add
1841
+ append_pathspec(op.detach, expect: true, resolve: false, pass: false)
1842
+ when :clean
1843
+ op << '--dry-run' unless op.arg?('n', 'f', 'force')
1844
+ end
1845
+ op.clear
1846
+ success?(source, flag != :list)
1847
+ end
1848
+
1776
1849
  def ls_remote(flag, opts = [], remote: nil)
1777
1850
  cmd, opts = git_session('ls-remote --refs', opts: opts)
1778
1851
  cmd << "--#{flag}" unless flag == :remote
@@ -1802,10 +1875,12 @@ module Squared
1802
1875
  list.concat(OPT_GIT[:log][:diff_context])
1803
1876
  when :revert
1804
1877
  list.concat(VAL_GIT[:rebase][:send])
1878
+ when :'sparse-checkout'
1879
+ cmd << 'set'
1805
1880
  end
1806
1881
  op = OptionPartition.new(opts, list, cmd, project: self, no: OPT_GIT[:no][flag], single: /\A\d+\z/,
1807
1882
  first: case flag
1808
- when :blame, :revert then nil
1883
+ when :blame, :revert, :'sparse-checkout' then nil
1809
1884
  else matchpathspec
1810
1885
  end)
1811
1886
  case flag
@@ -1826,12 +1901,12 @@ module Squared
1826
1901
  grep, pathspec = op.partition { |val| OptionPartition.pattern?(val) }
1827
1902
  unless grep.empty? && !pathspec.empty?
1828
1903
  grep.map! { |val| Regexp.new(val[1..-2]) }
1829
- files = []
1830
- status_data.each do |a, b|
1904
+ files = status_data.map do |a, b|
1831
1905
  next if b.strip.empty? || (!grep.empty? && grep.none? { |pat| pat.match?(a) })
1832
1906
 
1833
- files << "#{sub_style(b, color(:red))} #{a}"
1907
+ "#{sub_style(b, color(:red))} #{a}"
1834
1908
  end
1909
+ .compact
1835
1910
  unless files.empty?
1836
1911
  files = choice_index('Select files', files, multiple: true, trim: /^\S+\s/,
1837
1912
  accept: [accept_y('Add?')])
@@ -1840,13 +1915,12 @@ module Squared
1840
1915
  end
1841
1916
  end
1842
1917
  return source(git_session('status -s'), banner: false) unless append_pathspec(op.extras)
1843
- return success?(source) if flag == :add && !op.arg?('verbose')
1844
1918
  when :mv
1845
1919
  refs = projectmap op.extras
1846
1920
  raise_error 'no source/destination' unless refs.size > 1
1847
1921
  op.merge(refs)
1848
- when :rm, :clean
1849
- append_pathspec(op.extras, expect: flag == :rm)
1922
+ when :rm, :clean, :'sparse-checkout'
1923
+ append_pathspec(op.extras, expect: flag != :clean, resolve: flag != :'sparse-checkout')
1850
1924
  when :grep
1851
1925
  op.each do |val|
1852
1926
  if op.include?('--')
@@ -1863,7 +1937,7 @@ module Squared
1863
1937
  when :revert, :mv, :rm
1864
1938
  source(sync: false, stderr: true)
1865
1939
  else
1866
- source
1940
+ success?(source, flag == :'sparse-checkout' || (flag == :add && !op.arg?('verbose')))
1867
1941
  end
1868
1942
  end
1869
1943
 
@@ -1882,7 +1956,7 @@ module Squared
1882
1956
  private
1883
1957
 
1884
1958
  def source(cmd = @session, exception: true, io: false, sync: true, stdout: false, stderr: false, banner: true,
1885
- multiple: false, hint: nil, from: nil, send: :system, **kwargs)
1959
+ multiple: false, hint: nil, from: nil, timeout: nil, send: :system, **kwargs)
1886
1960
  cmd = cmd.target if cmd.is_a?(OptionPartition)
1887
1961
  if io && banner == false
1888
1962
  from = nil
@@ -1896,10 +1970,11 @@ module Squared
1896
1970
  from = nil
1897
1971
  elsif !from && cmd.respond_to?(:drop)
1898
1972
  from = cmd.drop(1).find { |val| val.match?(/\A[a-z]{1,2}[a-z-]*\z/) }
1899
- from &&= :"git:#{from}"
1973
+ from &&= symjoin 'git', from
1900
1974
  end
1901
- banner &&= cmd.temp { |val| val.start_with?(/--(?:work-tree|git-dir)/) } if cmd.respond_to?(:temp)
1975
+ banner &&= cmd.temp { |val| val.start_with?(/--(work-tree|git-dir)/) } if cmd.respond_to?(:temp)
1902
1976
  end
1977
+ timeout = session_timeout cmd if timeout.nil?
1903
1978
  cmd = session_done cmd
1904
1979
  log&.info cmd
1905
1980
  banner = if banner
@@ -1913,7 +1988,11 @@ module Squared
1913
1988
  return args ? [IO.popen(cmd), banner || '', from] : IO.popen(cmd)
1914
1989
  elsif stdin? ? sync : stdout
1915
1990
  print_item banner unless multiple
1916
- ret = `#{cmd}`.chomp
1991
+ ret = if stdin? && timeout.to_f > 0
1992
+ Timeout.timeout(timeout) { `#{cmd}` }
1993
+ else
1994
+ `#{cmd}`
1995
+ end.chomp
1917
1996
  raise(ret.empty? ? $?.to_s : ret) unless $?.success?
1918
1997
 
1919
1998
  if ret.empty?
@@ -1923,25 +2002,17 @@ module Squared
1923
2002
  end
1924
2003
  elsif !kwargs[:sub] && (sync || (!exception && !stderr))
1925
2004
  print_item banner unless multiple
1926
- ret = shell(cmd, name: send, exception: exception)
2005
+ ret = shell_t(cmd, name: send, exception: exception, timeout: timeout)
2006
+ elsif timeout.to_f > 0
2007
+ Timeout.timeout(timeout) { run_e(cmd, stderr: stderr, banner: banner, **kwargs) }
1927
2008
  else
1928
- require 'open3'
1929
- if stderr
1930
- Open3.popen3(cmd) do |_, out, err|
1931
- n = write_lines(out, banner: banner, pass: true, **kwargs)
1932
- if n == 0
1933
- n = write_lines(err, banner: banner)
1934
- success?(n == 0, n == 0 && !banner.nil?)
1935
- else
1936
- write_lines(err, loglevel: Logger::DEBUG)
1937
- end
1938
- end
1939
- else
1940
- Open3.popen2e(cmd) { |_, out| write_lines(out, banner: banner, **kwargs) }
1941
- end
2009
+ run_e(cmd, stderr: stderr, banner: banner, **kwargs)
1942
2010
  end
1943
- rescue StandardError => e
1944
- on_error(e, from, pass: true)
2011
+ rescue Timeout::Error => e
2012
+ print_error(Logger::ERROR, cmd, subject: name, hint: e)
2013
+ exit 1 unless exception?(Logger::DEBUG, Logger::INFO)
2014
+ rescue => e
2015
+ on_error(e, pass: true)
1945
2016
  nil
1946
2017
  else
1947
2018
  on :last, from
@@ -1949,32 +2020,6 @@ module Squared
1949
2020
  end
1950
2021
  end
1951
2022
 
1952
- def write_lines(data, grep: [], prefix: nil, sub: nil, banner: nil, loglevel: nil, pass: false, first: false)
1953
- grep = (matchmap(grep, prefix) unless grep.empty?)
1954
- sub = (as_a sub unless stdin?)
1955
- ret = 0
1956
- out = []
1957
- data.each do |line|
1958
- next if grep&.none? { |pat| pat.match?(line) }
1959
- next if block_given? && !(line = yield(line, ret))
1960
-
1961
- if loglevel
1962
- log&.add loglevel, line
1963
- else
1964
- sub&.each { |h| sub_style!(line, **h) }
1965
- if banner
1966
- out << line
1967
- else
1968
- puts line
1969
- end
1970
- end
1971
- ret += 1
1972
- break if first
1973
- end
1974
- print_item banner, out if banner && (ret > 0 || (!pass && !first))
1975
- ret
1976
- end
1977
-
1978
2023
  def list_result(size, type, action: 'found', grep: [], from: nil)
1979
2024
  if size == 0
1980
2025
  puts empty_status("No #{type} were #{action}", 'grep', grep.join(', '))
@@ -2010,7 +2055,7 @@ module Squared
2010
2055
  ret = choice_index('Choose a commit', git_spawn(cmd, stdout: false), column: /^(\S+)/, force: force, **kwargs)
2011
2056
  case ret
2012
2057
  when Array
2013
- ret.map!(&:stripstyle)
2058
+ ret.map(&:stripstyle)
2014
2059
  when String
2015
2060
  ret.stripstyle
2016
2061
  else
@@ -2028,24 +2073,18 @@ module Squared
2028
2073
  algorithm ||= Digest::SHA256
2029
2074
  glob = kwargs.fetch(:include, [])
2030
2075
  pass = kwargs.fetch(:exclude, [])
2031
- ret = {}
2032
- status_data(*args).each do |file,|
2076
+ status_data(*args).map(&:first).each_with_object({}) do |file, out|
2033
2077
  next if !glob.empty? && glob.none? { |val| File.fnmatch?(val, file, File::FNM_DOTMATCH) }
2034
2078
  next if pass.any? { |val| File.fnmatch?(val, file, File::FNM_DOTMATCH) }
2035
2079
 
2036
- ret[file] = algorithm.hexdigest(File.read(basepath(file)))
2080
+ out[file] = algorithm.hexdigest(File.read(basepath(file)))
2037
2081
  end
2038
- ret
2039
2082
  end
2040
2083
 
2041
2084
  def status_data(*args)
2042
- ret = []
2043
- git_spawn('status -z -uall', *args).split("\x0").each do |line|
2044
- next unless line =~ /^(.)(.) (.+)$/
2045
-
2046
- ret << [$3, $2, $1]
2085
+ git_spawn('status -z -uall', *args).split("\x0").each_with_object([]) do |line, out|
2086
+ out << [$3, $2, $1] if line =~ /^(.)(.) (.+)$/
2047
2087
  end
2048
- ret
2049
2088
  end
2050
2089
 
2051
2090
  def append_pull(opts, list, flag:, from:, target: @session, no: nil, remote: nil)
@@ -2053,9 +2092,8 @@ module Squared
2053
2092
  append_submodules(target: target, from: from)
2054
2093
  return if !remote && opts.empty?
2055
2094
 
2056
- refspec = []
2057
2095
  op = OptionPartition.new(opts, remote ? list + ['refspec=v'] : list, target, project: self, no: no)
2058
- op.each do |opt|
2096
+ refspec = op.each_with_object([]) do |opt, out|
2059
2097
  if opt =~ op.values
2060
2098
  case $1
2061
2099
  when 'rebase'
@@ -2066,7 +2104,7 @@ module Squared
2066
2104
  when 'recurse-submodules'
2067
2105
  op.append?($1, $2, type: :basic)
2068
2106
  when 'refspec'
2069
- refspec << shell_quote($2)
2107
+ out << shell_quote($2)
2070
2108
  end
2071
2109
  elsif op.arg?('multiple')
2072
2110
  op.found << opt
@@ -2078,7 +2116,7 @@ module Squared
2078
2116
  if remote
2079
2117
  op.append(remote, delim: true)
2080
2118
  if (val = option('refspec', target: target, strict: true))
2081
- op.append(*split_escape(val))
2119
+ op.append(split_escape(val))
2082
2120
  else
2083
2121
  op.merge(refspec)
2084
2122
  end
@@ -2092,27 +2130,31 @@ module Squared
2092
2130
  op.clear(errors: true, subject: flag) if flag
2093
2131
  end
2094
2132
 
2095
- def append_commit(*val, target: @session, head: false)
2096
- val.compact!
2097
- if !val.empty?
2098
- val.each { |ref| target << (commithash(ref) || shell_quote(ref)) }
2133
+ def append_commit(*refs, target: @session, head: false)
2134
+ refs.compact!
2135
+ if !refs.empty?
2136
+ target.merge(refs.map { |val| commithash(val) || shell_quote(val) })
2099
2137
  elsif head
2100
2138
  target << (append_head(target: target) || 'HEAD')
2101
2139
  end
2102
2140
  end
2103
2141
 
2104
- def append_pathspec(files = [], target: @session, expect: false, parent: false, pass: true)
2142
+ def append_pathspec(files = [], target: @session, expect: false, **kwargs)
2105
2143
  if session_arg?('pathspec-from-file', target: target)
2106
- option_clear files
2144
+ OptionPartition.clear(target, files, styles: theme[:inline])
2107
2145
  true
2108
2146
  else
2109
2147
  option('pathspec', target: target) { |val| files = split_escape(val) } if files.empty?
2110
- files = projectmap(files, parent: parent, pass: pass)
2148
+ files = projectmap(files, **kwargs)
2111
2149
  if !files.empty?
2112
2150
  target << '--' << files.join(' ')
2113
2151
  true
2114
2152
  elsif expect
2115
- raise_error(parent ? 'pathspec not present' : 'pathspec not within worktree')
2153
+ raise_error(if expect.is_a?(String)
2154
+ expect
2155
+ else
2156
+ kwargs[:parent] ? 'pathspec not present' : 'pathspec not within worktree'
2157
+ end)
2116
2158
  else
2117
2159
  false
2118
2160
  end
@@ -2156,11 +2198,15 @@ module Squared
2156
2198
  end
2157
2199
  end
2158
2200
 
2159
- def foreachref(path, *args, format: nil)
2160
- path = Array(path).map! { |val| "refs/#{val}" }
2201
+ def foreachref(path, *args, format: nil, chomp: true)
2202
+ path = Array(path).map { |val| "refs/#{val}" }
2161
2203
  format &&= quote_option('format', format)
2162
2204
  ret = git_spawn('for-each-ref', format, *args, *path, stdout: workspace.windows?)
2163
- ret.is_a?(String) ? ret.lines : ret
2205
+ if ret.is_a?(String)
2206
+ ret.lines(chomp: chomp)
2207
+ else
2208
+ chomp ? ret.readlines(chomp: chomp) : ret
2209
+ end
2164
2210
  end
2165
2211
 
2166
2212
  def git_session(*cmd, opts: nil, worktree: true, **kwargs)
@@ -2207,7 +2253,9 @@ module Squared
2207
2253
  raise_error(ArgumentError, "missing #{origin ? 'branch' : 'remote'} name", hint: origin)
2208
2254
  end
2209
2255
  branch = "#{branch}:#{origin[i.succ..-1]}" unless origin.end_with?("/#{branch}")
2210
- [origin[0..i.pred], branch].tap { |ret| ret.quote! if quote }
2256
+ ret = [origin[0..i.pred], branch]
2257
+ ret.quote! if quote
2258
+ ret
2211
2259
  end
2212
2260
 
2213
2261
  def commithash(val)
@@ -2232,16 +2280,22 @@ module Squared
2232
2280
  [/\A[^a-z\d-]+/i, %r{\A[^=\\/*]*[\\/*]}, /\A--\z/]
2233
2281
  end
2234
2282
 
2283
+ def matchfile(file, pat)
2284
+ file.read[pat, 0] if file.exist?
2285
+ rescue
2286
+ nil
2287
+ end
2288
+
2235
2289
  def messageopt
2236
2290
  option('message', 'm', prefix: 'git', ignore: false)
2237
2291
  end
2238
2292
 
2239
2293
  def threadargs
2240
- { stderr: true, exception: exception || !workspace.series.multiple? }
2294
+ { stderr: true, exception: exception? || !workspace.series.multiple? }
2241
2295
  end
2242
2296
  end
2243
2297
 
2244
- Application.implement Git
2298
+ Application.implement Git, base: 1
2245
2299
  end
2246
2300
  end
2247
2301
  end