squared 0.6.17 → 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,8 @@ 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
+ log&.debug e
144
145
  warn log_warn(e, pass: true) if warning
145
146
  ensure
146
147
  @revlock = false
@@ -168,28 +169,29 @@ module Squared
168
169
  }.freeze,
169
170
  fetch: {
170
171
  base: %w[multiple porcelain progress P|prune-tags refetch stdin u|update-head-ok
171
- recurse-submodules-default=b?].freeze,
172
+ recurse-submodules-default=b].freeze,
172
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
173
174
  unshallow update-shallow v|verbose deepen=i depth=i j|jobs=i negotiation-tip=q recurse-submodules=v
174
175
  refmap=q o|server-option=q shallow-exclude=b shallow-since=v upload-pack=q].freeze
175
176
  }.freeze,
176
177
  git: {
177
- add: %w[N|intent-to-add].freeze,
178
+ add: %w[N|intent-to-add refresh].freeze,
178
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
179
180
  p|porcelain root score-debug f|show-name e|show-email n|show-number show-stats abbrev=i contents=p
180
- 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,
181
182
  clean: %w[d x X f|force n|dry-run i|interactive q|quiet e|exclude=q].freeze,
182
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
183
184
  l|files-with-matches L|files-without-match F|fixed-strings full-name W|function-context heading
184
185
  i|ignore-case v|invert-match n|line-number name-only no-index not z|null o|only-matching or
185
186
  P|perl-regexp q|quiet recurse-submodules p|show-function a|text untracked w|word-regexp
186
- 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
187
188
  open-files-in-pager=b threads=n].freeze,
188
189
  mv: %w[k f|force n|dry-run v|verbose].freeze,
189
190
  revert: %w[e S=bm? n|no-commit reference cleanup=b gpg-sign=b? m|mainline=i s|signoff strategy=b
190
191
  X|strategy-option=b].freeze,
191
192
  rm: %w[r cached f|force n|dry-run ignore-unmatch pathspec-file-nul q|quiet sparse
192
- 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
193
195
  }.freeze,
