squared 0.5.2 → 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.
@@ -3,8 +3,8 @@
3
3
  module Squared
4
4
  module Workspace
5
5
  module Git
6
- GIT_REPO = Workspace.hashobj
7
- GIT_PROTO = %r{^(?:https?|ssh|git|file)://}i.freeze
6
+ GIT_REPO = Support.hashobj
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
@@ -181,6 +181,9 @@ module Squared
181
181
  }.freeze,
182
182
  git: {
183
183
  add: %w[N|intent-to-add refresh].freeze,
184
+ 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
185
+ p|porcelain root score-debug f|show-name e|show-email n|show-number show-stats abbrev=i
186
+ contents=p date=q encoding=b ignore-rev=b ignore-revs-file=p reverse=q].freeze,
184
187
  clean: %w[d x X f|force n|dry-run i|interactive q|quiet e|exclude=q].freeze,
185
188
  mv: %w[k f|force n|dry-run v|verbose].freeze,
186
189
  revert: %w[e S=bm? abort continue n|no-commit quit reference skip cleanup=b gpg-sign=b? m|mainline=i
@@ -244,10 +247,21 @@ module Squared
244
247
  pop: %w[index].freeze,
245
248
  apply: %w[index].freeze
246
249
  }.freeze,
247
- 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,
248
261
  tag: %w[n=im cleanup=b create-reflog i|ignore-case omit-empty color=b? column=b contains=b? format=q merged=b?
249
262
  no-contains=b? no-merged=b? points-at=q sort=q trailer=q].freeze,
250
263
  no: {
264
+ blame: %w[progress].freeze,
251
265
  branch: %w[color color-moved column track].freeze,
252
266
  checkout: %w[overwrite-ignore guess overlay progress recurse-submodules track].freeze,
253
267
  fetch: {
@@ -269,6 +283,8 @@ module Squared
269
283
  rev_parse: %w[flags].freeze,
270
284
  revert: %w[edit gpg-sign rerere-autoupdate].freeze,
271
285
  show: %w[standard-notes].freeze,
286
+ status: %w[ahead-behind column renames].freeze,
287
+ switch: %w[guess progress recurse-submodules track].freeze,
272
288
  tag: %w[column].freeze
273
289
  }.freeze
274
290
  }.freeze
@@ -290,10 +306,8 @@ module Squared
290
306
  def populate(ws, **)
291
307
  return if ws.series.exclude?(:pull, true)
292
308
 
293
- namespace(name = ws.task_name('git')) do
294
- all = ws.task_join(name, 'all')
295
-
296
- 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?')
297
311
  task 'all' do |_, args|
298
312
  args = args.to_a
299
313
  cmd = if args.include?('stash')
@@ -310,6 +324,7 @@ module Squared
310
324
  cmd << ws.task_sync('build')
311
325
  Common::Utils.task_invoke(*cmd, **ws.invokeargs)
312
326
  end
327
+
313
328
  ws.series.sync << all
314
329
  ws.series.multiple << all
315
330
  end
@@ -331,25 +346,27 @@ module Squared
331
346
  'checkout' => %i[commit branch track detach path].freeze,
332
347
  'commit' => %i[add all amend amend-orig fixup].freeze,
333
348
  'diff' => %i[head branch files view between contain].freeze,
334
- 'fetch' => %i[origin remote].freeze,
349
+ 'fetch' => %i[origin remote all].freeze,
335
350
  'files' => %i[cached modified deleted others].freeze,
336
- 'git' => %i[add clean mv revert rm].freeze,
351
+ 'git' => %i[add blame clean mv revert rm status].freeze,
337
352
  'log' => %i[view between contain].freeze,
338
353
  'merge' => %i[commit no-commit send].freeze,
339
- 'pull' => %i[origin remote].freeze,
354
+ 'pull' => %i[origin remote all].freeze,
340
355
  'rebase' => %i[branch onto send].freeze,
341
356
  'refs' => %i[heads tags remote].freeze,
342
- 'reset' => %i[commit index patch mode].freeze,
357
+ 'reset' => %i[commit index patch mode undo].freeze,
343
358
  'restore' => %i[source staged worktree].freeze,
344
359
  'rev' => %i[commit build output].freeze,
345
360
  'show' => %i[format oneline textconv].freeze,
