squared 0.5.3 → 0.5.4

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.
@@ -4,7 +4,7 @@ module Squared
4
4
  module Workspace
5
5
  module Git
6
6
  GIT_REPO = Support.hashobj
7
- GIT_PROTO = %r{^(?:https?|ssh|git|file)://}i.freeze
7
+ GIT_PROTO = %r{\A(https?|ssh|git|file)://}i.freeze
8
8
  private_constant :GIT_REPO, :GIT_PROTO
9
9
 
10
10
  attr_reader :revfile
@@ -247,7 +247,17 @@ module Squared
247
247
  pop: %w[index].freeze,
248
248
  apply: %w[index].freeze
249
249
  }.freeze,
250
- status: %w[u|ignore-submodules=bm? ignored=b? untracked-files=b?],
250
+ status: %w[z u=bm? b|branch long s|short show-stash v|verbose column=b find-renames=i? ignore-submodules=b?
251
+ ignored=b? porcelain=b? untracked-files=b?].freeze,
252
+ submodule: {
253
+ status: %w[cached recursive].freeze,
254
+ update: %w[checkout f|force init merge N|no-fetch no-recommend-shallow no-single-branch recommend-shallow
255
+ rebase recursive remote single-branch depth=i filter=q jobs=i reference=b ref-format=q].freeze,
256
+ branch: %w[b|branch d|default].freeze,
257
+ sync: %w[recursive].freeze
258
+ }.freeze,
259
+ switch: %w[d|detach discard-changes f|force ignore-other-worktrees m|merge q|quiet conflict=b c|create=q
260
+ C|force-create=q orphan=q t|track=b].freeze,
251
261
  tag: %w[n=im cleanup=b create-reflog i|ignore-case omit-empty color=b? column=b contains=b? format=q merged=b?
252
262
  no-contains=b? no-merged=b? points-at=q sort=q trailer=q].freeze,
253
263
  no: {
@@ -273,6 +283,8 @@ module Squared
273
283
  rev_parse: %w[flags].freeze,
274
284
  revert: %w[edit gpg-sign rerere-autoupdate].freeze,
275
285
  show: %w[standard-notes].freeze,
286
+ status: %w[ahead-behind column renames].freeze,
287
+ switch: %w[guess progress recurse-submodules track].freeze,
276
288
  tag: %w[column].freeze
277
289
  }.freeze
278
290
  }.freeze
@@ -294,10 +306,8 @@ module Squared
294
306
  def populate(ws, **)
295
307
  return if ws.series.exclude?(:pull, true)
296
308
 
297
- namespace(name = ws.task_name('git')) do
298
- all = ws.task_join(name, 'all')
299
-
300
- ws.format_desc(all, 'stash|rebase|autostash?,depend?')
309
+ namespace(ws.task_name('git')) do |ns|
310
+ ws.format_desc(all = ws.task_join(ns.scope.path, 'all'), 'stash|rebase|autostash?,depend?')
301
311
  task 'all' do |_, args|
302
312
  args = args.to_a
303
313
  cmd = if args.include?('stash')
@@ -314,6 +324,7 @@ module Squared
314
324
  cmd << ws.task_sync('build')
315
325
  Common::Utils.task_invoke(*cmd, **ws.invokeargs)
316
326
  end
327
+
317
328
  ws.series.sync << all
318
329
  ws.series.multiple << all
319
330
  end
@@ -335,25 +346,27 @@ module Squared
335
346
  'checkout' => %i[commit branch track detach path].freeze,
336
347
  'commit' => %i[add all amend amend-orig fixup].freeze,
337
348
  'diff' => %i[head branch files view between contain].freeze,
338
- 'fetch' => %i[origin remote].freeze,
349
+ 'fetch' => %i[origin remote all].freeze,
339
350
  'files' => %i[cached modified deleted others].freeze,
340
- 'git' => %i[add blame clean mv revert rm].freeze,
351
+ 'git' => %i[add blame clean mv revert rm status].freeze,
341
352
  'log' => %i[view between contain].freeze,
342
353
  'merge' => %i[commit no-commit send].freeze,
343
- 'pull' => %i[origin remote].freeze,
354
+ 'pull' => %i[origin remote all].freeze,
344
355
  'rebase' => %i[branch onto send].freeze,
345
356
  'refs' => %i[heads tags remote].freeze,