194
196
  log: {
195
197
  base: %w[L=qm all all-match alternate-refs author-date-order basic-regexp bisect boundary cherry cherry-mark
@@ -241,6 +243,10 @@ module Squared
241
243
  show-object-format=b? since=q tags=q? until=q].freeze,
242
244
  show: %w[t combined-all-paths no-diff-merges remerge-diff show-notes-by-default show-signature diff-merges=b
243
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,
244
250
  stash: {
245
251
  common: %w[q|quiet].freeze,
246
252
  push: %w[a|all u|include-untracked k|keep-index no-keep-index no-include-untracked pathspec-file-nul p|patch
@@ -253,7 +259,7 @@ module Squared
253
259
  submodule: {
254
260
  status: %w[cached recursive].freeze,
255
261
  update: %w[checkout f|force init merge N|no-fetch no-recommend-shallow no-single-branch recommend-shallow
256
- 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,
257
263
  branch: %w[b|branch d|default].freeze,
258
264
  sync: %w[recursive].freeze
259
265
  }.freeze,
@@ -286,6 +292,10 @@ module Squared
286
292
  rev_parse: %w[flags].freeze,
287
293
  revert: %w[edit gpg-sign rerere-autoupdate].freeze,
288
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,
289
299
  status: %w[ahead-behind column renames].freeze,
290
300
  switch: %w[guess progress recurse-submodules track].freeze,
291
301
  tag: %w[column].freeze
@@ -319,13 +329,13 @@ module Squared
319
329
  end
320
330
 
321
331
  subtasks({
322
- 'branch' => %i[create track delete move copy list all current].freeze,
332
+ 'branch' => %i[create track delete move copy list current].freeze,
323
333
  'checkout' => %i[commit branch track detach path].freeze,
324
334
  'commit' => %i[add all amend amend-orig fixup].freeze,
325
335
  'diff' => %i[head branch files view between contain].freeze,
326
336
  'fetch' => %i[origin remote all].freeze,
327
337
  'files' => %i[cached modified deleted others].freeze,
328
- '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,
329
339
  'log' => %i[view grep between contain].freeze,
330
340
  'merge' => %i[commit no-commit send].freeze,
331
341
  'pull' => %i[origin remote all].freeze,
@@ -333,8 +343,9 @@ module Squared
333
343
  'refs' => %i[heads tags remote].freeze,
334
344
  'reset' => %i[commit index patch mode undo].freeze,
335
345
  'restore' => %i[source staged worktree].freeze,
336
- 'rev' => %i[commit build output].freeze,
346
+ 'rev' => %i[commit branch build output].freeze,
337
347
  'show' => %i[format oneline textconv].freeze,
348
+ 'sparse-checkout' => %i[add reapply list clean disable].freeze,
338
349
  'stash' => %i[push pop apply branch drop clear list all staged worktree].freeze,
339
350
  'submodule' => %i[status update branch url sync].freeze,
340
351
  'switch' => %i[branch create detach].freeze,
@@ -425,7 +436,7 @@ module Squared
425
436
  end
426
437
  if squash
427
438
  found = false
428
- git_spawn('log --format=%h', stdout: false).each do |val|
439
+ git_spawn(git_output('log --format=%h'), stdout: false).each do |val|
429
440
  if found
430
441
  squash = val.chomp
431
442
  break
@@ -478,9 +489,8 @@ module Squared
478
489
  ])
479
490
  choice_remote
480
491
  end
481
- tag(flag, refs: [name], message: message, commit: commit, remote: remote).tap do |ret|
482
- success?(ret, !remote)
483
- end
492
+ ret = tag(flag, refs: [name], message: message, commit: commit, remote: remote)
493
+ success?(ret, !remote)
484
494
  end
485
495
  end
486
496
  when 'stash'
@@ -504,16 +514,15 @@ module Squared
504
514
  args.shift
505
515
  index = choice_commit(multiple: true)
506
516
  else
507
- index = []
508
- args.each do |val|
517
+ index = args.each_with_object([]) do |val, out|
509
518
  if matchhead(val)
510
- index << commithead(val)
519
+ out << commithead(val)
511
520
  elsif (sha = commithash(val))
512
- index << sha
521
+ out << sha
513
522
  elsif val.start_with?('^')
514
- index << shell_quote(val)
523
+ out << shell_quote(val)
515
524
  else
516
- break
525
+ break out
517
526
  end
518
527
  end
519
528
  args = args.drop(index.size)
@@ -546,11 +555,10 @@ module Squared
546
555
  args.shift
547
556
  index = choice_commit(multiple: true)
548
557
  else
549
- index = []
550
- args.each do |val|
551
- 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))
552
560
 
553
- index << sha
561
+ out << sha
554
562
  end
555
563
  args = args.drop(index.size)
556
564
  end
@@ -696,12 +704,12 @@ module Squared
696
704
  format_desc action, flag, '[^~]name*,:?'
697
705
  task flag do |_, args|
698
706
  refs = args.to_a
699
- if refs.empty? || (r = refs.last == ':')
707
+ if refs.empty? || (i = refs.last == ':')
700
708
  accept = ['Delete?']
701
- accept << accept_b('Force?') unless r
702
- 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,
703
711
  accept: accept)
704
- if r
712
+ if i
705
713
  refs.pop
706
714
  else
707
715
  refs = remote.first
@@ -716,7 +724,12 @@ module Squared
716
724
  task flag do |_, args|
717
725
  branch flag, args.to_a
718
726
  end
719
- when :move, :copy
727
+ when :current
728
+ format_desc action, flag
729
+ task flag do
730
+ branch flag
731
+ end
732
+ else
720
733
  format_desc action, flag, 'branch,oldbranch?'
721
734
  task flag, [:branch, :oldbranch] do |_, args|
722
735
  if (branch = args.branch)
@@ -727,11 +740,6 @@ module Squared
727
740
  end
728
741
  branch(flag, refs: [oldbranch, branch])
729
742
  end
730
- else
731
- format_desc action, flag
732
- task flag do
733
- branch flag
734
- end
735
743
  end
736
744
  when 'switch'
737
745
  case flag
@@ -881,6 +889,12 @@ module Squared
881
889
  end
882
890
  rev_parse(flag, ref: ref, size: size)
883
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
884
898
  when :build
885
899
  next unless build?
886
900
 
@@ -926,20 +940,28 @@ module Squared
926
940
  task flag do |_, args|
927
941
  args = args.to_a
928
942
  if args.empty? || args.last == ':'
929
- files = []
930
- 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
931
946
  unless files.empty?
932
947
  files = choice_index('Select a file', files, multiple: true, force: false,
933
948
  accept: 'Restore?')
934
949
  end
935
950
  args.pop
936
- args, glob = args.partition { |val| val.match?(/^(?:[a-z-]+=|[^*]+$)/) }
951
+ args, glob = args.partition { |val| val.match?(/^([a-z-]+=|[^*]+$)/) }
937
952
  files.concat(glob)
938
953
  next if args.empty? && files.empty?
939
954
  end
940
955
  restore(flag, args, files: files)
941
956
  end
942
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
943
965
  when 'git'
944
966
  before = case flag
945
967
  when :blame
@@ -954,6 +976,8 @@ module Squared
954
976
  'pathspec*,pattern*'
955
977
  when :grep
956
978
  'tree*,pathspec*'
979
+ when :'sparse-checkout'
980
+ 'dir+'
957
981
  when :clean, :rm, :status
958
982
  'pathspec*'
959
983
  end
@@ -1008,18 +1032,21 @@ module Squared
1008
1032
  printsucc
1009
1033
  end
1010
1034
  op = OptionPartition.new(opts, OPT_GIT[:pull], cmd, project: self, no: OPT_GIT[:no][:pull])
1011
- opts -= op.extras
1012
- reg, neg = matchmap(op, negate: true)
1035
+ reg = if op.empty?
1036
+ []
1037
+ else
1038
+ opts = op.uniq(opts)
1039
+ matchmap op
1040
+ end
1013
1041
  session_done op.target
1014
1042
  heads = []
1015
1043
  cur = nil
1016
1044
  foreachref('heads', format: '%(if)%(HEAD)%(then)* %(end)%(refname:short)').each do |line|
1017
- line.chomp!
1018
1045
  cur ||= line.delete_prefix!('* ')
1019
- heads << line if matchany?(line, reg) && !matchany?(neg, line, empty: false)
1046
+ heads << line if matchany?(reg, line)
1020
1047
  end
1021
1048
  raise_error 'head not found', hint: 'for-each-ref' unless cur
1022
- opts << 'ff-only' if opts.empty? && !option('ff-only', equals: '0')
1049
+ opts << 'ff-only' if opts.empty? && option('ff-only', notequals: '0')
1023
1050
  (heads.dup << cur).each_with_index do |branch, i|
1024
1051
  next unless (i < heads.size && cur != branch) || i == heads.size
1025
1052
 
@@ -1033,12 +1060,16 @@ module Squared
1033
1060
  end
1034
1061
  append_pull(opts, OPT_GIT[:pull] + OPT_GIT[:fetch][:pull],
1035
1062
  flag: flag, from: :pull, remote: remote, no: OPT_GIT[:no][:pull] + OPT_GIT[:no][:fetch][:pull])
1036
- source(sync: sync, sub: if stdout?
1037
- [
1038
- opt_style(color(:red), /^(.+)(\|\s+\d+\s+)([^-]*)(-+)(.*)$/, 4),
1039
- opt_style(color(:green), /^(.+)(\|\s+\d+\s+)(\++)(.*)$/, 3)
1040
- ]
1041
- 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
1042
1073
  end
1043
1074
 
1044
1075
  def rebase(flag = nil, opts = [], sync: invoked_sync?('rebase', flag), commit: nil, upstream: nil, branch: nil,
@@ -1082,7 +1113,11 @@ module Squared
1082
1113
  opts << 'all' if flag == :all || option('all')
1083
1114
  append_pull(opts, collect_hash(OPT_GIT[:fetch]), flag: flag, from: :fetch, remote: remote,
1084
1115
  no: collect_hash(OPT_GIT[:no][:fetch]))
1085
- source(sync: sync, **threadargs)
1116
+ if sync
1117
+ source
1118
+ else
1119
+ source(sync: false, **threadargs)
1120
+ end
1086
1121
  end
1087
1122
 
1088
1123
  def clone(*, sync: invoked_sync?('clone'), **)
@@ -1121,7 +1156,7 @@ module Squared
1121
1156
  option('no-tags') { opts[:'no-tags'] = true }
1122
1157
  opts.delete(:'recurse-submodules') || opts.delete(:'no-recurse-submodules') if append_submodules(from: :clone)
1123
1158
  append_hash opts
1124
- cmd << '--quiet' if quiet? || option('quiet')
1159
+ cmd << '--quiet' if option('quiet') || !verbose
1125
1160
  append_value(data[0], path, delim: true)
1126
1161
  source(sync: sync, banner: sync && !quiet?, multiple: !sync || quiet?)
1127
1162
  end
@@ -1185,15 +1220,19 @@ module Squared
1185
1220
  op.clear
1186
1221
  out, banner, from = source(io: true)
1187
1222
  print_item banner
1188
- list_result(write_lines(out), 'objects', banner: banner, from: from)
1223
+ list_result(write_lines(out), 'objects', from: from)
1189
1224
  return
1190
1225
  end
1191
1226
  else
1192
1227
  git_session('stash', 'push', opts: opts)
1193
- 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)
1194
1229
  append_message
1195
1230
  end
1196
- source(sync: sync, banner: !quiet?, **threadargs)
1231
+ if sync
1232
+ source(banner: !quiet?)
1233
+ else
1234
+ source(sync: false, banner: !quiet?, **threadargs)
1235
+ end
1197
1236
  end
1198
1237
 
1199
1238
  def status(flag = nil, opts = [])
@@ -1206,12 +1245,10 @@ module Squared
1206
1245
  cmd << '--branch' if option('branch')
1207
1246
  option('ignore-submodules', ignore: false) do |val|
1208
1247
  cmd << basic_option('ignore-submodules', case val
1209
- when '0', 'none'
1248
+ when 'none', '0', 'false'
1210
1249
  'none'
1211
- when '1', 'untracked'
1212
- 'untracked'
1213
- when '2', 'dirty'
1214
- 'dirty'
1250
+ when 'untracked', 'dirty'
1251
+ val
1215
1252
  else
1216
1253
  'all'
1217
1254
  end)
@@ -1234,10 +1271,10 @@ module Squared
1234
1271
  end
1235
1272
  out, banner, from = source(io: true)
1236
1273
  ret = write_lines(out, banner: banner, sub: sub)
1237
- list_result(ret, 'files', action: 'modified', banner: banner, from: from)
1274
+ list_result(ret, 'files', action: 'modified', from: from)
1238
1275
  end
1239
1276
 
1240
- def revbuild(flag = nil, opts = [], sync: nil, **kwargs)
1277
+ def revbuild(flag = nil, opts = [], **kwargs)
1241
1278
  kw = lambda do
1242
1279
  {
1243
1280
  include: relativepath(*Array(kwargs[:include]), all: true),
@@ -1255,7 +1292,7 @@ module Squared
1255
1292
  sha = git_spawn('rev-parse --verify HEAD').chomp
1256
1293
  return if sha.empty?
1257
1294
 
1258
- sync = invoked_sync?('revbuild', flag) if sync.nil?
1295
+ sync = kwargs.fetch(:sync) { invoked_sync?('revbuild', flag) }
1259
1296
  kwargs = kwargs.key?(:include) || kwargs.key?(:exclude) ? kw.call : @revbuild || {}
1260
1297
  case flag
1261
1298
  when :build
@@ -1278,11 +1315,11 @@ module Squared
1278
1315
  end
1279
1316
  end
1280
1317
  start = time_epoch
1281
- build(*@output, sync: sync, from: :'git:revbuild')
1282
- rescue StandardError => e
1318
+ build(*@output, sync: sync, from: symjoin('git', 'revbuild'))
1319
+ rescue => e
1283
1320
  print_error(e, pass: true)
1284
1321
  else
1285
- print_status('revbuild', subject: name, start: start, from: :completed)
1322
+ print_status('revbuild', subject: name, start: start)
1286
1323
  workspace.rev_write(name, { 'revision' => sha, 'files' => status_digest(*args, **kwargs) },
1287
1324
  sync: sync, utc: 'build')
1288
1325
  end
@@ -1336,9 +1373,9 @@ module Squared
1336
1373
  cmd << '-m' if merge
1337
1374
  cmd << '--detach' << commit
1338
1375
  else
1339
- list = OPT_GIT[:checkout] + OPT_GIT[:log][:diff_context]
1340
- op = OptionPartition.new(opts, list, cmd, project: self, no: OPT_GIT[:no][:checkout],
1341
- 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))
1342
1379
  if flag == :path
1343
1380
  append_head
1344
1381
  append_pathspec(op.extras, pass: false)
@@ -1375,18 +1412,20 @@ module Squared
1375
1412
  out, banner, from = source(io: true)
1376
1413
  print_item banner
1377
1414
  ret = write_lines(out, grep: op.extras)
1378
- list_result(ret, 'tags', grep: op.extras, banner: banner, from: from)
1415
+ list_result(ret, 'tags', grep: op.extras, from: from)
1379
1416
  return
1380
1417
  end
1381
- remote ||= option('remote')
1382
- 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
1383
1422
  end
1384
1423
 
1385
1424
  def log!(flag, opts = [], range: [], index: [], grep: [])
1386
1425
  cmd, opts = git_session('log', opts: opts)
1387
- op = OptionPartition.new(opts, collect_hash(OPT_GIT[:log]), cmd,
1388
- project: self, no: collect_hash(OPT_GIT[:no][:log]),
1389
- 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)
1390
1429
  case flag
1391
1430
  when :between, :contain
1392
1431
  op.add_quote(range.join(flag == :between ? '..' : '...'))
@@ -1399,7 +1438,6 @@ module Squared
1399
1438
  append_pathspec op.extras
1400
1439
  source(exception: false)
1401
1440
  end
1402
- alias log_ log!
1403
1441
 
1404
1442
  def diff(flag, opts = [], refs: [], branch: nil, range: [], index: [])
1405
1443
  cmd, opts = git_session('diff', opts: opts)
@@ -1484,7 +1522,7 @@ module Squared
1484
1522
  format = '%(if)%(HEAD)%(then)%(refname:short)...%(upstream:short)...%(upstream:track)%(end)'
1485
1523
  git_spawn 'fetch --no-tags --quiet'
1486
1524
  foreachref('heads', format: format).each do |line|
1487
- next if (line = line.chomp).empty?
1525
+ next if line.empty?
1488
1526
 
1489
1527
  branch, origin, hint = line.split('...')
1490
1528
  if hint && !hint.match?(/^\[(\D+0,\D+0)\]$/)
@@ -1529,7 +1567,7 @@ module Squared
1529
1567
  cached = git_spawn 'diff --cached --name-only --no-color'
1530
1568
  if amend || !cached.empty? || dryrun?
1531
1569
  if adding.empty? && !cached.empty? && banner?
1532
- puts(cached.lines(chomp: true).map! { |val| "cached #{shell_quote(val)}" })
1570
+ puts(cached.lines(chomp: true).map { |val| "cached #{shell_quote(val)}" })
1533
1571
  end
1534
1572
  source co
1535
1573
  source pu
@@ -1601,7 +1639,7 @@ module Squared
1601
1639
  end
1602
1640
  when :delete
1603
1641
  remote&.each { |val| source git_output('push --delete', *val.split('/', 2).quote!) }
1604
- force, list = refs.partition { |val| val.start_with?(/[~^]/) }
1642
+ force, list = refs.partition { |val| val.start_with?('~', '^') }
1605
1643
  force.each do |val|
1606
1644
  r = '-r' if val.delete!('~')
1607
1645
  source git_output('branch', val.delete!('^') ? '-D' : '-d', r, shell_quote(val))
@@ -1631,7 +1669,7 @@ module Squared
1631
1669
  opt_style(color(:green), /^(\*\s+)(\S+)(.*)$/, 2),
1632
1670
  opt_style(color(:red), %r{^(\s*)(remotes/\S+)(.*)$}, 2)
1633
1671
  ])
1634
- list_result(ret, 'branches', banner: banner, from: from)
1672
+ list_result(ret, 'branches', from: from)
1635
1673
  return
1636
1674
  else
1637
1675
  if (head = git_spawn('rev-parse --abbrev-ref HEAD').chomp).empty?
@@ -1640,7 +1678,7 @@ module Squared
1640
1678
  git_spawn 'fetch --all --prune --quiet' if option('sync')
1641
1679
  cmd << '-vv --no-abbrev --list'
1642
1680
  out, banner, from = source(io: true)
1643
- first = from_base?('branch')
1681
+ first = workspace.size > 1
1644
1682
  grep = first ? [/^\*\s+#{Regexp.escape(head)}\s/] : []
1645
1683
  ret = write_lines(out, grep: grep, banner: banner, first: first) do |line, index|
1646
1684
  next line if stdin?
@@ -1708,12 +1746,14 @@ module Squared
1708
1746
  op << '--recursive' if option('r', 'recursive')
1709
1747
  op.splice(path: true)
1710
1748
  end
1711
- source.tap { |ret| success?(ret, flag == :branch) }
1749
+ success?(source, flag == :branch)
1712
1750
  end
1713
1751
 
1714
1752
  def restore(flag, opts = [], commit: nil, files: nil)
1715
1753
  cmd, opts = git_session('restore', shell_option(flag, commit, escape: false, force: false), opts: opts)
1716
- 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,
1717
1757
  project: self, no: OPT_GIT[:no][:restore], first: matchpathspec)
1718
1758
  append_pathspec(op.extras + (files || []), pass: false)
1719
1759
  source(sync: false, stderr: true)
@@ -1726,7 +1766,7 @@ module Squared
1726
1766
  cmd << '--textconv'
1727
1767
  append_value(files.flat_map { |val| Dir[val] }
1728
1768
  .select { |val| projectpath?(val) }
1729
- .map! { |val| shell_quote("HEAD:#{val}") })
1769
+ .map { |val| shell_quote("HEAD:#{val}") })
1730
1770
  source(banner: false)