346
361
  'stash' => %i[push pop apply branch drop clear list].freeze,
347
- 'switch' => %i[create detach merge].freeze,
362
+ 'submodule' => %i[status update branch url sync].freeze,
363
+ 'switch' => %i[branch create detach].freeze,
348
364
  'tag' => %i[add sign delete list].freeze
349
365
  })
350
366
 
351
367
  def initialize(*, **)
352
368
  super
369
+ @submodule = basepath('.gitmodules').exist?
353
370
  initialize_ref Git.ref if gitpath.exist?
354
371
  end
355
372
 
@@ -359,11 +376,11 @@ module Squared
359
376
 
360
377
  def populate(*, **)
361
378
  super
362
- return unless ref?(Git.ref)
379
+ return unless ref?(Git.ref) || @only
363
380
 
364
381
  namespace name do
365
382
  Git.subtasks do |action, flags|
366
- next if @pass.include?(action)
383
+ next if task_pass?(action)
367
384
 
368
385
  namespace action do
369
386
  flags.each do |flag|
@@ -381,11 +398,34 @@ module Squared
381
398
  __send__(action, flag, args, remote: remote)
382
399
  end
383
400
  else
384
- format_desc action, flag, 'opts*'
401
+ format_desc(action, flag, 'opts*', after: flag == :all && action == 'pull' ? 'pattern*' : nil)
385
402
  task flag do |_, args|
386
403
  __send__ action, flag, args.to_a
387
404
  end
388
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
389
429
  when 'commit'
390
430
  case flag
391
431
  when :all
@@ -625,10 +665,7 @@ module Squared
625
665
  task flag, [:upstream, :name] do |_, args|
626
666
  if (ref = args.upstream)
627
667
  target = args.name
628
- if ref.start_with?('~')
629
- ref = ref[1..-1]
630
- remote = true
631
- end
668
+ remote = true if ref.delete_prefix!('~')
632
669
  else
633
670
  ref, remote, target = choice_refs('Choose a remote', 'remotes', accept: [['Push?', true]],
634
671
  values: ['Enter branch name'])
@@ -681,10 +718,10 @@ module Squared
681
718
  when :create
682
719
  format_desc action, flag, '(^)name,ref?=HEAD|:'
683
720
  task flag, [:name, :commit] do |_, args|
684
- target = param_guard(action, flag, args: args, key: :name)
721
+ branch = param_guard(action, flag, args: args, key: :name)
685
722
  commit = commithead args.commit
686
723
  commit, track = choice_commit(values: ['Track? [Y|n]'], force: false) if commit == ':'
687
- switch(flag, target: target, commit: commit, track: track)
724
+ switch(flag, branch: branch, commit: commit, track: track)
688
725
  end
689
726
  when :detach
690
727
  format_desc action, flag, 'ref?=HEAD'
@@ -692,11 +729,16 @@ module Squared
692
729
  commit = commithead(args.commit) || choice_commit(force: false)
693
730
  switch(flag, commit: commit)
694
731
  end
695
- when :merge
696
- format_desc action, flag, 'branch?'
697
- task flag, [:branch] do |_, args|
698
- commit = args.branch || choice_refs('Choose a branch')
699
- 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'))
700
742
  end
701
743
  end
702
744
  when 'reset'
@@ -717,10 +759,10 @@ module Squared
717
759
  end
718
760
  print_success if success?(reset(flag, args, commit: commit))
719
761
  end
720
- when :index
721
- format_desc action, flag, 'opts*,pathspec*'
762
+ when :index, :undo
763
+ format_desc(action, flag, flag == :index ? 'opts*,pathspec*' : nil)
722
764
  task flag do |_, args|
723
- reset flag, args.to_a
765
+ reset(flag, flag == :index ? args.to_a : [])
724
766
  end
725
767
  when :mode
726
768
  format_desc action, flag, 'mode,ref?=HEAD|:'
@@ -819,7 +861,7 @@ module Squared
819
861
  rev_parse(flag, ref: ref, size: size)
820
862
  end
821
863
  when :build
822
- format_desc action, flag, OPT_GIT[:status]
864
+ format_desc action, flag, 'opts*'
823
865
  task flag do |_, args|
824
866
  revbuild flag, args.to_a
825
867
  end
@@ -836,7 +878,7 @@ module Squared
836
878
  ls_remote(flag, args.extras, remote: args.remote)