346
- 'reset' => %i[commit index patch mode].freeze,
357
+ 'reset' => %i[commit index patch mode undo].freeze,
347
358
  'restore' => %i[source staged worktree].freeze,
348
359
  'rev' => %i[commit build output].freeze,
349
360
  'show' => %i[format oneline textconv].freeze,
350
361
  'stash' => %i[push pop apply branch drop clear list].freeze,
351
- 'switch' => %i[create detach merge].freeze,
362
+ 'submodule' => %i[status update branch url sync].freeze,
363
+ 'switch' => %i[branch create detach].freeze,
352
364
  'tag' => %i[add sign delete list].freeze
353
365
  })
354
366
 
355
367
  def initialize(*, **)
356
368
  super
369
+ @submodule = basepath('.gitmodules').exist?
357
370
  initialize_ref Git.ref if gitpath.exist?
358
371
  end
359
372
 
@@ -363,7 +376,7 @@ module Squared
363
376
 
364
377
  def populate(*, **)
365
378
  super
366
- return unless ref?(Git.ref)
379
+ return unless ref?(Git.ref) || @only
367
380
 
368
381
  namespace name do
369
382
  Git.subtasks do |action, flags|
@@ -385,11 +398,34 @@ module Squared
385
398
  __send__(action, flag, args, remote: remote)
386
399
  end
387
400
  else
388
- format_desc action, flag, 'opts*'
401
+ format_desc(action, flag, 'opts*', after: flag == :all && action == 'pull' ? 'pattern*' : nil)
389
402
  task flag do |_, args|
390
403
  __send__ action, flag, args.to_a
391
404
  end
392
405
  end
406
+ when 'submodule'
407
+ break unless @submodule
408
+
409
+ case flag
410
+ when :branch
411
+ format_desc action, flag, 'path,opts*'
412
+ task flag, [:path] do |_, args|
413
+ path = param_guard(action, flag, args: args, key: :path)
414
+ submodule(flag, args.extras, path: path)
415
+ end
416
+ when :url
417
+ format_desc action, flag, 'path,url,opts*'
418
+ task flag, [:path, :url] do |_, args|
419
+ path = param_guard(action, flag, args: args, key: :path)
420
+ url = param_guard(action, flag, args: args, key: :url)
421
+ submodule(flag, args.extras, path: path, url: url)
422
+ end
423
+ else
424
+ format_desc action, flag, 'opts*,path*'
425
+ task flag do |_, args|
426
+ submodule flag, args.to_a
427
+ end
428
+ end
393
429
  when 'commit'
394
430
  case flag
395
431
  when :all
@@ -629,10 +665,7 @@ module Squared
629
665
  task flag, [:upstream, :name] do |_, args|
630
666
  if (ref = args.upstream)
631
667
  target = args.name
632
- if ref.start_with?('~')
633
- ref = ref[1..-1]
634
- remote = true
635
- end
668
+ remote = true if ref.delete_prefix!('~')
636
669
  else
637
670
  ref, remote, target = choice_refs('Choose a remote', 'remotes', accept: [['Push?', true]],
638
671
  values: ['Enter branch name'])
@@ -685,10 +718,10 @@ module Squared
685
718
  when :create
686
719
  format_desc action, flag, '(^)name,ref?=HEAD|:'
687
720
  task flag, [:name, :commit] do |_, args|
688
- target = param_guard(action, flag, args: args, key: :name)
721
+ branch = param_guard(action, flag, args: args, key: :name)
689
722
  commit = commithead args.commit
690
723
  commit, track = choice_commit(values: ['Track? [Y|n]'], force: false) if commit == ':'
691
- switch(flag, target: target, commit: commit, track: track)
724
+ switch(flag, branch: branch, commit: commit, track: track)
692
725
  end
693
726
  when :detach
694
727
  format_desc action, flag, 'ref?=HEAD'
@@ -696,11 +729,16 @@ module Squared
696
729
  commit = commithead(args.commit) || choice_commit(force: false)
697
730
  switch(flag, commit: commit)
698
731
  end
699
- when :merge
700
- format_desc action, flag, 'branch?'
701
- task flag, [:branch] do |_, args|
702
- commit = args.branch || choice_refs('Choose a branch')
703
- switch(flag, commit: commit)
732
+ when :branch
733
+ format_desc action, flag, 'name|:,opts*'
734
+ task flag, [:name] do |_, args|
735
+ if (branch = args.name)
736
+ args = args.extras
737
+ branch = nil if branch == ':'
738
+ else
739
+ args = []
740
+ end
741
+ switch(flag, args, branch: branch || choice_refs('Choose a branch'))
704
742
  end