1731
1771
  return
1732
1772
  when :oneline
@@ -1741,9 +1781,8 @@ module Squared
1741
1781
  opts << format if format
1742
1782
  end
1743
1783
  list = OPT_GIT[:show] + OPT_GIT[:diff][:show] + OPT_GIT[:log][:diff] + OPT_GIT[:log][:diff_context]
1744
- op = OptionPartition.new(opts, list, cmd,
1745
- project: self,
1746
- 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]))
1747
1786
  op.append(delim: true)
1748
1787
  source(exception: false, banner: flag != :oneline)
1749
1788
  end
@@ -1755,8 +1794,13 @@ module Squared
1755
1794
  cmd << (size.to_i.zero? ? '--verify' : basic_option('short', [size.to_i, 5].max))
1756
1795
  append_commit(ref, head: true)
1757
1796
  when :branch
1758
- cmd << '--abbrev-ref'
1759
- 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
1760
1804
  when :output
1761
1805
  if opts.delete('sq-quote')
1762
1806
  cmd << '--sq-quote'
@@ -1768,6 +1812,20 @@ module Squared
1768
1812
  source(banner: verbose?)
1769
1813
  end
1770
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
+
1771
1829
  def ls_remote(flag, opts = [], remote: nil)
1772
1830
  cmd, opts = git_session('ls-remote --refs', opts: opts)