837
879
  end
838
880
  else
839
- format_desc action, flag, 'opts*,pattern*'
881
+ format_desc(action, flag, 'opts*,pattern*', after: action == 'files' ? 'pathspec*' : nil)
840
882
  task flag do |_, args|
841
883
  __send__(action == 'refs' ? :ls_remote : :ls_files, flag, args.to_a)
842
884
  end
@@ -862,19 +904,14 @@ module Squared
862
904
  args = args.to_a
863
905
  if args.empty? || args.last == ':'
864
906
  files = []
865
- status_data.each do |line|
866
- case (flag == :staged ? line[2] : line[1])
867
- when /[AMDRTC]/
868
- files << line[0]
869
- end
870
- end
907
+ status_data.each { |row| files << row[0] if row[flag == :staged ? 2 : 1].match?(/[AMDRTC]/) }
871
908
  unless files.empty?
872
909
  files = choice_index('Select a file', files, multiple: true, force: false,
873
910
  accept: 'Restore?')
874
911
  end
875
912
  args.pop
876
913
  args, glob = args.partition { |val| val.match?(/^(?:[a-z-]+=|[^*]+$)/) }
877
- (files ||= []).concat(glob)
914
+ files.concat(glob)
878
915
  next if args.empty? && files.empty?
879
916
  end
880
917
  restore(flag, args, files: files)
@@ -882,13 +919,17 @@ module Squared
882
919
  end
883
920
  when 'git'
884
921
  before = case flag
922
+ when :blame then 'file'
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
@@ -1146,10 +1226,7 @@ module Squared
1146
1226
  rescue StandardError => e
1147
1227
  warn log_message(Logger::WARN, e, pass: true) if warning?
1148
1228
  else
1149
- if verbose
1150
- msg = sub_style('completed', styles: theme[:active])
1151
- puts log_message(Logger::INFO, name, msg, subject: 'revbuild', hint: time_format(epochtime - start))
1152
- end
1229
+ print_status(name, subject: 'revbuild', start: start, from: :completed)
1153
1230
  workspace.rev_write(name, { 'revision' => sha, 'files' => status_digest(*args, **kwargs) },
1154
1231
  sync: sync, utc: 'build')
1155
1232
  end
@@ -1179,6 +1256,9 @@ module Squared
1179
1256
  when :patch
1180
1257
  cmd << '--patch'
1181
1258
  append_pathspec(refs, pass: false)
1259
+ when :undo
1260
+ cmd << '--hard HEAD@{1}'
1261
+ ref = false
1182
1262
  end
1183
1263
  unless ref == false
1184
1264
  append_commit(ref, head: true)
@@ -1189,20 +1269,15 @@ module Squared
1189
1269
 
1190
1270
  def checkout(flag, opts = [], branch: nil, origin: nil, create: nil, commit: nil, detach: nil, merge: false)
1191
1271
  cmd, opts = git_session('checkout', opts: opts)
1192
- append_option 'force', 'merge'
1272
+ append_option 'force', 'f', 'merge'
1193
1273
  case flag
1194
1274
  when :branch
1195
1275
  cmd << '--detach' if detach == 'd' || option('detach')
1196
1276
  append_option('track', equals: true)
1197
1277
  cmd << (create ? quote_option(create, branch) : branch) << commit
1198
1278
  when :track
1199
- if branch
1200
- if branch.start_with?('^')
1201
- opt = 'B'
1202
- branch = branch[1..-1]
1203
- end
1204
- cmd << quote_option(opt || 'b', branch)
1205
- end
1279
+
1280
+ cmd << quote_option(branch.delete_prefix!('^') ? 'B' : 'b', branch) if branch
1206
1281
  cmd << '--track' << shell_quote(origin)
1207
1282
  when :detach
1208
1283
  cmd << '-m' if merge
@@ -1231,7 +1306,7 @@ module Squared
1231
1306
  elsif !session_arg?('s', 'sign', 'u', 'local-user')
1232
1307
  cmd << '--annotate'
1233
1308
  end
1234
- cmd << '--force' if option('force')
1309
+ cmd << '--force' if option('force', 'f')
1235
1310
  if !commit && message && (sha = commithash(message))
1236
1311
  commit = sha
1237
1312
  message = nil
@@ -1262,7 +1337,7 @@ module Squared
1262
1337
  first: matchpathspec)