705
743
  end
706
744
  when 'reset'
@@ -721,10 +759,10 @@ module Squared
721
759
  end
722
760
  print_success if success?(reset(flag, args, commit: commit))
723
761
  end
724
- when :index
725
- format_desc action, flag, 'opts*,pathspec*'
762
+ when :index, :undo
763
+ format_desc(action, flag, flag == :index ? 'opts*,pathspec*' : nil)
726
764
  task flag do |_, args|
727
- reset flag, args.to_a
765
+ reset(flag, flag == :index ? args.to_a : [])
728
766
  end
729
767
  when :mode
730
768
  format_desc action, flag, 'mode,ref?=HEAD|:'
@@ -823,7 +861,7 @@ module Squared
823
861
  rev_parse(flag, ref: ref, size: size)
824
862
  end
825
863
  when :build
826
- format_desc action, flag, OPT_GIT[:status]
864
+ format_desc action, flag, 'opts*'
827
865
  task flag do |_, args|
828
866
  revbuild flag, args.to_a
829
867
  end
@@ -840,7 +878,7 @@ module Squared
840
878
  ls_remote(flag, args.extras, remote: args.remote)
841
879
  end
842
880
  else
843
- format_desc action, flag, 'opts*,pattern*'
881
+ format_desc(action, flag, 'opts*,pattern*', after: action == 'files' ? 'pathspec*' : nil)
844
882
  task flag do |_, args|
845
883
  __send__(action == 'refs' ? :ls_remote : :ls_files, flag, args.to_a)
846
884
  end
@@ -885,10 +923,13 @@ module Squared
885
923
  when :mv then 'source+,destination'
886
924
  when :revert then 'commit+' end
887
925
  format_desc(action, flag, 'opts*', before: before, after: case flag
888
- when :add then 'pathspec*,:pattern:*'
889
- when :clean, :rm then 'pathspec*' end)
926
+ when :add
927
+ 'pathspec*,pattern*'
928
+ when :clean, :rm, :status
929
+ 'pathspec*'
930
+ end)
890
931
  task flag do |_, args|
891
- git flag, args.to_a
932
+ __send__(flag == :status ? :status : :git, flag, args.to_a)
892
933
  end
893
934
  end
894
935
  end
@@ -912,11 +953,44 @@ module Squared
912
953
  super
913
954
  end
914
955
 
915
- def pull(flag = nil, opts = [], sync: invoked_sync?('pull', flag), remote: nil)
956
+ def pull(flag = nil, opts = [], sync: invoked_sync?('pull', flag), remote: nil, hint: nil)
916
957
  cmd, opts = git_session('pull', opts: opts)
917
- if flag == :rebase
958
+ cmd << '--autostash' if option('autostash')
959
+ case flag
960
+ when :rebase
918
961
  cmd << '--rebase'
919
- cmd << '--autostash' if option('autostash')
962
+ when :all
963
+ unless git_spawn('status -s -z --untracked-files=all').empty?
964
+ if confirm('Stash local changes?', 'Y')
965
+ git_spawn 'stash push --keep-index --quiet'
966
+ elsif !(force = confirm('Force checkout?', 'N'))
967
+ return
968
+ end
969
+ end
970
+ op = OptionPartition.new(opts, OPT_GIT[:pull], cmd, project: self, no: OPT_GIT[:no][:pull])
971
+ reg = if op.empty?
972
+ []
973
+ else
974
+ opts = opts.reject { |val| op.extras.include?(val) }
975
+ matchmap op
976
+ end
977
+ session_done op.target
978
+ heads = []
979
+ cur = nil
980
+ foreachref('heads', format: '%(if)%(HEAD)%(then)* %(end)%(refname:short)').each do |line|
981
+ line.chomp!
982
+ cur ||= line.delete_prefix!('* ')
983
+ heads << line if matchany?(line, reg)
984
+ end
985
+ raise_error('head not found', hint: 'for-each-ref') unless cur
986
+ opts << 'ff-only' if opts.empty? && !option('ff-only', equals: '0')
987
+ (heads.dup << cur).each_with_index do |branch, index|
988
+ next unless (index < heads.size && cur != branch) || index == heads.size
989
+
990
+ git_spawn 'switch --quiet', force && '--force', shell_quote(branch)
991
+ pull(nil, opts, sync: false, hint: branch) if heads.include?(branch)
992
+ end
993
+ return
920
994
  else