1773
1831
  cmd << "--#{flag}" unless flag == :remote
@@ -1776,7 +1834,7 @@ module Squared
1776
1834
  out, banner, from = source(io: true)
1777
1835
  print_item banner
1778
1836
  ret = write_lines(out, grep: op.extras, prefix: "refs/#{flag}/")
1779
- 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)
1780
1838
  end
1781
1839
 
1782
1840
  def ls_files(flag, opts = [])
@@ -1786,7 +1844,7 @@ module Squared
1786
1844
  out, banner, from = source(io: true)
1787
1845
  print_item banner
1788
1846
  ret = write_lines(out, grep: op.extras)
1789
- list_result(ret, 'files', grep: op.extras, banner: banner, from: from)
1847
+ list_result(ret, 'files', grep: op.extras, from: from)
1790
1848
  end
1791
1849
 
1792
1850
  def git(flag, opts = [])
@@ -1797,10 +1855,12 @@ module Squared
1797
1855
  list.concat(OPT_GIT[:log][:diff_context])
1798
1856
  when :revert
1799
1857
  list.concat(VAL_GIT[:rebase][:send])
1858
+ when :'sparse-checkout'
1859
+ cmd << 'set'
1800
1860
  end
1801
1861
  op = OptionPartition.new(opts, list, cmd, project: self, no: OPT_GIT[:no][flag], single: /\A\d+\z/,
1802
1862
  first: case flag
1803
- when :blame, :revert then nil
1863
+ when :blame, :revert, :'sparse-checkout' then nil
1804
1864
  else matchpathspec
1805
1865
  end)