1263
1338
  case flag
1264
1339
  when :between, :contain
1265
- op << shell_quote(range.join(flag == :between ? '..' : '...'))
1340
+ op.add_quote(range.join(flag == :between ? '..' : '...'))
1266
1341
  else
1267
1342
  op.merge(index)
1268
1343
  end
@@ -1291,13 +1366,13 @@ module Squared
1291
1366
  op.merge(range)
1292
1367
  when :between, :contain
1293
1368
  op.delete('--merge-base')
1294
- op << shell_quote(range.join(flag == :between ? '..' : '...'))
1369
+ op.add_quote(range.join(flag == :between ? '..' : '...'))
1295
1370
  else
1296
1371
  op << '--merge-base' if option('merge-base')
1297
- op << shell_quote(branch) if branch
1372
+ op.add_quote(branch) if branch
1298
1373
  if !index.empty?
1299
1374
  if op.arg?('cached')
1300
- 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
1301
1376
  op << index.first
1302
1377
  else
1303
1378
  op.merge(index)
@@ -1427,13 +1502,13 @@ module Squared
1427
1502
  '--track'
1428
1503
  end
1429
1504
  end
1430
- cmd << '--force' if option('force')
1505
+ cmd << '--force' if option('force', 'f')
1431
1506
  cmd << shell_quote(target)
1432
1507
  cmd << shell_quote(ref) if ref
1433
1508
  when :track
1434
1509
  raise_error('invalid upstream', hint: ref) unless ref.include?('/')
1435
- if ref.start_with?('^')
1436
- cmd << '--unset-upstream' << shell_quote(ref[1..-1])
1510
+ if ref.delete_prefix!('^')
1511
+ cmd << '--unset-upstream' << shell_quote(ref)
1437
1512
  remote = false
1438
1513
  stdout = true
1439
1514
  else
@@ -1442,7 +1517,7 @@ module Squared
1442
1517
  end
1443
1518
  when :delete
1444
1519
  remote&.each do |val|
1445
- 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) })
1446
1521
  end
1447
1522
  force, list = refs.partition { |val| val.start_with?(/[~^]/) }
1448
1523
  force.each do |val|
@@ -1458,7 +1533,7 @@ module Squared
1458
1533
  remote = nil
1459
1534
  when :move, :copy
1460
1535
  flag = "-#{flag.to_s[0]}"
1461
- cmd << (option('force') ? flag.upcase : flag)
1536
+ cmd << (option('force', 'f') ? flag.upcase : flag)
1462
1537
  refs.compact.each { |val| cmd << shell_quote(val) }
1463
1538
  stdout = true
1464
1539
  when :current
@@ -1468,7 +1543,7 @@ module Squared
1468
1543
  when :list
1469
1544
  op = OptionPartition.new(opts, OPT_GIT[:branch], cmd << '--list',
1470
1545
  project: self, no: OPT_GIT[:no][:branch], single: /\Av+\z/)
1471
- op.each { |val| op << shell_quote(val) }
1546
+ op.each { |val| op.add_quote(val) }
1472
1547
  out, banner, from = source(io: true)
1473
1548
  print_item banner
1474
1549
  ret = write_lines(out, sub: [
@@ -1514,33 +1589,47 @@ module Squared
1514
1589
  if !ref
1515
1590
  print_success if flag == :create
1516
1591
  elsif remote && target
1517
- 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))
1518
1593
  end
1519
1594
  end
1520
1595
 
1521
- def switch(flag, opts = [], target: nil, commit: nil, track: nil)
1522
- cmd = git_session('switch', opts: opts).first
1523
- case flag
1524
- when :create
1525
- c = 'c'
1526
- if target.start_with?('^')
1527
- target = target[1..-1]
1528
- 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}"
1529
1616
  end
1530
- cmd << quote_option(c, target)
1531
- cmd << case (track ||= option('track', ignore: false))
1532
- when 'n', 'N', '0', 'false'
1533
- '--no-track'
1534
- when 'y', 'Y', '1', 'true'
1535
- '--track'
1536
- when 'direct', 'inherit'
1537
- basic_option 'track', track
1538
- end
1539
- when :detach, :merge
1540
- 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)
1541
1632
  end
1542
- cmd << '--force' if option('force')
1543
- append_head commit
1544
1633
  source