921
995
  cmd << '--autostash' if flag == :autostash
922
996
  option('rebase', ignore: false) do |val|
@@ -935,7 +1009,7 @@ module Squared
935
1009
  { pat: /^(.+)(\|\s+\d+\s+)([^-]*)(-+)(.*)$/, styles: color(:red), index: 4 },
936
1010
  { pat: /^(.+)(\|\s+\d+\s+)(\++)(.*)$/, styles: color(:green), index: 3 }
937
1011
  ]
938
- end, **threadargs)
1012
+ end, hint: hint, **threadargs)
939
1013
  end
940
1014
 
941
1015
  def rebase(flag = nil, opts = [], sync: invoked_sync?('rebase', flag), commit: nil, upstream: nil, branch: nil,
@@ -972,6 +1046,7 @@ module Squared
972
1046
 
973
1047
  def fetch(flag = nil, opts = [], sync: invoked_sync?('fetch', flag), remote: nil)
974
1048
  opts = git_session('fetch', opts: opts).last
1049
+ opts << 'all' if flag == :all || option('all')
975
1050
  append_pull(opts, collect_hash(OPT_GIT[:fetch]), no: collect_hash(OPT_GIT[:no][:fetch]),
976
1051
  remote: remote, flag: flag, from: :fetch)
977
1052
  source(sync: sync, **threadargs)
@@ -1014,15 +1089,15 @@ module Squared
1014
1089
  cmd, opts = git_session('stash', flag, opts: opts)
1015
1090
  list = OPT_GIT[:stash][:common] + OPT_GIT[:stash].fetch(flag, [])
1016
1091
  if flag == :list
1017
- list += collect_hash(OPT_GIT[:log])
1018
- no = collect_hash(OPT_GIT[:no][:log])
1092
+ list += collect_hash OPT_GIT[:log]
1093
+ no = collect_hash OPT_GIT[:no][:log]
1019
1094
  end
1020
1095
  op = OptionPartition.new(opts, list, cmd, project: self, no: no, first: flag == :push ? matchpathspec : nil)
1021
1096
  case flag
1022
1097
  when :push
1023
1098
  append_pathspec op.extras
1024
1099
  when :pop, :apply, :drop, :branch
1025
- if op.extras.delete(':')
1100
+ if op.remove(':')
1026
1101
  if flag == :branch
1027
1102
  if op.empty?
1028
1103
  values = [['Branch name', true]]
@@ -1055,29 +1130,34 @@ module Squared
1055
1130
  end
1056
1131
  else
1057
1132
  git_session('stash', 'push', opts: opts)
1058
- append_option(OPT_GIT[:stash][:push].grep_v(/[=|]/), no: true, ignore: false)
1133
+ append_option(OptionPartition.select(OPT_GIT[:stash][:push], no: false), no: true, ignore: false)
1059
1134
  append_message
1060
1135
  end
1061
1136
  source(banner: !quiet?, sync: sync, **threadargs)
1062
1137
  end
1063
1138
 
1064
- def status(*)
1065
- cmd = git_session 'status'
1066
- cmd << (option('long') ? '--long' : '--short')
1067
- cmd << '--branch' if option('branch')
1068
- option('ignore-submodules', ignore: false) do |val|
1069
- cmd << basic_option('ignore-submodules', case val
1070
- when '0', 'none'
1071
- 'none'
1072
- when '1', 'untracked'
1073
- 'untracked'
1074
- when '2', 'dirty'
1075
- 'dirty'
1076
- else
1077
- 'all'
1078
- end)
1139
+ def status(flag = nil, opts = [])
1140
+ cmd, opts = git_session('status', opts: opts)
1141
+ if flag
1142
+ op = OptionPartition.new(opts, OPT_GIT[:status], cmd, project: self, no: OPT_GIT[:no][:status])
1143
+ append_pathspec op.extras
1144
+ else
1145
+ cmd << (option('long') ? '--long' : '--short')
1146
+ cmd << '--branch' if option('branch')
1147
+ option('ignore-submodules', ignore: false) do |val|
1148
+ cmd << basic_option('ignore-submodules', case val
1149
+ when '0', 'none'
1150
+ 'none'
1151
+ when '1', 'untracked'
1152
+ 'untracked'
1153
+ when '2', 'dirty'
1154
+ 'dirty'
1155
+ else
1156
+ 'all'
1157
+ end)
1158
+ end
1159
+ append_pathspec
1079
1160
  end
1080
- append_pathspec
1081
1161
  if verbose
1082
1162
  r = color(:red)
1083
1163
  g = color(:green)
@@ -1119,7 +1199,7 @@ module Squared
1119
1199
  kwargs = kwargs.key?(:include) || kwargs.key?(:exclude) ? statusargs.call : @revbuild || {}
1120
1200
  case flag
1121
1201
  when :build
1122
- op = OptionPartition.new(opts, OPT_GIT[:status], project: self)
1202
+ op = OptionPartition.new(opts, %w[ignore-submodules=b? ignored=b? untracked-files=b?], project: self)
1123
1203
  op.clear(append: true)
1124
1204
  args = op.to_a
1125
1205
  else
@@ -1176,6 +1256,9 @@ module Squared
1176
1256
  when :patch
1177
1257
  cmd << '--patch'
1178
1258
  append_pathspec(refs, pass: false)
1259
+ when :undo
1260
+ cmd << '--hard HEAD@{1}'
1261
+ ref = false
1179
1262
  end
1180
1263
  unless ref == false
1181
1264
  append_commit(ref, head: true)
@@ -1186,20 +1269,15 @@ module Squared
1186
1269
 
1187
1270
  def checkout(flag, opts = [], branch: nil, origin: nil, create: nil, commit: nil, detach: nil, merge: false)
1188
1271
  cmd, opts = git_session('checkout', opts: opts)
1189
- append_option 'force', 'merge'
1272
+ append_option 'force', 'f', 'merge'
1190
1273
  case flag
1191
1274
  when :branch
1192
1275
  cmd << '--detach' if detach == 'd' || option('detach')
1193
1276
  append_option('track', equals: true)
1194
1277
  cmd << (create ? quote_option(create, branch) : branch) << commit
1195
1278
  when :track
1196
- if branch
1197
- if branch.start_with?('^')
1198
- opt = 'B'
1199
- branch = branch[1..-1]
1200
- end
1201
- cmd << quote_option(opt || 'b', branch)
1202
- end
1279
+
1280
+ cmd << quote_option(branch.delete_prefix!('^') ? 'B' : 'b', branch) if branch
1203
1281
  cmd << '--track' << shell_quote(origin)
1204
1282
  when :detach
1205
1283
  cmd << '-m' if merge
@@ -1228,7 +1306,7 @@ module Squared
1228
1306
  elsif !session_arg?('s', 'sign', 'u', 'local-user')
1229
1307
  cmd << '--annotate'
1230
1308
  end
1231
- cmd << '--force' if option('force')
1309
+ cmd << '--force' if option('force', 'f')
1232
1310
  if !commit && message && (sha = commithash(message))
1233
1311
  commit = sha
1234
1312
  message = nil
@@ -1259,7 +1337,7 @@ module Squared
1259
1337
  first: matchpathspec)