1806
1866
  case flag
@@ -1821,12 +1881,12 @@ module Squared
1821
1881
  grep, pathspec = op.partition { |val| OptionPartition.pattern?(val) }
1822
1882
  unless grep.empty? && !pathspec.empty?
1823
1883
  grep.map! { |val| Regexp.new(val[1..-2]) }
1824
- files = []
1825
- status_data.each do |a, b|
1884
+ files = status_data.map do |a, b|
1826
1885
  next if b.strip.empty? || (!grep.empty? && grep.none? { |pat| pat.match?(a) })
1827
1886
 
1828
- files << "#{sub_style(b, color(:red))} #{a}"
1887
+ "#{sub_style(b, color(:red))} #{a}"
1829
1888
  end
1889
+ .compact
1830
1890
  unless files.empty?
1831
1891
  files = choice_index('Select files', files, multiple: true, trim: /^\S+\s/,
1832
1892
  accept: [accept_y('Add?')])
@@ -1835,23 +1895,17 @@ module Squared
1835
1895
  end
1836
1896
  end
1837
1897
  return source(git_session('status -s'), banner: false) unless append_pathspec(op.extras)
1838
- return success?(source) if flag == :add && !op.arg?('verbose')
1839
1898
  when :mv
1840
1899
  refs = projectmap op.extras
