squared 0.6.16 → 0.7.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.
@@ -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
@@ -169,28 +169,29 @@ module Squared
169
169
  }.freeze,
170
170
  fetch: {
171
171
  base: %w[multiple porcelain progress P|prune-tags refetch stdin u|update-head-ok
172
- recurse-submodules-default=b?].freeze,
172
+ recurse-submodules-default=b].freeze,
173
173
  pull: %w[4 6 n t a|append atomic dry-run f|force k|keep negotiate-only prefetch p|prune q|quiet set-upstream
174
174
  unshallow update-shallow v|verbose deepen=i depth=i j|jobs=i negotiation-tip=q recurse-submodules=v
175
175
  refmap=q o|server-option=q shallow-exclude=b shallow-since=v upload-pack=q].freeze
176
176
  }.freeze,
177
177
  git: {
178
- add: %w[N|intent-to-add].freeze,
178
+ add: %w[N|intent-to-add refresh].freeze,
179
179
  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
180
180
  p|porcelain root score-debug f|show-name e|show-email n|show-number show-stats abbrev=i contents=p
181
- date=q diff-algorithm=b encoding=b ignore-rev=b ignore-revs-file=p reverse=q].freeze,
181
+ date=q encoding=b ignore-rev=b ignore-revs-file=p reverse=q].freeze,
182
182
  clean: %w[d x X f|force n|dry-run i|interactive q|quiet e|exclude=q].freeze,
183
183
  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
184
184
  l|files-with-matches L|files-without-match F|fixed-strings full-name W|function-context heading
185
185
  i|ignore-case v|invert-match n|line-number name-only no-index not z|null o|only-matching or
186
186
  P|perl-regexp q|quiet recurse-submodules p|show-function a|text untracked w|word-regexp
187
- A|after-context=i B|before-context=i color=b C|context=i m|max-count=n max-depth=n
187
+ A|after-context=i B|before-context=i color=b C|context=i m|max-count=n max-depth=i
188
188
  open-files-in-pager=b threads=n].freeze,
189
189
  mv: %w[k f|force n|dry-run v|verbose].freeze,
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
@@ -254,7 +259,7 @@ module Squared
254
259
  submodule: {
255
260
  status: %w[cached recursive].freeze,
256
261
  update: %w[checkout f|force init merge N|no-fetch no-recommend-shallow no-single-branch recommend-shallow
257
- rebase recursive remote single-branch depth=i filter=q jobs=i reference=q ref-format=q].freeze,
262
+ rebase recursive remote single-branch depth=i filter=q jobs=i reference=b ref-format=q].freeze,
258
263
  branch: %w[b|branch d|default].freeze,
259
264
  sync: %w[recursive].freeze
260
265
  }.freeze,
@@ -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
@@ -320,13 +329,13 @@ module Squared
320
329
  end
321
330
 