1260
1338
  case flag
1261
1339
  when :between, :contain
1262
- op << shell_quote(range.join(flag == :between ? '..' : '...'))
1340
+ op.add_quote(range.join(flag == :between ? '..' : '...'))
1263
1341
  else
1264
1342
  op.merge(index)
1265
1343
  end
@@ -1288,13 +1366,13 @@ module Squared
1288
1366
  op.merge(range)
1289
1367
  when :between, :contain
1290
1368
  op.delete('--merge-base')
1291
- op << shell_quote(range.join(flag == :between ? '..' : '...'))
1369
+ op.add_quote(range.join(flag == :between ? '..' : '...'))
1292
1370
  else
1293
1371
  op << '--merge-base' if option('merge-base')
1294
- op << shell_quote(branch) if branch
1372
+ op.add_quote(branch) if branch
1295
1373
  if !index.empty?
1296
1374
  if op.arg?('cached')
1297
- raise_error("one commit only: #{index.join(', ')}", hint: '--cached') if index.size > 1
1375
+ raise_error("one commit only: #{index.join(', ')}", hint: 'cached') if index.size > 1
1298
1376
  op << index.first
1299
1377
  else
1300
1378
  op.merge(index)
@@ -1424,13 +1502,13 @@ module Squared
1424
1502
  '--track'