1545
1634
  end
1546
1635
 
@@ -1609,7 +1698,7 @@ module Squared
1609
1698
  cmd, opts = git_session('ls-remote', '--refs', opts: opts)
1610
1699
  cmd << "--#{flag}" unless flag == :remote
1611
1700
  op = OptionPartition.new(opts, OPT_GIT[:ls_remote], cmd, project: self)
1612
- op << shell_quote(remote) if remote
1701
+ op.add_quote(remote) if remote
1613
1702
  out, banner, from = source(io: true)
1614
1703
  print_item banner
1615
1704
  ret = write_lines(out, grep: op.extras, prefix: "refs/#{flag}/")
@@ -1619,6 +1708,7 @@ module Squared
1619
1708
  def ls_files(flag, opts = [])
1620
1709
  cmd, opts = git_session('ls-files', "--#{flag}", opts: opts)
1621
1710
  op = OptionPartition.new(opts, OPT_GIT[:ls_files], cmd, project: self)
1711
+ op.splice(path: true, pattern: true)
1622
1712
  out, banner, from = source(io: true)
1623
1713
  print_item banner
1624
1714
  ret = write_lines(out, grep: op.extras)
@@ -1627,10 +1717,16 @@ module Squared
1627
1717
 
1628
1718
  def git(flag, opts = [])
1629
1719
  cmd, opts = git_session(flag, opts: opts)
1630
- list = OPT_GIT[:git].fetch(flag, []) + OPT_GIT.fetch(flag, [])
1631
- op = OptionPartition.new(opts, list, cmd, project: self, no: OPT_GIT[:no][flag],
1632
- first: flag == :revert ? nil : matchpathspec)
1720
+ op = OptionPartition.new(opts, OPT_GIT[:git].fetch(flag, []) + OPT_GIT.fetch(flag, []), cmd,
1721
+ project: self, no: OPT_GIT[:no][flag], first: case flag
1722
+ when :blame, :revert
1723
+ nil
1724
+ else matchpathspec end)
1633
1725
  case flag
1726
+ when :blame
1727
+ raise_error 'no file found' unless (n = op.index { |s| (path + s).file? })
1728
+ op << '--' << shell_quote(path + op.delete_at(n))
1729
+ op.clear
1634
1730
  when :revert
1635
1731
  if VAL_GIT[:rebase][:send].any? { |val| op.arg?(val) }
1636
1732
  op.clear
@@ -1641,16 +1737,16 @@ module Squared
1641
1737
  end
1642
1738
  when :add, :clean
1643
1739
  if flag == :add && !op.arg?('pathspec-from-file')
1644
- grep, list = op.extras.partition { |val| val.start_with?(':') && val.end_with?(':') }
1645
- if list.empty? || !grep.empty?
1646
- red = color(:red)
1740
+ grep, list = op.partition { |val| OptionPartition.pattern?(val) }
1741
+ unless grep.empty? && !list.empty?
1647
1742
  grep.map! { |val| Regexp.new(val[1..-2]) }
1648
- files = status_data.map! do |a, b|
1649
- 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) })
1650
1746
 
1651
- "#{sub_style(b, styles: red)} #{a}"
1747
+ out << "#{sub_style(b, styles: color(:red))} #{a}"
1748
+ end
1652
1749
  end
1653
- .compact
1654
1750
  unless files.empty?
1655
1751
  files = choice_index('Select files', files, multiple: true, force: true, trim: /^\S+\s/,
1656
1752
  accept: [['Add?', false, true]])
@@ -1658,7 +1754,7 @@ module Squared
1658
1754
  op.swap(list + files)
1659
1755
  end
1660
1756
  end
1661
- 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)
1662
1758
 
1663
1759
  verbose = flag == :add && !op.arg?('verbose')
1664
1760
  print_success if success?(source) && verbose
@@ -1688,7 +1784,7 @@ module Squared
1688
1784
  private
1689
1785
 
1690
1786
  def source(cmd = @session, exception: true, io: false, sync: true, stdout: false, stderr: false, banner: true,
1691
- multiple: false, from: nil, **kwargs)
1787
+ multiple: false, hint: nil, from: nil, **kwargs)
1692
1788
  cmd = cmd.target if cmd.is_a?(OptionPartition)
1693
1789
  if io && banner == false