322
331
  subtasks({
323
- 'branch' => %i[create track delete move copy list all current].freeze,
332
+ 'branch' => %i[create track delete move copy list current].freeze,
324
333
  'checkout' => %i[commit branch track detach path].freeze,
325
334
  'commit' => %i[add all amend amend-orig fixup].freeze,
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,
@@ -426,7 +436,7 @@ module Squared
426
436
  end
427
437
  if squash
428
438
  found = false
429
- git_spawn('log --format=%h', stdout: false).each do |val|
439
+ git_spawn(git_output('log --format=%h'), stdout: false).each do |val|
430
440
  if found
431
441
  squash = val.chomp
432
442
  break
@@ -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
@@ -717,7 +724,12 @@ module Squared
717
724
  task flag do |_, args|
718
725
  branch flag, args.to_a
719
726
  end
720
- when :move, :copy
727
+ when :current
728
+ format_desc action, flag
729
+ task flag do
730
+ branch flag
731
+ end
732
+ else
721
733
  format_desc action, flag, 'branch,oldbranch?'
722
734
  task flag, [:branch, :oldbranch] do |_, args|
723
735
  if (branch = args.branch)
@@ -728,11 +740,6 @@ module Squared
728
740
  end
729
741
  branch(flag, refs: [oldbranch, branch])
730
742
  end
731
- else
732
- format_desc action, flag
733
- task flag do
734
- branch flag
735
- end
736
743
  end
737
744
  when 'switch'
738
745
  case flag
@@ -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
@@ -1009,18 +1032,21 @@ module Squared
1009
1032
  printsucc
1010
1033
  end
1011
1034
  op = OptionPartition.new(opts, OPT_GIT[:pull], cmd, project: self, no: OPT_GIT[:no][:pull])
1012
- opts -= op.extras
1013
- reg, neg = matchmap(op, negate: true)
1035
+ reg = if op.empty?
1036
+ []
1037
+ else
1038
+ opts = op.uniq(opts)
1039
+ matchmap op
1040
+ end
1014
1041
  session_done op.target
1015
1042
  heads = []
1016
1043
  cur = nil
1017
1044
  foreachref('heads', format: '%(if)%(HEAD)%(then)* %(end)%(refname:short)').each do |line|
1018
- line.chomp!
1019
1045
  cur ||= line.delete_prefix!('* ')
1020
- heads << line if matchany?(line, reg) && !matchany?(neg, line, empty: false)
1046
+ heads << line if matchany?(reg, line)
1021
1047
  end
1022
1048
  raise_error 'head not found', hint: 'for-each-ref' unless cur
1023
- opts << 'ff-only' if opts.empty? && !option('ff-only', equals: '0')
1049
+ opts << 'ff-only' if opts.empty? && option('ff-only', notequals: '0')
1024
1050
  (heads.dup << cur).each_with_index do |branch, i|
1025
1051
  next unless (i < heads.size && cur != branch) || i == heads.size
1026
1052
 
@@ -1034,12 +1060,16 @@ module Squared
1034
1060
  end
1035
1061
  append_pull(opts, OPT_GIT[:pull] + OPT_GIT[:fetch][:pull],
1036
1062
  flag: flag, from: :pull, remote: remote, no: OPT_GIT[:no][:pull] + OPT_GIT[:no][:fetch][:pull])
1037
- source(sync: sync, sub: if stdout?
1038
- [
1039
- opt_style(color(:red), /^(.+)(\|\s+\d+\s+)([^-]*)(-+)(.*)$/, 4),
1040
- opt_style(color(:green), /^(.+)(\|\s+\d+\s+)(\++)(.*)$/, 3)
1041
- ]
1042
- 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
1043
1073
  end
1044
1074
 
1045
1075
  def rebase(flag = nil, opts = [], sync: invoked_sync?('rebase', flag), commit: nil, upstream: nil, branch: nil,
@@ -1083,7 +1113,11 @@ module Squared
1083
1113
  opts << 'all' if flag == :all || option('all')
1084
1114
  append_pull(opts, collect_hash(OPT_GIT[:fetch]), flag: flag, from: :fetch, remote: remote,
1085
1115
  no: collect_hash(OPT_GIT[:no][:fetch]))
1086
- source(sync: sync, **threadargs)
1116
+ if sync
1117
+ source
1118
+ else
1119
+ source(sync: false, **threadargs)
1120
+ end
1087
1121
  end
1088
1122
 
1089
1123
  def clone(*, sync: invoked_sync?('clone'), **)
@@ -1122,7 +1156,7 @@ module Squared
1122
1156
  option('no-tags') { opts[:'no-tags'] = true }
1123
1157
  opts.delete(:'recurse-submodules') || opts.delete(:'no-recurse-submodules') if append_submodules(from: :clone)
1124
1158
  append_hash opts
1125
- cmd << '--quiet' if quiet? || option('quiet')
1159
+ cmd << '--quiet' if option('quiet') || !verbose
1126
1160
  append_value(data[0], path, delim: true)
1127
1161
  source(sync: sync, banner: sync && !quiet?, multiple: !sync || quiet?)
1128
1162
  end
@@ -1186,15 +1220,19 @@ module Squared
1186
1220
  op.clear
1187
1221
  out, banner, from = source(io: true)
1188
1222
  print_item banner
1189
- list_result(write_lines(out), 'objects', banner: banner, from: from)
1223
+ list_result(write_lines(out), 'objects', from: from)
1190
1224
  return
1191
1225
  end
1192
1226
  else
1193
1227
  git_session('stash', 'push', opts: opts)
1194
- 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)
1195
1229
  append_message
1196
1230
  end
1197
- source(sync: sync, banner: !quiet?, **threadargs)
1231
+ if sync
1232
+ source(banner: !quiet?)
1233
+ else
1234
+ source(sync: false, banner: !quiet?, **threadargs)
1235
+ end
1198
1236
  end
1199
1237
 
1200
1238
  def status(flag = nil, opts = [])
@@ -1207,12 +1245,10 @@ module Squared
1207
1245
  cmd << '--branch' if option('branch')
1208
1246
  option('ignore-submodules', ignore: false) do |val|
1209
1247
  cmd << basic_option('ignore-submodules', case val
1210
- when '0', 'none'
1248
+ when 'none', '0', 'false'
1211
1249
  'none'
1212
- when '1', 'untracked'
1213
- 'untracked'
1214
- when '2', 'dirty'
1215
- 'dirty'
1250
+ when 'untracked', 'dirty'
1251
+ val
1216
1252
  else
1217
1253
  'all'
1218
1254
  end)
@@ -1235,10 +1271,10 @@ module Squared
1235
1271
  end
1236
1272
  out, banner, from = source(io: true)
1237
1273
  ret = write_lines(out, banner: banner, sub: sub)
1238
- list_result(ret, 'files', action: 'modified', banner: banner, from: from)
1274
+ list_result(ret, 'files', action: 'modified', from: from)
1239
1275
  end
1240
1276
 
1241
- def revbuild(flag = nil, opts = [], sync: nil, **kwargs)
1277
+ def revbuild(flag = nil, opts = [], **kwargs)
1242
1278
  kw = lambda do
1243
1279
  {
1244
1280
  include: relativepath(*Array(kwargs[:include]), all: true),
@@ -1256,7 +1292,7 @@ module Squared
1256
1292
  sha = git_spawn('rev-parse --verify HEAD').chomp
1257
1293
  return if sha.empty?
1258
1294
 
1259
- sync = invoked_sync?('revbuild', flag) if sync.nil?
1295
+ sync = kwargs.fetch(:sync) { invoked_sync?('revbuild', flag) }
1260
1296
  kwargs = kwargs.key?(:include) || kwargs.key?(:exclude) ? kw.call : @revbuild || {}
1261
1297
  case flag
1262
1298
  when :build
@@ -1279,11 +1315,11 @@ module Squared
1279
1315
  end
1280
1316
  end
1281
1317
  start = time_epoch
1282
- build(*@output, sync: sync, from: :'git:revbuild')
1283
- rescue StandardError => e
1318
+ build(*@output, sync: sync, from: symjoin('git', 'revbuild'))
1319
+ rescue => e
1284
1320
  print_error(e, pass: true)
1285
1321
  else
1286
- print_status('revbuild', subject: name, start: start, from: :completed)
1322
+ print_status('revbuild', subject: name, start: start)
1287
1323
  workspace.rev_write(name, { 'revision' => sha, 'files' => status_digest(*args, **kwargs) },
1288
1324
  sync: sync, utc: 'build')
1289
1325
  end
@@ -1337,9 +1373,9 @@ module Squared
1337
1373
  cmd << '-m' if merge
1338
1374
  cmd << '--detach' << commit
1339
1375
  else
1340
- list = OPT_GIT[:checkout] + OPT_GIT[:log][:diff_context]
1341
- op = OptionPartition.new(opts, list, cmd, project: self, no: OPT_GIT[:no][:checkout],
1342
- first: (matchpathspec if flag == :path))
1376
+ op = OptionPartition.new(opts, OPT_GIT[:checkout] + OPT_GIT[:log][:diff_context], cmd,
1377
+ project: self, no: OPT_GIT[:no][:checkout],
1378
+ first: (matchpathspec if flag == :path))
1343
1379
  if flag == :path
1344
1380
  append_head
1345
1381
  append_pathspec(op.extras, pass: false)
@@ -1376,18 +1412,20 @@ module Squared
1376
1412
  out, banner, from = source(io: true)
1377
1413
  print_item banner
1378
1414
  ret = write_lines(out, grep: op.extras)
1379
- list_result(ret, 'tags', grep: op.extras, banner: banner, from: from)
1415
+ list_result(ret, 'tags', grep: op.extras, from: from)
1380
1416
  return
1381
1417
  end
1382
- remote ||= option('remote')
1383
- source.tap { |ret| git_spawn('push', ('-d' if flag == :delete), remote, *refs.quote!) if ret && remote }
1418
+ if (ret = source) && (remote ||= option('remote'))
1419
+ git_spawn('push', ('-d' if flag == :delete), remote, *refs.quote!)
1420
+ end
1421
+ ret
1384
1422
  end
1385
1423
 
1386
1424
  def log!(flag, opts = [], range: [], index: [], grep: [])
1387
1425
  cmd, opts = git_session('log', opts: opts)
1388
- op = OptionPartition.new(opts, collect_hash(OPT_GIT[:log]), cmd,
1389
- project: self, no: collect_hash(OPT_GIT[:no][:log]),
1390
- first: matchpathspec)
1426
+ op = OptionPartition.new(opts, collect_hash(OPT_GIT[:log]), cmd, project: self,
1427
+ no: collect_hash(OPT_GIT[:no][:log]),
1428
+ first: matchpathspec)
1391
1429
  case flag
1392
1430
  when :between, :contain
1393
1431
  op.add_quote(range.join(flag == :between ? '..' : '...'))
@@ -1400,7 +1438,6 @@ module Squared
1400
1438
  append_pathspec op.extras
1401
1439
  source(exception: false)
1402
1440
  end
1403
- alias log_ log!
1404
1441
 
1405
1442
  def diff(flag, opts = [], refs: [], branch: nil, range: [], index: [])
1406
1443
  cmd, opts = git_session('diff', opts: opts)
@@ -1485,7 +1522,7 @@ module Squared
1485
1522
  format = '%(if)%(HEAD)%(then)%(refname:short)...%(upstream:short)...%(upstream:track)%(end)'
1486
1523
  git_spawn 'fetch --no-tags --quiet'
1487
1524
  foreachref('heads', format: format).each do |line|
1488
- next if (line = line.chomp).empty?
1525
+ next if line.empty?
1489
1526
 
1490
1527
  branch, origin, hint = line.split('...')
1491
1528
  if hint && !hint.match?(/^\[(\D+0,\D+0)\]$/)
@@ -1530,7 +1567,7 @@ module Squared
1530
1567
  cached = git_spawn 'diff --cached --name-only --no-color'
1531
1568
  if amend || !cached.empty? || dryrun?
1532
1569
  if adding.empty? && !cached.empty? && banner?
1533
- puts(cached.lines(chomp: true).map! { |val| "cached #{shell_quote(val)}" })
1570
+ puts(cached.lines(chomp: true).map { |val| "cached #{shell_quote(val)}" })
1534
1571
  end
1535
1572
  source co
1536
1573
  source pu
@@ -1602,7 +1639,7 @@ module Squared
1602
1639
  end
1603
1640
  when :delete
1604
1641
  remote&.each { |val| source git_output('push --delete', *val.split('/', 2).quote!) }
1605
- force, list = refs.partition { |val| val.start_with?(/[~^]/) }
1642
+ force, list = refs.partition { |val| val.start_with?('~', '^') }
1606
1643
  force.each do |val|
1607
1644
  r = '-r' if val.delete!('~')
1608
1645
  source git_output('branch', val.delete!('^') ? '-D' : '-d', r, shell_quote(val))
@@ -1632,7 +1669,7 @@ module Squared
1632
1669
  opt_style(color(:green), /^(\*\s+)(\S+)(.*)$/, 2),
1633
1670
  opt_style(color(:red), %r{^(\s*)(remotes/\S+)(.*)$}, 2)
1634
1671
  ])
1635
- list_result(ret, 'branches', banner: banner, from: from)
1672
+ list_result(ret, 'branches', from: from)
1636
1673
  return
1637
1674
  else
1638
1675
  if (head = git_spawn('rev-parse --abbrev-ref HEAD').chomp).empty?
@@ -1641,7 +1678,7 @@ module Squared
1641
1678
  git_spawn 'fetch --all --prune --quiet' if option('sync')
1642
1679
  cmd << '-vv --no-abbrev --list'
1643
1680
  out, banner, from = source(io: true)
1644
- first = from_base?('branch')
1681
+ first = workspace.size > 1
1645
1682
  grep = first ? [/^\*\s+#{Regexp.escape(head)}\s/] : []
1646
1683
  ret = write_lines(out, grep: grep, banner: banner, first: first) do |line, index|
1647
1684
  next line if stdin?
@@ -1709,12 +1746,14 @@ module Squared
1709
1746
  op << '--recursive' if option('r', 'recursive')
1710
1747
  op.splice(path: true)
1711
1748
  end
1712
- source.tap { |ret| success?(ret, flag == :branch) }
1749
+ success?(source, flag == :branch)
1713
1750
  end
1714
1751
 
1715
1752
  def restore(flag, opts = [], commit: nil, files: nil)
1716
1753
  cmd, opts = git_session('restore', shell_option(flag, commit, escape: false, force: false), opts: opts)
1717
- op = OptionPartition.new(opts, OPT_GIT[:restore] + OPT_GIT[:log][:diff_context], cmd,
1754
+ op = OptionPartition.new(opts,
1755
+ OPT_GIT[:restore] + OPT_GIT[:log][:diff_context],
1756
+ cmd,
1718
1757
  project: self, no: OPT_GIT[:no][:restore], first: matchpathspec)
1719
1758
  append_pathspec(op.extras + (files || []), pass: false)
1720
1759
  source(sync: false, stderr: true)
@@ -1727,7 +1766,7 @@ module Squared
1727
1766
  cmd << '--textconv'
1728
1767
  append_value(files.flat_map { |val| Dir[val] }
1729
1768
  .select { |val| projectpath?(val) }
1730
- .map! { |val| shell_quote("HEAD:#{val}") })
1769
+ .map { |val| shell_quote("HEAD:#{val}") })
1731
1770
  source(banner: false)
1732
1771
  return
1733
1772
  when :oneline
@@ -1742,9 +1781,8 @@ module Squared
1742
1781
  opts << format if format
1743
1782
  end
1744
1783
  list = OPT_GIT[:show] + OPT_GIT[:diff][:show] + OPT_GIT[:log][:diff] + OPT_GIT[:log][:diff_context]
1745
- op = OptionPartition.new(opts, list, cmd,
1746
- project: self,
1747
- no: OPT_GIT[:no][:show] + collect_hash(OPT_GIT[:no][:log], pass: [:base]))
1784
+ op = OptionPartition.new(opts, list, cmd, project: self, pass: [:base],
1785
+ no: OPT_GIT[:no][:show] + collect_hash(OPT_GIT[:no][:log]))
1748
1786
  op.append(delim: true)
1749
1787
  source(exception: false, banner: flag != :oneline)
1750
1788
  end
@@ -1756,8 +1794,13 @@ module Squared
1756
1794
  cmd << (size.to_i.zero? ? '--verify' : basic_option('short', [size.to_i, 5].max))
1757
1795
  append_commit(ref, head: true)
1758
1796
  when :branch
1759
- cmd << '--abbrev-ref'
1760
- append_commit(ref, head: true)
1797
+ cmd << '--abbrev-ref' << '--symbolic-full-name' << shell_quote(case ref
1798
+ when 'upstream', 'push'
1799
+ "@{#{ref}}"
1800
+ else
1801
+ ref
1802
+ end)
1803
+ return git_spawn(cmd, exception: false).chomp
1761
1804
  when :output
1762
1805
  if opts.delete('sq-quote')
1763
1806
  cmd << '--sq-quote'
@@ -1769,6 +1812,20 @@ module Squared
1769
1812
  source(banner: verbose?)
1770
1813
  end
1771
1814
 
1815
+ def sparse_checkout(flag, opts = [])
1816
+ cmd, opts = git_session('sparse-checkout', flag, opts: opts)
1817
+ op = OptionPartition.new(opts, OPT_GIT[:sparse_checkout].fetch(flag, []), cmd,
1818
+ project: self, no: OPT_GIT[:no][:sparse_checkout][flag])
1819
+ case flag
1820
+ when :add
1821
+ append_pathspec(op.detach, expect: true, resolve: false, pass: false)
1822
+ when :clean
1823
+ op << '--dry-run' unless op.arg?('n', 'f', 'force')
1824
+ end
1825
+ op.clear
1826
+ success?(source, flag != :list)
1827
+ end
1828
+
1772
1829
  def ls_remote(flag, opts = [], remote: nil)
1773
1830
  cmd, opts = git_session('ls-remote --refs', opts: opts)
1774
1831
  cmd << "--#{flag}" unless flag == :remote
@@ -1777,7 +1834,7 @@ module Squared
1777
1834
  out, banner, from = source(io: true)
1778
1835
  print_item banner
1779
1836
  ret = write_lines(out, grep: op.extras, prefix: "refs/#{flag}/")
1780
- list_result(ret, flag.to_s, grep: op.extras, banner: banner, from: from)
1837
+ list_result(ret, flag.to_s, grep: op.extras, from: from)
1781
1838
  end
1782
1839
 
1783
1840
  def ls_files(flag, opts = [])
@@ -1787,7 +1844,7 @@ module Squared
1787
1844
  out, banner, from = source(io: true)
1788
1845
  print_item banner
1789
1846
  ret = write_lines(out, grep: op.extras)
1790
- list_result(ret, 'files', grep: op.extras, banner: banner, from: from)
1847
+ list_result(ret, 'files', grep: op.extras, from: from)
1791
1848
  end
1792
1849
 
1793
1850
  def git(flag, opts = [])
@@ -1798,10 +1855,12 @@ module Squared
1798
1855
  list.concat(OPT_GIT[:log][:diff_context])
1799
1856
  when :revert
1800
1857
  list.concat(VAL_GIT[:rebase][:send])
1858
+ when :'sparse-checkout'
1859
+ cmd << 'set'
1801
1860
  end
1802
1861
  op = OptionPartition.new(opts, list, cmd, project: self, no: OPT_GIT[:no][flag], single: /\A\d+\z/,
1803
1862
  first: case flag
1804
- when :blame, :revert then nil
1863
+ when :blame, :revert, :'sparse-checkout' then nil
1805
1864
  else matchpathspec
1806
1865
  end)
1807
1866
  case flag
@@ -1822,12 +1881,12 @@ module Squared
1822
1881
  grep, pathspec = op.partition { |val| OptionPartition.pattern?(val) }
1823
1882
  unless grep.empty? && !pathspec.empty?
1824
1883
  grep.map! { |val| Regexp.new(val[1..-2]) }
1825
- files = []
1826
- status_data.each do |a, b|
1884
+ files = status_data.map do |a, b|
1827
1885
  next if b.strip.empty? || (!grep.empty? && grep.none? { |pat| pat.match?(a) })
1828
1886
 
1829
- files << "#{sub_style(b, color(:red))} #{a}"
1887
+ "#{sub_style(b, color(:red))} #{a}"
1830
1888
  end
1889
+ .compact
1831
1890
  unless files.empty?
1832
1891
  files = choice_index('Select files', files, multiple: true, trim: /^\S+\s/,
1833
1892
  accept: [accept_y('Add?')])
@@ -1836,23 +1895,17 @@ module Squared
1836
1895
  end
1837
1896
  end
1838
1897
  return source(git_session('status -s'), banner: false) unless append_pathspec(op.extras)
1839
- return success?(source) if flag == :add && !op.arg?('verbose')
1840
1898
  when :mv
1841
1899
  refs = projectmap op.extras
1842
1900
  raise_error 'no source/destination' unless refs.size > 1
1843
1901
  op.merge(refs)
1844
- when :rm, :clean
1845
- append_pathspec(op.extras, expect: flag == :rm)
1902
+ when :rm, :clean, :'sparse-checkout'
1903
+ append_pathspec(op.extras, expect: flag != :clean, resolve: flag != :'sparse-checkout')
1846
1904
  when :grep
1847
- delim = false
1848
1905
  op.each do |val|
1849
- if val == '--'
1850
- delim = true
1851
- op.delim
1852
- elsif delim
1906
+ if op.include?('--')
1853
1907
  op.add_path(val)
1854
1908
  elsif op.exist?(val, glob: true)
1855
- delim = true
1856
1909
  op.delim
1857
1910
  .add_path(val)
1858
1911
  else
@@ -1864,7 +1917,7 @@ module Squared
1864
1917
  when :revert, :mv, :rm
1865
1918
  source(sync: false, stderr: true)
1866
1919
  else
1867
- source
1920
+ success?(source, flag == :'sparse-checkout' || (flag == :add && !op.arg?('verbose')))
1868
1921
  end
1869
1922
  end
1870
1923
 
@@ -1883,31 +1936,28 @@ module Squared
1883
1936
  private
1884
1937
 
1885
1938
  def source(cmd = @session, exception: true, io: false, sync: true, stdout: false, stderr: false, banner: true,
1886
- multiple: false, hint: nil, from: nil, send: :system, **kwargs)
1887
- unless cmd
1888
- print_error('no git session started', subject: project, hint: from, pass: true)
1889
- return
1890
- end
1939
+ multiple: false, hint: nil, from: nil, timeout: nil, send: :system, **kwargs)
1891
1940
  cmd = cmd.target if cmd.is_a?(OptionPartition)
1892
1941
  if io && banner == false
1893
1942
  from = nil
1894
1943
  banner = nil
1895
- args = false
1896
1944
  else
1945
+ if banner
1946
+ banner = nil unless banner? && !multiple
1947
+ args = true
1948
+ end
1897
1949
  if from == false
1898
1950
  from = nil
1899
1951
  elsif !from && cmd.respond_to?(:drop)
1900
1952
  from = cmd.drop(1).find { |val| val.match?(/\A[a-z]{1,2}[a-z-]*\z/) }
1901
- from &&= :"git:#{from}"
1902
- end
1903
- if banner
1904
- banner = cmd.temp { |val| val.start_with?(/--(work-tree|git-dir)/) } if cmd.respond_to?(:temp)
1905
- args = true
1953
+ from &&= symjoin 'git', from
1906
1954
  end
1955
+ banner &&= cmd.temp { |val| val.start_with?(/--(work-tree|git-dir)/) } if cmd.respond_to?(:temp)
1907
1956
  end
1957
+ timeout = session_timeout cmd if timeout.nil?
1908
1958
  cmd = session_done cmd
1909
- log&.info cmd unless args == false
1910
- banner = if banner && banner? && !multiple
1959
+ log&.info cmd
1960
+ banner = if banner
1911
1961
  format_banner(banner.is_a?(String) ? banner : cmd, hint: hint, strip: true)
1912
1962
  end
1913
1963
  on :first, from
@@ -1918,7 +1968,11 @@ module Squared
1918
1968
  return args ? [IO.popen(cmd), banner || '', from] : IO.popen(cmd)
1919
1969
  elsif stdin? ? sync : stdout
1920
1970
  print_item banner unless multiple
1921
- ret = `#{cmd}`.chomp
1971
+ ret = if stdin? && timeout.to_f > 0
1972
+ Timeout.timeout(timeout) { `#{cmd}` }
1973
+ else
1974
+ `#{cmd}`
1975
+ end.chomp
1922
1976
  raise(ret.empty? ? $?.to_s : ret) unless $?.success?
1923
1977
 
1924
1978
  if ret.empty?
@@ -1928,25 +1982,17 @@ module Squared
1928
1982
  end
1929
1983
  elsif !kwargs[:sub] && (sync || (!exception && !stderr))
1930
1984
  print_item banner unless multiple
1931
- ret = shell(cmd, name: send, exception: exception)
1985
+ ret = shell_t(cmd, name: send, exception: exception, timeout: timeout)
1986
+ elsif timeout.to_f > 0
1987
+ Timeout.timeout(timeout) { run_e(cmd, stderr: stderr, banner: banner, **kwargs) }
1932
1988
  else
1933
- require 'open3'
1934
- if stderr
1935
- Open3.popen3(cmd) do |_, out, err|
1936
- n = write_lines(out, banner: banner, pass: true, **kwargs)
1937
- if n == 0
1938
- n = write_lines(err, banner: banner)
1939
- success?(n == 0, n == 0 && !banner.nil?)
1940
- else
1941
- write_lines(err, loglevel: Logger::DEBUG)
1942
- end
1943
- end
1944
- else
1945
- Open3.popen2e(cmd) { |_, out| write_lines(out, banner: banner, **kwargs) }
1946
- end
1989
+ run_e(cmd, stderr: stderr, banner: banner, **kwargs)
1947
1990
  end
1948
- rescue StandardError => e
1949
- on_error(e, from, pass: true)
1991
+ rescue Timeout::Error => e
1992
+ print_error(Logger::ERROR, cmd, subject: name, hint: e)
1993
+ exit 1 unless exception?(Logger::DEBUG, Logger::INFO)
1994
+ rescue => e
1995
+ on_error(e, pass: true)
1950
1996
  nil
1951
1997
  else
1952
1998
  on :last, from
@@ -1954,42 +2000,10 @@ module Squared
1954
2000
  end
1955
2001
  end
1956
2002
 
1957
- def write_lines(data, grep: [], prefix: nil, sub: nil, banner: nil, loglevel: nil, pass: false, first: false)
1958
- grep = (matchmap(grep, prefix) unless grep.empty?)
1959
- sub = (as_a sub unless stdin?)
1960
- ret = 0
1961
- out = []
1962
- data.each do |line|
1963
- next if grep&.none? { |pat| pat.match?(line) }
1964
- next if block_given? && !(line = yield(line, ret))
1965
-
1966
- if loglevel
1967
- log&.add loglevel, line
1968
- else
1969
- sub&.each { |h| sub_style!(line, **h) }
1970
- if banner
1971
- out << line
1972
- else
1973
- puts line
1974
- end
1975
- end
1976
- ret += 1
1977
- break if first
1978
- end
1979
- if banner && (ret > 0 || (!pass && !first))
1980
- if banner.empty?
1981
- print_item(*out)
1982
- else
1983
- print_item(banner, *out)
1984
- end
1985
- end
1986
- ret
1987
- end
1988
-
1989
- def list_result(size, type, action: 'found', grep: [], banner: nil, from: nil)
2003
+ def list_result(size, type, action: 'found', grep: [], from: nil)
1990
2004
  if size == 0
1991
2005
  puts empty_status("No #{type} were #{action}", 'grep', grep.join(', '))
1992
- elsif stdout? && (banner.nil? || (banner.is_a?(String) && !banner.empty?))
2006
+ elsif stdout?
1993
2007
  styles = theme.fetch(:banner, []).reject { |s| s.to_s.end_with?('!') }
1994
2008
  styles << :bold if styles.size <= 1
1995
2009
  puts print_footer("#{size} #{size == 1 ? type.sub(/(?:(?<!l)e)?s\z/, '') : type}",
@@ -2021,7 +2035,7 @@ module Squared
2021
2035
  ret = choice_index('Choose a commit', git_spawn(cmd, stdout: false), column: /^(\S+)/, force: force, **kwargs)
2022
2036
  case ret
2023
2037
  when Array
2024
- ret.map!(&:stripstyle)
2038
+ ret.map(&:stripstyle)
2025
2039
  when String
2026
2040
  ret.stripstyle
2027
2041
  else
@@ -2039,24 +2053,18 @@ module Squared
2039
2053
  algorithm ||= Digest::SHA256
2040
2054
  glob = kwargs.fetch(:include, [])
2041
2055
  pass = kwargs.fetch(:exclude, [])
2042
- ret = {}
2043
- status_data(*args).each do |file,|
2056
+ status_data(*args).map(&:first).each_with_object({}) do |file, out|
2044
2057
  next if !glob.empty? && glob.none? { |val| File.fnmatch?(val, file, File::FNM_DOTMATCH) }
2045
2058
  next if pass.any? { |val| File.fnmatch?(val, file, File::FNM_DOTMATCH) }
2046
2059
 
2047
- ret[file] = algorithm.hexdigest(File.read(basepath(file)))
2060
+ out[file] = algorithm.hexdigest(File.read(basepath(file)))
2048
2061
  end
2049
- ret
2050
2062
  end
2051
2063
 
2052
2064
  def status_data(*args)
2053
- ret = []
2054
- git_spawn('status -z -uall', *args).split("\x0").each do |line|
2055
- next unless line =~ /^(.)(.) (.+)$/
2056
-
2057
- ret << [$3, $2, $1]
2065
+ git_spawn('status -z -uall', *args).split("\x0").each_with_object([]) do |line, out|
2066
+ out << [$3, $2, $1] if line =~ /^(.)(.) (.+)$/
2058
2067
  end
2059
- ret
2060
2068
  end
2061
2069
 
2062
2070
  def append_pull(opts, list, flag:, from:, target: @session, no: nil, remote: nil)
@@ -2064,9 +2072,8 @@ module Squared
2064
2072
  append_submodules(target: target, from: from)
2065
2073
  return if !remote && opts.empty?
2066
2074
 
2067
- refspec = []
2068
2075
  op = OptionPartition.new(opts, remote ? list + ['refspec=v'] : list, target, project: self, no: no)
2069
- op.each do |opt|
2076
+ refspec = op.each_with_object([]) do |opt, out|
2070
2077
  if opt =~ op.values
2071
2078
  case $1
2072
2079
  when 'rebase'
@@ -2077,7 +2084,7 @@ module Squared
2077
2084
  when 'recurse-submodules'
2078
2085
  op.append?($1, $2, type: :basic)
2079
2086
  when 'refspec'
2080
- refspec << shell_quote($2)
2087
+ out << shell_quote($2)
2081
2088
  end
2082
2089
  elsif op.arg?('multiple')
2083
2090
  op.found << opt
@@ -2087,9 +2094,9 @@ module Squared
2087
2094
  end
2088
2095
  op << '--verbose' if (flag || from == :fetch) && stdout? && !op.arg?('quiet')
2089
2096
  if remote
2090
- op.append(remote)
2097
+ op.append(remote, delim: true)
2091
2098
  if (val = option('refspec', target: target, strict: true))
2092
- op.append(*split_escape(val))
2099
+ op.append(split_escape(val))
2093
2100
  else
2094
2101
  op.merge(refspec)
2095
2102
  end
@@ -2103,27 +2110,31 @@ module Squared
2103
2110
  op.clear(errors: true, subject: flag) if flag
2104
2111
  end
2105
2112
 
2106
- def append_commit(*val, target: @session, head: false)
2107
- val.compact!
2108
- if !val.empty?
2109
- val.each { |ref| target << (commithash(ref) || shell_quote(ref)) }
2113
+ def append_commit(*refs, target: @session, head: false)
2114
+ refs.compact!
2115
+ if !refs.empty?
2116
+ target.merge(refs.map { |val| commithash(val) || shell_quote(val) })
2110
2117
  elsif head
2111
2118
  target << (append_head(target: target) || 'HEAD')
2112
2119
  end
2113
2120
  end
2114
2121
 
2115
- def append_pathspec(files = [], target: @session, expect: false, parent: false, pass: true)
2122
+ def append_pathspec(files = [], target: @session, expect: false, **kwargs)
2116
2123
  if session_arg?('pathspec-from-file', target: target)
2117
- option_clear files
2124
+ OptionPartition.clear(target, files, styles: theme[:inline])
2118
2125
  true
2119
2126
  else
2120
2127
  option('pathspec', target: target) { |val| files = split_escape(val) } if files.empty?
2121
- files = projectmap(files, parent: parent, pass: pass)
2128
+ files = projectmap(files, **kwargs)
2122
2129
  if !files.empty?
2123
2130
  target << '--' << files.join(' ')
2124
2131
  true
2125
2132
  elsif expect
2126
- raise_error(parent ? 'pathspec not present' : 'pathspec not within worktree')
2133
+ raise_error(if expect.is_a?(String)
2134
+ expect
2135
+ else
2136
+ kwargs[:parent] ? 'pathspec not present' : 'pathspec not within worktree'
2137
+ end)
2127
2138
  else
2128
2139
  false
2129
2140
  end
@@ -2132,7 +2143,9 @@ module Squared
2132
2143
 
2133
2144
  def append_message(val = nil, target: @session)
2134
2145
  val = messageopt if val.to_s.empty?
2135
- target << quote_option('message', val) if val
2146
+ return unless val
2147
+
2148
+ target << quote_option('message', val)
2136
2149
  end
2137
2150
 
2138
2151
  def append_head(val = nil, target: @session)
@@ -2165,11 +2178,15 @@ module Squared
2165
2178
  end
2166
2179
  end
2167
2180
 
2168
- def foreachref(path, *args, format: nil)
2169
- path = Array(path).map! { |val| "refs/#{val}" }
2181
+ def foreachref(path, *args, format: nil, chomp: true)
2182
+ path = Array(path).map { |val| "refs/#{val}" }
2170
2183
  format &&= quote_option('format', format)
2171
2184
  ret = git_spawn('for-each-ref', format, *args, *path, stdout: workspace.windows?)
2172
- ret.is_a?(String) ? ret.lines : ret
2185
+ if ret.is_a?(String)
2186
+ ret.lines(chomp: chomp)
2187
+ else
2188
+ chomp ? ret.readlines(chomp: chomp) : ret
2189
+ end
2173
2190
  end
2174
2191
 
2175
2192
  def git_session(*cmd, opts: nil, worktree: true, **kwargs)
@@ -2202,9 +2219,9 @@ module Squared
2202
2219
  end
2203
2220
 
2204
2221
  def quiet?(*, target: @session, **)
2205
- return silent? unless target
2222
+ return false unless target
2206
2223
 
2207
- silent? || target.include?('--quiet') || (target.include?('-q') && target.first.stripext == 'git')
2224
+ target.include?('--quiet') || (target.include?('-q') && target.first.stripext == 'git')
2208
2225
  end
2209
2226
 
2210
2227
  def gitpath(*args)
@@ -2216,7 +2233,9 @@ module Squared
2216
2233
  raise_error(ArgumentError, "missing #{origin ? 'branch' : 'remote'} name", hint: origin)
2217
2234
  end
2218
2235
  branch = "#{branch}:#{origin[i.succ..-1]}" unless origin.end_with?("/#{branch}")
2219
- [origin[0..i.pred], branch].tap { |ret| ret.quote! if quote }
2236
+ ret = [origin[0..i.pred], branch]
2237
+ ret.quote! if quote
2238
+ ret
2220
2239
  end
2221
2240
 
2222
2241
  def commithash(val)
@@ -2241,16 +2260,22 @@ module Squared
2241
2260
  [/\A[^a-z\d-]+/i, %r{\A[^=\\/*]*[\\/*]}, /\A--\z/]
2242
2261
  end
2243
2262
 
2263
+ def matchfile(file, pat)
2264
+ file.read[pat, 0] if file.exist?
2265
+ rescue
2266
+ nil
2267
+ end
2268
+
2244
2269
  def messageopt
2245
2270
  option('message', 'm', prefix: 'git', ignore: false)
2246
2271
  end
2247
2272
 
2248
2273
  def threadargs
2249
- { stderr: true, exception: exception || !workspace.series.multiple? }
2274
+ { stderr: true, exception: exception? || !workspace.series.multiple? }
2250
2275
  end
2251
2276
  end
2252
2277
 
2253
- Application.implement Git
2278
+ Application.implement Git, base: 1
2254
2279
  end
2255
2280
  end
2256
2281
  end