1425
1503
  end
1426
1504
  end
1427
- cmd << '--force' if option('force')
1505
+ cmd << '--force' if option('force', 'f')
1428
1506
  cmd << shell_quote(target)
1429
1507
  cmd << shell_quote(ref) if ref
1430
1508
  when :track
1431
1509
  raise_error('invalid upstream', hint: ref) unless ref.include?('/')
1432
- if ref.start_with?('^')
1433
- cmd << '--unset-upstream' << shell_quote(ref[1..-1])
1510
+ if ref.delete_prefix!('^')
1511
+ cmd << '--unset-upstream' << shell_quote(ref)
1434
1512
  remote = false
1435
1513
  stdout = true
1436
1514
  else
@@ -1439,7 +1517,7 @@ module Squared
1439
1517
  end
1440
1518
  when :delete
1441
1519
  remote&.each do |val|
1442
- source git_output('push', '--delete', *val.split('/', 2).map { |s| shell_quote(s) })
1520
+ source git_output('push --delete', *val.split('/', 2).map { |s| shell_quote(s) })
1443
1521
  end
1444
1522
  force, list = refs.partition { |val| val.start_with?(/[~^]/) }
1445
1523
  force.each do |val|
@@ -1455,7 +1533,7 @@ module Squared
1455
1533
  remote = nil
1456
1534
  when :move, :copy
1457
1535
  flag = "-#{flag.to_s[0]}"
1458
- cmd << (option('force') ? flag.upcase : flag)
1536
+ cmd << (option('force', 'f') ? flag.upcase : flag)
1459
1537
  refs.compact.each { |val| cmd << shell_quote(val) }
1460
1538
  stdout = true
1461
1539
  when :current
@@ -1465,7 +1543,7 @@ module Squared
1465
1543
  when :list
1466
1544
  op = OptionPartition.new(opts, OPT_GIT[:branch], cmd << '--list',
1467
1545
  project: self, no: OPT_GIT[:no][:branch], single: /\Av+\z/)
1468
- op.each { |val| op << shell_quote(val) }
1546
+ op.each { |val| op.add_quote(val) }
1469
1547
  out, banner, from = source(io: true)
1470
1548
  print_item banner