1841
1900
  raise_error 'no source/destination' unless refs.size > 1
1842
1901
  op.merge(refs)
1843
- when :rm, :clean
1844
- 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')
1845
1904
  when :grep
1846
- delim = false
1847
1905
  op.each do |val|
1848
- if val == '--'
1849
- delim = true
1850
- op.delim
1851
- elsif delim
1906
+ if op.include?('--')
1852
1907
  op.add_path(val)
1853
1908
  elsif op.exist?(val, glob: true)
1854
- delim = true
1855
1909
  op.delim
1856
1910
  .add_path(val)
1857
1911
  else
@@ -1863,7 +1917,7 @@ module Squared
1863
1917
  when :revert, :mv, :rm
1864
1918
  source(sync: false, stderr: true)
1865
1919
  else
1866
- source
1920
+ success?(source, flag == :'sparse-checkout' || (flag == :add && !op.arg?('verbose')))
1867
1921
  end
1868
1922
  end
1869
1923
 
@@ -1882,31 +1936,28 @@ module Squared
1882
1936
  private
1883
1937
 
1884
1938
  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)
1886
- unless cmd
1887
- print_error('no git session started', subject: project, hint: from, pass: true)
1888
- return
1889
- end
1939
+ multiple: false, hint: nil, from: nil, timeout: nil, send: :system, **kwargs)
1890
1940
  cmd = cmd.target if cmd.is_a?(OptionPartition)
1891
1941
  if io && banner == false
1892
1942
  from = nil
1893
1943
  banner = nil
1894
- args = false
1895
1944
  else
1945
+ if banner
1946
+ banner = nil unless banner? && !multiple
1947
+ args = true
1948
+ end
1896
1949
  if from == false
1897
1950
  from = nil
1898
1951
  elsif !from && cmd.respond_to?(:drop)
1899
1952
  from = cmd.drop(1).find { |val| val.match?(/\A[a-z]{1,2}[a-z-]*\z/) }
1900
- from &&= :"git:#{from}"
1901
- end
1902
- if banner
1903
- banner = cmd.temp { |val| val.start_with?(/--(work-tree|git-dir)/) } if cmd.respond_to?(:temp)
1904
- args = true
1953
+ from &&= symjoin 'git', from
1905
1954
  end
1955
+ banner &&= cmd.temp { |val| val.start_with?(/--(work-tree|git-dir)/) } if cmd.respond_to?(:temp)
1906
1956
  end
1957
+ timeout = session_timeout cmd if timeout.nil?
1907
1958
  cmd = session_done cmd
1908
- log&.info cmd unless args == false
1909
- banner = if banner && banner? && !multiple
1959
+ log&.info cmd
1960
+ banner = if banner
1910
1961
  format_banner(banner.is_a?(String) ? banner : cmd, hint: hint, strip: true)
1911
1962
  end
1912
1963
  on :first, from
@@ -1917,7 +1968,11 @@ module Squared
1917
1968
  return args ? [IO.popen(cmd), banner || '', from] : IO.popen(cmd)
1918
1969
  elsif stdin? ? sync : stdout
1919
1970
  print_item banner unless multiple
1920
- ret = `#{cmd}`.chomp
1971
+ ret = if stdin? && timeout.to_f > 0
1972
+ Timeout.timeout(timeout) { `#{cmd}` }
1973
+ else
1974
+ `#{cmd}`
1975
+ end.chomp
1921
1976
  raise(ret.empty? ? $?.to_s : ret) unless $?.success?
1922
1977
 
1923
1978
  if ret.empty?
@@ -1927,25 +1982,17 @@ module Squared
1927
1982
  end
1928
1983
  elsif !kwargs[:sub] && (sync || (!exception && !stderr))
1929
1984
  print_item banner unless multiple
1930
- 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) }
1931
1988
  else