1694
1790
  from = nil
@@ -1706,7 +1802,8 @@ module Squared
1706
1802
  cmd = session_done cmd
1707
1803
  log&.info cmd
1708
1804
  banner = if banner
1709
- 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)
1710
1807
  end
1711
1808
  on :first, from
1712
1809
  begin
@@ -1745,11 +1842,7 @@ module Squared
1745
1842
  end
1746
1843
  end
1747
1844
  rescue StandardError => e
1748
- log&.error e
1749
- ret = on :error, from, e
1750
- raise if exception && ret != true
1751
-
1752
- warn log_message(Logger::WARN, e, pass: true) if warning?
1845
+ on_error(e, from, pass: true)
1753
1846
  nil
1754
1847
  else
1755
1848
  on :last, from
@@ -1758,16 +1851,7 @@ module Squared
1758
1851
  end
1759
1852
 
1760
1853
  def write_lines(data, grep: [], prefix: nil, sub: nil, banner: nil, loglevel: nil, pass: false, first: false)
1761
- grep = unless grep.empty?
1762
- grep.map do |val|
1763
- if val.is_a?(Regexp)
1764
- val
1765
- else
1766
- val = ".*#{val}" if prefix && !val.sub!(/\A(\^|\\A)/, '')
1767
- Regexp.new("#{prefix}#{val == '*' ? '.+' : val}")
1768
- end
1769
- end
1770
- end
1854
+ grep = grep.empty? ? nil : matchmap(grep, prefix)
1771
1855
  sub = nil if stdin?
1772
1856
  ret = 0
1773
1857
  out = []
@@ -1841,8 +1925,7 @@ module Squared
1841
1925
  glob = kwargs.fetch(:include, [])
1842
1926
  pass = kwargs.fetch(:exclude, [])
1843
1927
  ret = {}
1844
- status_data(*args).each do |line|
1845
- file = line.first
1928
+ status_data(*args).each do |file,|
1846
1929
  next if !glob.empty? && glob.none? { |val| File.fnmatch?(val, file, File::FNM_DOTMATCH) }
1847
1930
  next if !pass.empty? && pass.any? { |val| File.fnmatch?(val, file, File::FNM_DOTMATCH) }
1848
1931
 
@@ -1852,17 +1935,17 @@ module Squared
1852
1935
  end
1853
1936
 
1854
1937
  def status_data(*args)
1855
- ret = []
1856
- git_spawn('status -z -uall', *args).split("\x0").each do |line|
1857
- next unless line =~ /^(.)(.) (.+)$/
1938
+ [].tap do |ret|
1939
+ git_spawn('status -z -uall', *args).split("\x0").each do |line|
1940
+ next unless line =~ /^(.)(.) (.+)$/
1858
1941
 
1859
- ret << [$3, $2, $1]
1942
+ ret << [$3, $2, $1]
1943
+ end
1860
1944
  end
1861
- ret
1862
1945
  end
1863
1946
 
1864
1947
  def append_pull(opts, list, target: @session, flag: nil, no: nil, remote: nil, from: nil)
1865
- target << '--force' if option('force', target: target)
1948
+ target << '--force' if option('force', 'f', target: target)
1866
1949
  append_submodules(target: target, from: from)
1867
1950
  return if !remote && opts.empty?
1868
1951
 
@@ -1920,7 +2003,7 @@ module Squared
1920
2003
  option_clear files
1921
2004
  true
1922
2005
  else
1923
- 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?
1924
2007
  files = projectmap(files, parent: parent, pass: pass)
1925
2008
  if !files.empty?
1926
2009
  target << '--' << files.join(' ')
@@ -1978,12 +2061,11 @@ module Squared
1978
2061
  end
1979
2062
 
1980
2063
  def git_session(*cmd, opts: nil, worktree: true, **kwargs)
1981
- 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)] : []
1982
2065
  return session('git', *dir, *cmd, **kwargs) unless opts
1983
2066
 
1984
2067
  op = OptionPartition.new(opts, OPT_GIT[:common], dir, project: self)
1985
- ret = session('git', *op.to_a, *cmd, **kwargs)
1986
- [ret, op.extras]
2068
+ [session('git', *op.to_a, *cmd, **kwargs), op.extras]
1987
2069
  end
1988
2070
 
1989
2071
  def git_output(*cmd, **kwargs)