1471
1549
  ret = write_lines(out, sub: [
@@ -1511,33 +1589,47 @@ module Squared
1511
1589
  if !ref
1512
1590
  print_success if flag == :create
1513
1591
  elsif remote && target
1514
- source git_output('push', '-u', shell_quote(ref.split('/', 2).first), shell_quote(target))
1592
+ source git_output('push -u', shell_quote(ref.split('/', 2).first), shell_quote(target))
1515
1593
  end
1516
1594
  end
1517
1595
 
1518
- def switch(flag, opts = [], target: nil, commit: nil, track: nil)
1519
- cmd = git_session('switch', opts: opts).first
1520
- case flag
1521
- when :create
1522
- c = 'c'
1523
- if target.start_with?('^')
1524
- target = target[1..-1]
1525
- c = c.upcase
1596
+ def switch(flag, opts = [], branch: nil, commit: nil, track: nil)
1597
+ cmd, opts = git_session('switch', opts: opts)
1598
+ cmd << '--force' if option('force', 'f')
1599
+ if flag == :branch
1600
+ op = OptionPartition.new(opts, OPT_GIT[:switch], cmd, project: self, no: OPT_GIT[:no][:switch])
1601
+ op.add_quote(branch)
1602
+ else
1603
+ case flag
1604
+ when :create
1605
+ cmd << quote_option(branch.delete_prefix!('^') ? 'C' : 'c', branch)
1606
+ cmd << case (track ||= option('track', ignore: false))
1607
+ when 'n', 'N', '0', 'false'
1608
+ '--no-track'
1609
+ when 'y', 'Y', '1', 'true'
1610
+ '--track'
1611
+ when 'direct', 'inherit'
1612
+ basic_option 'track', track
1613
+ end
1614
+ when :detach
1615
+ cmd << "--#{flag}"
1526
1616
  end
1527
- cmd << quote_option(c, target)
1528
- cmd << case (track ||= option('track', ignore: false))
1529
- when 'n', 'N', '0', 'false'
1530
- '--no-track'
1531
- when 'y', 'Y', '1', 'true'
1532
- '--track'
1533
- when 'direct', 'inherit'
1534
- basic_option 'track', track
1535
- end
1536
- when :detach, :merge
1537
- cmd << "--#{flag}"
1617
+ append_head commit
1618
+ end
1619
+ source
1620
+ end
1621
+
1622
+ def submodule(flag, opts = [], path: nil, url: nil)
1623
+ cmd, opts = git_session('submodule', opts: opts)
1624
+ op = OptionPartition.new(opts, OPT_GIT[:submodule].fetch(flag, []), cmd, project: self)
1625
+ case flag
1626
+ when :branch, :url
1627
+ op << '--'
1628
+ op.add_path(path) if path
1629
+ op.add_quote(url) if url
1630
+ else
1631
+ op.splice(path: true)
1538
1632
  end
1539
- cmd << '--force' if option('force')
1540
- append_head commit
1541
1633
  source
1542
1634
  end
1543
1635
 
@@ -1606,7 +1698,7 @@ module Squared
1606
1698
  cmd, opts = git_session('ls-remote', '--refs', opts: opts)
1607
1699
  cmd << "--#{flag}" unless flag == :remote
1608
1700
  op = OptionPartition.new(opts, OPT_GIT[:ls_remote], cmd, project: self)
1609
- op << shell_quote(remote) if remote
1701
+ op.add_quote(remote) if remote
1610
1702
  out, banner, from = source(io: true)
1611
1703
  print_item banner
1612
1704
  ret = write_lines(out, grep: op.extras, prefix: "refs/#{flag}/")
@@ -1616,6 +1708,7 @@ module Squared
1616
1708
  def ls_files(flag, opts = [])
1617
1709
  cmd, opts = git_session('ls-files', "--#{flag}", opts: opts)
1618
1710
  op = OptionPartition.new(opts, OPT_GIT[:ls_files], cmd, project: self)
1711
+ op.splice(path: true, pattern: true)
1619
1712
  out, banner, from = source(io: true)
1620
1713
  print_item banner
1621
1714
  ret = write_lines(out, grep: op.extras)
@@ -1644,15 +1737,16 @@ module Squared
1644
1737
  end
1645
1738
  when :add, :clean
1646
1739
  if flag == :add && !op.arg?('pathspec-from-file')
1647
- grep, list = op.partition { |val| val.start_with?(':') && val.end_with?(':') }
1740
+ grep, list = op.partition { |val| OptionPartition.pattern?(val) }
1648
1741
  unless grep.empty? && !list.empty?
1649
1742
  grep.map! { |val| Regexp.new(val[1..-2]) }
1650
- files = status_data.map! do |a, b|
1651
- next if b.strip.empty? || (!grep.empty? && grep.none? { |pat| pat.match?(a) })
1743
+ files = [].tap do |out|
1744
+ status_data.each do |a, b|
1745
+ next if b.strip.empty? || (!grep.empty? && grep.none? { |pat| pat.match?(a) })
1652
1746
 
1653
- "#{sub_style(b, styles: color(:red))} #{a}"
1747
+ out << "#{sub_style(b, styles: color(:red))} #{a}"
1748
+ end
1654
1749
  end
1655
- .compact
1656
1750
  unless files.empty?
1657
1751
  files = choice_index('Select files', files, multiple: true, force: true, trim: /^\S+\s/,
1658
1752
  accept: [['Add?', false, true]])
@@ -1660,7 +1754,7 @@ module Squared
1660
1754
  op.swap(list + files)
1661
1755
  end
1662
1756
  end
1663
- return source(git_session('status', '-s'), banner: false) unless append_pathspec(op.extras)
1757
+ return source(git_session('status -s'), banner: false) unless append_pathspec(op.extras)
1664
1758
 
1665
1759
  verbose = flag == :add && !op.arg?('verbose')
1666
1760
  print_success if success?(source) && verbose
@@ -1690,7 +1784,7 @@ module Squared
1690
1784
  private
1691
1785
 
1692
1786
  def source(cmd = @session, exception: true, io: false, sync: true, stdout: false, stderr: false, banner: true,
1693
- multiple: false, from: nil, **kwargs)
1787
+ multiple: false, hint: nil, from: nil, **kwargs)
1694
1788
  cmd = cmd.target if cmd.is_a?(OptionPartition)
1695
1789
  if io && banner == false
1696
1790
  from = nil
@@ -1708,7 +1802,8 @@ module Squared
1708
1802
  cmd = session_done cmd
1709
1803
  log&.info cmd
1710
1804
  banner = if banner
1711
- format_banner((banner.is_a?(String) ? banner : cmd).gsub(File.join(path, ''), ''), banner: true)
1805
+ banner = (banner.is_a?(String) ? banner : cmd).gsub(File.join(path, ''), '')
1806
+ format_banner(hint ? "#{banner} (#{hint})" : banner, banner: true)
1712
1807
  end
1713
1808
  on :first, from
1714
1809
  begin
@@ -1756,16 +1851,7 @@ module Squared
1756
1851
  end
1757
1852
 
1758
1853
  def write_lines(data, grep: [], prefix: nil, sub: nil, banner: nil, loglevel: nil, pass: false, first: false)
1759
- grep = unless grep.empty?
1760
- grep.map do |val|
1761
- if val.is_a?(Regexp)
1762
- val
1763
- else
1764
- val = ".*#{val}" if prefix && !val.sub!(/\A(\^|\\A)/, '')
1765
- Regexp.new("#{prefix}#{val == '*' ? '.+' : val}")
1766
- end
1767
- end
1768
- end
1854
+ grep = grep.empty? ? nil : matchmap(grep, prefix)
1769
1855
  sub = nil if stdin?
1770
1856
  ret = 0
1771
1857
  out = []
@@ -1839,8 +1925,7 @@ module Squared
1839
1925
  glob = kwargs.fetch(:include, [])
1840
1926
  pass = kwargs.fetch(:exclude, [])
1841
1927
  ret = {}
1842
- status_data(*args).each do |line|
1843
- file = line.first
1928
+ status_data(*args).each do |file,|
1844
1929
  next if !glob.empty? && glob.none? { |val| File.fnmatch?(val, file, File::FNM_DOTMATCH) }
1845
1930
  next if !pass.empty? && pass.any? { |val| File.fnmatch?(val, file, File::FNM_DOTMATCH) }
1846
1931
 
@@ -1850,17 +1935,17 @@ module Squared
1850
1935
  end
1851
1936
 
1852
1937
  def status_data(*args)
1853
- ret = []
1854
- git_spawn('status -z -uall', *args).split("\x0").each do |line|
1855
- next unless line =~ /^(.)(.) (.+)$/
1938
+ [].tap do |ret|
1939
+ git_spawn('status -z -uall', *args).split("\x0").each do |line|
1940
+ next unless line =~ /^(.)(.) (.+)$/
1856
1941
 
1857
- ret << [$3, $2, $1]
1942
+ ret << [$3, $2, $1]
1943
+ end
1858
1944
  end
1859
- ret
1860
1945
  end
1861
1946
 
1862
1947
  def append_pull(opts, list, target: @session, flag: nil, no: nil, remote: nil, from: nil)
1863
- target << '--force' if option('force', target: target)
1948
+ target << '--force' if option('force', 'f', target: target)
1864
1949
  append_submodules(target: target, from: from)
1865
1950
  return if !remote && opts.empty?
1866
1951
 
@@ -1918,7 +2003,7 @@ module Squared
1918
2003
  option_clear files
1919
2004
  true
1920
2005
  else
1921
- option('pathspec', target: target) { |val| files = split_escape val } if files.empty?
2006
+ option('pathspec', target: target) { |val| files = split_escape(val) } if files.empty?
1922
2007
  files = projectmap(files, parent: parent, pass: pass)
1923
2008
  if !files.empty?
1924
2009
  target << '--' << files.join(' ')
@@ -1976,12 +2061,11 @@ module Squared
1976
2061
  end
1977
2062
 
1978
2063
  def git_session(*cmd, opts: nil, worktree: true, **kwargs)
1979
- dir = worktree ? ["--work-tree=#{shell_quote(path)}", "--git-dir=#{shell_quote(gitpath)}"] : []
2064
+ dir = worktree ? [quote_option('work-tree', path), quote_option('git-dir', gitpath)] : []
1980
2065
  return session('git', *dir, *cmd, **kwargs) unless opts
1981
2066
 
1982
2067
  op = OptionPartition.new(opts, OPT_GIT[:common], dir, project: self)
1983
- ret = session('git', *op.to_a, *cmd, **kwargs)
1984
- [ret, op.extras]
2068
+ [session('git', *op.to_a, *cmd, **kwargs), op.extras]
1985
2069
  end
1986
2070
 
1987
2071
  def git_output(*cmd, **kwargs)