1932
- require 'open3'
1933
- if stderr
1934
- Open3.popen3(cmd) do |_, out, err|
1935
- n = write_lines(out, banner: banner, pass: true, **kwargs)
1936
- if n == 0
1937
- n = write_lines(err, banner: banner)
1938
- success?(n == 0, n == 0 && !banner.nil?)
1939
- else
1940
- write_lines(err, loglevel: Logger::DEBUG)
1941
- end
1942
- end
1943
- else
1944
- Open3.popen2e(cmd) { |_, out| write_lines(out, banner: banner, **kwargs) }
1945
- end
1989
+ run_e(cmd, stderr: stderr, banner: banner, **kwargs)
1946
1990
  end
1947
- rescue StandardError => e
1948
- 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)
1949
1996
  nil
1950
1997
  else
1951
1998
  on :last, from
@@ -1953,42 +2000,10 @@ module Squared
1953
2000
  end
1954
2001
  end
1955
2002
 
1956
- def write_lines(data, grep: [], prefix: nil, sub: nil, banner: nil, loglevel: nil, pass: false, first: false)
1957
- grep = (matchmap(grep, prefix) unless grep.empty?)
1958
- sub = (as_a sub unless stdin?)
1959
- ret = 0
1960
- out = []
1961
- data.each do |line|
1962
- next if grep&.none? { |pat| pat.match?(line) }
1963
- next if block_given? && !(line = yield(line, ret))
1964
-
1965
- if loglevel
1966
- log&.add loglevel, line
1967
- else
1968
- sub&.each { |h| sub_style!(line, **h) }
1969
- if banner
1970
- out << line
1971
- else
1972
- puts line
1973
- end
1974
- end
1975
- ret += 1
1976
- break if first
1977
- end
1978
- if banner && (ret > 0 || (!pass && !first))
1979
- if banner.empty?
1980
- print_item(*out)
1981
- else
1982
- print_item(banner, *out)
1983
- end
1984
- end
1985
- ret
1986
- end
1987
-
1988
- def list_result(size, type, action: 'found', grep: [], banner: nil, from: nil)
2003
+ def list_result(size, type, action: 'found', grep: [], from: nil)
1989
2004
  if size == 0
1990
2005
  puts empty_status("No #{type} were #{action}", 'grep', grep.join(', '))
1991
- elsif stdout? && (banner.nil? || (banner.is_a?(String) && !banner.empty?))
2006
+ elsif stdout?
1992
2007
  styles = theme.fetch(:banner, []).reject { |s| s.to_s.end_with?('!') }
1993
2008
  styles << :bold if styles.size <= 1
1994
2009
  puts print_footer("#{size} #{size == 1 ? type.sub(/(?:(?<!l)e)?s\z/, '') : type}",
@@ -2020,7 +2035,7 @@ module Squared
2020
2035
  ret = choice_index('Choose a commit', git_spawn(cmd, stdout: false), column: /^(\S+)/, force: force, **kwargs)
2021
2036
  case ret
2022
2037
  when Array
2023
- ret.map { |val| val&.stripstyle }
2038
+ ret.map(&:stripstyle)
2024
2039
  when String
2025
2040
  ret.stripstyle
2026
2041
  else
@@ -2038,24 +2053,18 @@ module Squared
2038
2053
  algorithm ||= Digest::SHA256
2039
2054
  glob = kwargs.fetch(:include, [])
2040
2055
  pass = kwargs.fetch(:exclude, [])
2041
- ret = {}
2042
- status_data(*args).each do |file,|
2056
+ status_data(*args).map(&:first).each_with_object({}) do |file, out|
2043
2057
  next if !glob.empty? && glob.none? { |val| File.fnmatch?(val, file, File::FNM_DOTMATCH) }
2044
2058
  next if pass.any? { |val| File.fnmatch?(val, file, File::FNM_DOTMATCH) }
2045
2059
 
2046
- ret[file] = algorithm.hexdigest(File.read(basepath(file)))
2060
+ out[file] = algorithm.hexdigest(File.read(basepath(file)))
2047
2061
  end
2048
- ret
2049
2062
  end
2050
2063
 
2051
2064
  def status_data(*args)
2052
- ret = []
2053
- git_spawn('status -z -uall', *args).split("\x0").each do |line|
2054
- next unless line =~ /^(.)(.) (.+)$/
2055
-
2056
- 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 =~ /^(.)(.) (.+)$/
2057
2067
  end
2058
- ret
2059
2068
  end
2060
2069
 
2061
2070
  def append_pull(opts, list, flag:, from:, target: @session, no: nil, remote: nil)
@@ -2063,9 +2072,8 @@ module Squared
2063
2072
  append_submodules(target: target, from: from)
2064
2073
  return if !remote && opts.empty?
2065
2074
 
2066
- refspec = []
2067
2075
  op = OptionPartition.new(opts, remote ? list + ['refspec=v'] : list, target, project: self, no: no)
2068
- op.each do |opt|
2076
+ refspec = op.each_with_object([]) do |opt, out|
2069
2077
  if opt =~ op.values
2070
2078
  case $1
2071
2079
  when 'rebase'
@@ -2076,7 +2084,7 @@ module Squared
2076
2084
  when 'recurse-submodules'
2077
2085
  op.append?($1, $2, type: :basic)
2078
2086
  when 'refspec'
2079
- refspec << shell_quote($2)
2087
+ out << shell_quote($2)
2080
2088
  end
2081
2089
  elsif op.arg?('multiple')
2082
2090
  op.found << opt
@@ -2086,9 +2094,9 @@ module Squared
2086
2094
  end
2087
2095
  op << '--verbose' if (flag || from == :fetch) && stdout? && !op.arg?('quiet')
2088
2096
  if remote
2089
- op.append(remote)
2097
+ op.append(remote, delim: true)
2090
2098
  if (val = option('refspec', target: target, strict: true))
2091
- op.append(*split_escape(val))
2099
+ op.append(split_escape(val))
2092
2100
  else
2093
2101
  op.merge(refspec)
2094
2102
  end
@@ -2102,27 +2110,31 @@ module Squared
2102
2110
  op.clear(errors: true, subject: flag) if flag
2103
2111
  end
2104
2112
 
2105
- def append_commit(*val, target: @session, head: false)
2106
- val.compact!
2107
- if !val.empty?
2108
- 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) })
2109
2117
  elsif head
2110
2118
  target << (append_head(target: target) || 'HEAD')
2111
2119
  end
2112
2120
  end
2113
2121
 
2114
- def append_pathspec(files = [], target: @session, expect: false, parent: false, pass: true)
2122
+ def append_pathspec(files = [], target: @session, expect: false, **kwargs)
2115
2123
  if session_arg?('pathspec-from-file', target: target)
2116
- option_clear files
2124
+ OptionPartition.clear(target, files, styles: theme[:inline])
2117
2125
  true
2118
2126
  else
2119
2127
  option('pathspec', target: target) { |val| files = split_escape(val) } if files.empty?
2120
- files = projectmap(files, parent: parent, pass: pass)
2128
+ files = projectmap(files, **kwargs)
2121
2129
  if !files.empty?
2122
2130
  target << '--' << files.join(' ')
2123
2131
  true
2124
2132
  elsif expect
2125
- 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)
2126
2138
  else
2127
2139
  false
2128
2140
  end
@@ -2131,7 +2143,9 @@ module Squared
2131
2143
 
2132
2144
  def append_message(val = nil, target: @session)
2133
2145
  val = messageopt if val.to_s.empty?
2134
- target << quote_option('message', val) if val
2146
+ return unless val
2147
+
2148
+ target << quote_option('message', val)
2135
2149
  end
2136
2150
 
2137
2151
  def append_head(val = nil, target: @session)
@@ -2164,11 +2178,15 @@ module Squared
2164
2178
  end
2165
2179
  end
2166
2180
 
2167
- def foreachref(path, *args, format: nil)
2168
- path = Array(path).map! { |val| "refs/#{val}" }
2181
+ def foreachref(path, *args, format: nil, chomp: true)
2182
+ path = Array(path).map { |val| "refs/#{val}" }
2169
2183
  format &&= quote_option('format', format)
2170
2184
  ret = git_spawn('for-each-ref', format, *args, *path, stdout: workspace.windows?)
2171
- 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
2172
2190
  end
2173
2191
 
2174
2192
  def git_session(*cmd, opts: nil, worktree: true, **kwargs)
@@ -2196,10 +2214,14 @@ module Squared
2196
2214
  stdout: stdout, banner: banner, **kwargs)
2197
2215
  end
2198
2216
 
2217
+ def dryrun?(*, target: @session, prefix: target&.first)
2218
+ Array(target).include?('--dry-run') || !option('dry-run', target: target, prefix: prefix).nil?
2219
+ end
2220
+
2199
2221
  def quiet?(*, target: @session, **)
2200
- return silent? unless target
2222
+ return false unless target
2201
2223
 
2202
- silent? || target.include?('--quiet') || (target.include?('-q') && target.first.stripext == 'git')
2224
+ target.include?('--quiet') || (target.include?('-q') && target.first.stripext == 'git')
2203
2225
  end
2204
2226
 
2205
2227
  def gitpath(*args)
@@ -2211,7 +2233,9 @@ module Squared
2211
2233
  raise_error(ArgumentError, "missing #{origin ? 'branch' : 'remote'} name", hint: origin)
2212
2234
  end
2213
2235
  branch = "#{branch}:#{origin[i.succ..-1]}" unless origin.end_with?("/#{branch}")
2214
- [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
2215
2239
  end
2216
2240
 
2217
2241
  def commithash(val)
@@ -2236,16 +2260,22 @@ module Squared
2236
2260
  [/\A[^a-z\d-]+/i, %r{\A[^=\\/*]*[\\/*]}, /\A--\z/]
2237
2261
  end
2238
2262
 
2263
+ def matchfile(file, pat)
2264
+ file.read[pat, 0] if file.exist?
2265
+ rescue
2266
+ nil
2267
+ end
2268
+
2239
2269
  def messageopt
2240
2270
  option('message', 'm', prefix: 'git', ignore: false)
2241
2271
  end
2242
2272
 
2243
2273
  def threadargs
2244
- { stderr: true, exception: exception || !workspace.series.multiple? }
2274
+ { stderr: true, exception: exception? || !workspace.series.multiple? }
2245
2275
  end
2246
2276
  end
2247
2277
 
2248
- Application.implement Git
2278
+ Application.implement Git, base: 1
2249
2279
  end
2250
2280
  end
2251
2281
  end