squared 0.4.15 → 0.4.17

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
@@ -179,6 +179,9 @@ module Squared
179
179
  }.freeze,
180
180
  git: {
181
181
  add: %w[N|intent-to-add refresh].freeze,
182
+ 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
183
+ p|porcelain root score-debug f|show-name e|show-email n|show-number show-stats abbrev=i
184
+ contents=p date=q encoding=b ignore-rev=b ignore-revs-file=p reverse=q].freeze,
182
185
  clean: %w[d x X f|force n|dry-run i|interactive q|quiet e|exclude=q].freeze,
183
186
  mv: %w[k f|force n|dry-run v|verbose].freeze,
184
187
  revert: %w[e S=bm? abort continue n|no-commit quit reference skip cleanup=b gpg-sign=b? m|mainline=i
@@ -241,10 +244,21 @@ module Squared
241
244
  pop: %w[index].freeze,
242
245
  apply: %w[index].freeze
243
246
  }.freeze,
244
- status: %w[u|ignore-submodules=bm? ignored=b? untracked-files=b?],
247
+ status: %w[z u=bm? b|branch long s|short show-stash v|verbose column=b find-renames=i? ignore-submodules=b?
248
+ ignored=b? porcelain=b? untracked-files=b?].freeze,
249
+ submodule: {
250
+ status: %w[cached recursive].freeze,
251
+ update: %w[checkout f|force init merge N|no-fetch no-recommend-shallow no-single-branch recommend-shallow
252
+ rebase recursive remote single-branch depth=i filter=q jobs=i reference=b ref-format=q].freeze,
253
+ branch: %w[b|branch d|default].freeze,
254
+ sync: %w[recursive].freeze
255
+ }.freeze,
256
+ switch: %w[d|detach discard-changes f|force ignore-other-worktrees m|merge q|quiet conflict=b c|create=q
257
+ C|force-create=q orphan=q t|track=b].freeze,
245
258
  tag: %w[n=im cleanup=b create-reflog i|ignore-case color=b? column=b contains=b? format=q merged=b?
246
259
  no-contains=b? no-merged=b? points-at=q sort=q].freeze,
247
260
  no: {
261
+ blame: %w[progress].freeze,
248
262
  branch: %w[color color-moved column track].freeze,
249
263
  checkout: %w[overwrite-ignore guess overlay progress recurse-submodules track].freeze,
250
264
  fetch: {
@@ -266,6 +280,8 @@ module Squared
266
280
  rev_parse: %w[flags].freeze,
267
281
  revert: %w[edit gpg-sign rerere-autoupdate].freeze,
268
282
  show: %w[standard-notes].freeze,
283
+ status: %w[ahead-behind column renames].freeze,
284
+ switch: %w[guess progress recurse-submodules track].freeze,
269
285
  tag: %w[column].freeze
270
286
  }.freeze
271
287
  }.freeze
@@ -287,10 +303,8 @@ module Squared
287
303
  def populate(ws, **)
288
304
  return if ws.series.exclude?(:pull, true)
289
305
 
290
- namespace(name = ws.task_name('git')) do
291
- all = ws.task_join(name, 'all')
292
-
293
- ws.format_desc(all, 'stash|rebase|autostash?,depend?')
306
+ namespace(ws.task_name('git')) do |ns|
307
+ ws.format_desc(all = ws.task_join(ns.scope.path, 'all'), 'stash|rebase|autostash?,depend?')
294
308
  task 'all' do |_, args|
295
309
  args = args.to_a
296
310
  cmd = if args.include?('stash')
@@ -307,6 +321,7 @@ module Squared
307
321
  cmd << ws.task_sync('build')
308
322
  Common::Utils.task_invoke(*cmd, **ws.invokeargs)
309
323
  end
324
+
310
325
  ws.series.sync << all
311
326
  ws.series.multiple << all
312
327
  end
@@ -328,25 +343,27 @@ module Squared
328
343
  'checkout' => %i[commit branch track detach path].freeze,
329
344
  'commit' => %i[add all amend amend-orig fixup].freeze,
330
345
  'diff' => %i[head branch files view between contain].freeze,
331
- 'fetch' => %i[origin remote].freeze,
346
+ 'fetch' => %i[origin remote all].freeze,
332
347
  'files' => %i[cached modified deleted others].freeze,
333
- 'git' => %i[add clean mv revert rm].freeze,
348
+ 'git' => %i[add blame clean mv revert rm status].freeze,
334
349
  'log' => %i[view between contain].freeze,
335
350
  'merge' => %i[commit no-commit send].freeze,
336
- 'pull' => %i[origin remote].freeze,
351
+ 'pull' => %i[origin remote all].freeze,
337
352
  'rebase' => %i[branch onto send].freeze,
338
353
  'refs' => %i[heads tags remote].freeze,
339
- 'reset' => %i[commit index patch mode].freeze,
354
+ 'reset' => %i[commit index patch mode undo].freeze,
340
355
  'restore' => %i[source staged worktree].freeze,
341
356
  'rev' => %i[commit build output].freeze,
342
357
  'show' => %i[format oneline textconv].freeze,
343
358
  'stash' => %i[push pop apply branch drop clear list].freeze,
344
- 'switch' => %i[create detach merge].freeze,
359
+ 'submodule' => %i[status update branch url sync].freeze,
360
+ 'switch' => %i[branch create detach].freeze,
345
361
  'tag' => %i[add sign delete list].freeze
346
362
  })
347
363
 
348
364
  def initialize(*, **)
349
365
  super
366
+ @submodule = basepath('.gitmodules').exist?
350
367
  initialize_ref Git.ref if gitpath.exist?
351
368
  end
352
369
 
@@ -356,11 +373,11 @@ module Squared
356
373
 
357
374
  def populate(*, **)
358
375
  super
359
- return unless ref?(Git.ref)
376
+ return unless ref?(Git.ref) || @only
360
377
 
361
378
  namespace name do
362
379
  Git.subtasks do |action, flags|
363
- next if @pass.include?(action)
380
+ next if task_pass?(action)
364
381
 
365
382
  namespace action do
366
383
  flags.each do |flag|
@@ -378,11 +395,34 @@ module Squared
378
395
  __send__(action, flag, args, remote: remote)
379
396
  end
380
397
  else
381
- format_desc action, flag, 'opts*'
398
+ format_desc(action, flag, 'opts*', after: flag == :all && action == 'pull' ? 'pattern*' : nil)
382
399
  task flag do |_, args|
383
400
  __send__ action, flag, args.to_a
384
401
  end
385
402
  end
403
+ when 'submodule'
404
+ break unless @submodule
405
+
406
+ case flag
407
+ when :branch
408
+ format_desc action, flag, 'path,opts*'
409
+ task flag, [:path] do |_, args|
410
+ path = param_guard(action, flag, args: args, key: :path)
411
+ submodule(flag, args.extras, path: path)
412
+ end
413
+ when :url
414
+ format_desc action, flag, 'path,url,opts*'
415
+ task flag, [:path, :url] do |_, args|
416
+ path = param_guard(action, flag, args: args, key: :path)
417
+ url = param_guard(action, flag, args: args, key: :url)
418
+ submodule(flag, args.extras, path: path, url: url)
419
+ end
420
+ else
421
+ format_desc action, flag, 'opts*,path*'
422
+ task flag do |_, args|
423
+ submodule flag, args.to_a
424
+ end
425
+ end
386
426
  when 'commit'
387
427
  case flag
388
428
  when :all
@@ -678,10 +718,10 @@ module Squared
678
718
  when :create
679
719
  format_desc action, flag, '(^)name,ref?=HEAD|:'
680
720
  task flag, [:name, :commit] do |_, args|
681
- target = param_guard(action, flag, args: args, key: :name)
721
+ branch = param_guard(action, flag, args: args, key: :name)
682
722
  commit = commithead args.commit
683
723
  commit, track = choice_commit(values: ['Track? [Y|n]'], force: false) if commit == ':'
684
- switch(flag, target: target, commit: commit, track: track)
724
+ switch(flag, branch: branch, commit: commit, track: track)
685
725
  end
686
726
  when :detach
687
727
  format_desc action, flag, 'ref?=HEAD'
@@ -689,11 +729,16 @@ module Squared
689
729
  commit = commithead(args.commit) || choice_commit(force: false)
690
730
  switch(flag, commit: commit)
691
731
  end
692
- when :merge
693
- format_desc action, flag, 'branch?'
694
- task flag, [:branch] do |_, args|
695
- commit = args.branch || choice_refs('Choose a branch')
696
- 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'))
697
742
  end
698
743
  end
699
744
  when 'reset'
@@ -714,10 +759,10 @@ module Squared
714
759
  end
715
760
  print_success if success?(reset(flag, args, commit: commit))
716
761
  end
717
- when :index
718
- format_desc action, flag, 'opts*,pathspec*'
762
+ when :index, :undo
763
+ format_desc(action, flag, flag == :index ? 'opts*,pathspec*' : nil)
719
764
  task flag do |_, args|
720
- reset flag, args.to_a
765
+ reset(flag, flag == :index ? args.to_a : [])
721
766
  end
722
767
  when :mode
723
768
  format_desc action, flag, 'mode,ref?=HEAD|:'
@@ -816,7 +861,7 @@ module Squared
816
861
  rev_parse(flag, ref: ref, size: size)
817
862
  end
818
863
  when :build
819
- format_desc action, flag, OPT_GIT[:status]
864
+ format_desc action, flag, 'opts*'
820
865
  task flag do |_, args|
821
866
  revbuild flag, args.to_a
822
867
  end
@@ -833,7 +878,7 @@ module Squared
833
878
  ls_remote(flag, args.extras, remote: args.remote)
834
879
  end
835
880
  else
836
- format_desc action, flag, 'opts*,pattern*'
881
+ format_desc(action, flag, 'opts*,pattern*', after: action == 'files' ? 'pathspec*' : nil)
837
882
  task flag do |_, args|
838
883
  __send__(action == 'refs' ? :ls_remote : :ls_files, flag, args.to_a)
839
884
  end
@@ -859,19 +904,14 @@ module Squared
859
904
  args = args.to_a
860
905
  if args.empty? || args.last == ':'
861
906
  files = []
862
- status_data.each do |line|
863
- case (flag == :staged ? line[2] : line[1])
864
- when /[AMDRTC]/
865
- files << line[0]
866
- end
867
- end
907
+ status_data.each { |row| files << row[0] if row[flag == :staged ? 2 : 1].match?(/[AMDRTC]/) }
868
908
  unless files.empty?
869
909
  files = choice_index('Select a file', files, multiple: true, force: false,
870
910
  accept: 'Restore?')
871
911
  end
872
912
  args.pop
873
913
  args, glob = args.partition { |val| val.match?(/^(?:[a-z-]+=|[^*]+$)/) }
874
- (files ||= []).concat(glob)
914
+ files.concat(glob)
875
915
  next if args.empty? && files.empty?
876
916
  end
877
917
  restore(flag, args, files: files)
@@ -879,13 +919,17 @@ module Squared
879
919
  end
880
920
  when 'git'
881
921
  before = case flag
922
+ when :blame then 'file'
882
923
  when :mv then 'source+,destination'
883
924
  when :revert then 'commit+' end
884
925
  format_desc(action, flag, 'opts*', before: before, after: case flag
885
- when :add then 'pathspec*|:pattern:*'
886
- when :clean, :rm then 'pathspec*' end)
926
+ when :add
927
+ 'pathspec*,pattern*'
928
+ when :clean, :rm, :status
929
+ 'pathspec*'
930
+ end)
887
931
  task flag do |_, args|
888
- git flag, args.to_a
932
+ __send__(flag == :status ? :status : :git, flag, args.to_a)
889
933
  end
890
934
  end
891
935
  end
@@ -909,11 +953,44 @@ module Squared
909
953
  super
910
954
  end
911
955
 
912
- 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)
913
957
  cmd, opts = git_session('pull', opts: opts)
914
- if flag == :rebase
958
+ cmd << '--autostash' if option('autostash')
959
+ case flag
960
+ when :rebase
915
961
  cmd << '--rebase'
916
- 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/n] ', 'Y')
965
+ git_spawn 'stash push --keep-index --quiet'
966
+ elsif !(force = confirm('Force checkout? [y/N] ', '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
917
994
  else
918
995
  cmd << '--autostash' if flag == :autostash
919
996
  if (val = option('rebase', ignore: false))
@@ -932,7 +1009,7 @@ module Squared
932
1009
  { pat: /^(.+)(\|\s+\d+\s+)([^-]*)(-+)(.*)$/, styles: color(:red), index: 4 },
933
1010
  { pat: /^(.+)(\|\s+\d+\s+)(\++)(.*)$/, styles: color(:green), index: 3 }
934
1011
  ]
935
- end, **threadargs)
1012
+ end, hint: hint, **threadargs)
936
1013
  end
937
1014
 
938
1015
  def rebase(flag = nil, opts = [], sync: invoked_sync?('rebase', flag), commit: nil, upstream: nil, branch: nil,
@@ -969,6 +1046,7 @@ module Squared
969
1046
 
970
1047
  def fetch(flag = nil, opts = [], sync: invoked_sync?('fetch', flag), remote: nil)
971
1048
  opts = git_session('fetch', opts: opts).last
1049
+ opts << 'all' if flag == :all || option('all')
972
1050
  append_pull(opts, collect_hash(OPT_GIT[:fetch]), no: collect_hash(OPT_GIT[:no][:fetch]),
973
1051
  remote: remote, flag: flag, from: :fetch)
974
1052
  source(sync: sync, **threadargs)
@@ -1008,15 +1086,15 @@ module Squared
1008
1086
  cmd, opts = git_session('stash', flag, opts: opts)
1009
1087
  list = OPT_GIT[:stash][:common] + OPT_GIT[:stash].fetch(flag, [])
1010
1088
  if flag == :list
1011
- list += collect_hash(OPT_GIT[:log])
1012
- no = collect_hash(OPT_GIT[:no][:log])
1089
+ list += collect_hash OPT_GIT[:log]
1090
+ no = collect_hash OPT_GIT[:no][:log]
1013
1091
  end
1014
1092
  op = OptionPartition.new(opts, list, cmd, project: self, no: no, first: flag == :push ? matchpathspec : nil)
1015
1093
  case flag
1016
1094
  when :push
1017
1095
  append_pathspec op.extras
1018
1096
  when :pop, :apply, :drop, :branch
1019
- if op.extras.delete(':')
1097
+ if op.remove(':')
1020
1098
  if flag == :branch
1021
1099
  if op.empty?
1022
1100
  values = [['Branch name', true]]
@@ -1051,29 +1129,34 @@ module Squared
1051
1129
  end
1052
1130
  else
1053
1131
  git_session('stash', 'push', opts: opts)
1054
- append_option(OPT_GIT[:stash][:push].grep_v(/[=|]/), no: true, ignore: false)
1132
+ append_option(OptionPartition.select(OPT_GIT[:stash][:push], no: false), no: true, ignore: false)
1055
1133
  append_message
1056
1134
  end
1057
1135
  source(banner: !quiet?, sync: sync, **threadargs)
1058
1136
  end
1059
1137
 
1060
- def status(*)
1061
- cmd = git_session 'status'
1062
- cmd << (option('long') ? '--long' : '--short')
1063
- cmd << '--branch' if option('branch')
1064
- if (val = option('ignore-submodules', ignore: false))
1065
- cmd << basic_option('ignore-submodules', case val
1066
- when '0', 'none'
1067
- 'none'
1068
- when '1', 'untracked'
1069
- 'untracked'
1070
- when '2', 'dirty'
1071
- 'dirty'
1072
- else
1073
- 'all'
1074
- end)
1138
+ def status(flag = nil, opts = [])
1139
+ cmd, opts = git_session('status', opts: opts)
1140
+ if flag
1141
+ op = OptionPartition.new(opts, OPT_GIT[:status], cmd, project: self, no: OPT_GIT[:no][:status])
1142
+ append_pathspec op.extras
1143
+ else
1144
+ cmd << (option('long') ? '--long' : '--short')
1145
+ cmd << '--branch' if option('branch')
1146
+ if (val = option('ignore-submodules', ignore: false))
1147
+ cmd << basic_option('ignore-submodules', case val
1148
+ when '0', 'none'
1149
+ 'none'
1150
+ when '1', 'untracked'
1151
+ 'untracked'
1152
+ when '2', 'dirty'
1153
+ 'dirty'
1154
+ else
1155
+ 'all'
1156
+ end)
1157
+ end
1158
+ append_pathspec
1075
1159
  end
1076
- append_pathspec
1077
1160
  if verbose
1078
1161
  r = color(:red)
1079
1162
  g = color(:green)
@@ -1115,7 +1198,7 @@ module Squared
1115
1198
  kwargs = kwargs.key?(:include) || kwargs.key?(:exclude) ? statusargs.call : @revbuild || {}
1116
1199
  case flag
1117
1200
  when :build
1118
- op = OptionPartition.new(opts, OPT_GIT[:status], project: self)
1201
+ op = OptionPartition.new(opts, %w[ignore-submodules=b? ignored=b? untracked-files=b?], project: self)
1119
1202
  op.clear(append: true)
1120
1203
  args = op.to_a
1121
1204
  else
@@ -1142,10 +1225,7 @@ module Squared
1142
1225
  rescue StandardError => e
1143
1226
  warn log_message(Logger::WARN, e, pass: true) if warning?
1144
1227
  else
1145
- if verbose
1146
- msg = sub_style('completed', styles: theme[:active])
1147
- puts log_message(Logger::INFO, name, msg, subject: 'revbuild', hint: time_format(epochtime - start))
1148
- end
1228
+ print_status(name, subject: 'revbuild', start: start, from: :completed)
1149
1229
  workspace.rev_write(name, { 'revision' => sha, 'files' => status_digest(*args, **kwargs) },
1150
1230
  sync: sync, utc: 'build')
1151
1231
  end
@@ -1175,6 +1255,9 @@ module Squared
1175
1255
  when :patch
1176
1256
  cmd << '--patch'
1177
1257
  append_pathspec(refs, pass: false)
1258
+ when :undo
1259
+ cmd << '--hard HEAD@{1}'
1260
+ ref = false
1178
1261
  end
1179
1262
  unless ref == false
1180
1263
  append_commit(ref, head: true)
@@ -1185,7 +1268,7 @@ module Squared
1185
1268
 
1186
1269
  def checkout(flag, opts = [], branch: nil, origin: nil, create: nil, commit: nil, detach: nil, merge: false)
1187
1270
  cmd, opts = git_session('checkout', opts: opts)
1188
- append_option 'force', 'merge'
1271
+ append_option 'force', 'f', 'merge'
1189
1272
  case flag
1190
1273
  when :branch
1191
1274
  cmd << '--detach' if detach == 'd' || option('detach')
@@ -1227,7 +1310,7 @@ module Squared
1227
1310
  elsif !session_arg?('s', 'sign', 'u', 'local-user')
1228
1311
  cmd << '--annotate'
1229
1312
  end
1230
- cmd << '--force' if option('force')
1313
+ cmd << '--force' if option('force', 'f')
1231
1314
  if !commit && message && (sha = commithash(message))
1232
1315
  commit = sha
1233
1316
  message = nil
@@ -1258,7 +1341,7 @@ module Squared
1258
1341
  first: matchpathspec)
1259
1342
  case flag
1260
1343
  when :between, :contain
1261
- op << shell_quote(range.join(flag == :between ? '..' : '...'))
1344
+ op.add_quote(range.join(flag == :between ? '..' : '...'))
1262
1345
  else
1263
1346
  op.merge(index)
1264
1347
  end
@@ -1287,13 +1370,13 @@ module Squared
1287
1370
  op.merge(range)
1288
1371
  when :between, :contain
1289
1372
  op.delete('--merge-base')
1290
- op << shell_quote(range.join(flag == :between ? '..' : '...'))
1373
+ op.add_quote(range.join(flag == :between ? '..' : '...'))
1291
1374
  else
1292
1375
  op << '--merge-base' if option('merge-base')
1293
- op << shell_quote(branch) if branch
1376
+ op.add_quote(branch) if branch
1294
1377
  if !index.empty?
1295
1378
  if op.arg?('cached')
1296
- raise_error("one commit only: #{index.join(', ')}", hint: '--cached') if index.size > 1
1379
+ raise_error("one commit only: #{index.join(', ')}", hint: 'cached') if index.size > 1
1297
1380
  op << index.first
1298
1381
  else
1299
1382
  op.merge(index)
@@ -1423,7 +1506,7 @@ module Squared
1423
1506
  '--track'
1424
1507
  end
1425
1508
  end
1426
- cmd << '--force' if option('force')
1509
+ cmd << '--force' if option('force', 'f')
1427
1510
  cmd << shell_quote(target)
1428
1511
  cmd << shell_quote(ref) if ref
1429
1512
  when :track
@@ -1438,7 +1521,7 @@ module Squared
1438
1521
  end
1439
1522
  when :delete
1440
1523
  remote&.each do |val|
1441
- source git_output('push', '--delete', *val.split('/', 2).map { |s| shell_quote(s) })
1524
+ source git_output('push --delete', *val.split('/', 2).map { |s| shell_quote(s) })
1442
1525
  end
1443
1526
  force, list = refs.partition { |val| val.match?(/^[~^]/) }
1444
1527
  force.each do |val|
@@ -1454,7 +1537,7 @@ module Squared
1454
1537
  remote = nil
1455
1538
  when :move, :copy
1456
1539
  flag = "-#{flag.to_s[0]}"
1457
- cmd << (option('force') ? flag.upcase : flag)
1540
+ cmd << (option('force', 'f') ? flag.upcase : flag)
1458
1541
  refs.compact.each { |val| cmd << shell_quote(val) }
1459
1542
  stdout = true
1460
1543
  when :current
@@ -1464,7 +1547,7 @@ module Squared
1464
1547
  when :list
1465
1548
  op = OptionPartition.new(opts, OPT_GIT[:branch], cmd << '--list',
1466
1549
  project: self, no: OPT_GIT[:no][:branch], single: /\Av+\z/)
1467
- op.each { |val| op << shell_quote(val) }
1550
+ op.each { |val| op.add_quote(val) }
1468
1551
  out, banner, from = source(io: true)
1469
1552
  print_item banner
1470
1553
  ret = write_lines(out, sub: [
@@ -1510,33 +1593,52 @@ module Squared
1510
1593
  if !ref
1511
1594
  print_success if flag == :create
1512
1595
  elsif remote && target
1513
- source git_output('push', '-u', shell_quote(ref.split('/', 2).first), shell_quote(target))
1596
+ source git_output('push -u', shell_quote(ref.split('/', 2).first), shell_quote(target))
1514
1597
  end
1515
1598
  end
1516
1599
 
1517
- def switch(flag, opts = [], target: nil, commit: nil, track: nil)
1518
- cmd = git_session('switch', opts: opts).first
1519
- case flag
1520
- when :create
1521
- c = 'c'
1522
- if target.start_with?('^')
1523
- target = target[1..-1]
1524
- c = c.upcase
1600
+ def switch(flag, opts = [], branch: nil, commit: nil, track: nil)
1601
+ cmd, opts = git_session('switch', opts: opts)
1602
+ cmd << '--force' if option('force', 'f')
1603
+ if flag == :branch
1604
+ op = OptionPartition.new(opts, OPT_GIT[:switch], cmd, project: self, no: OPT_GIT[:no][:switch])
1605
+ op.add_quote(branch)
1606
+ else
1607
+ case flag
1608
+ when :create
1609
+ c = 'c'
1610
+ if target.start_with?('^')
1611
+ target = target[1..-1]
1612
+ c = c.upcase
1613
+ end
1614
+ cmd << quote_option(c, target)
1615
+ cmd << case (track ||= option('track', ignore: false))
1616
+ when 'n', 'N', '0', 'false'
1617
+ '--no-track'
1618
+ when 'y', 'Y', '1', 'true'
1619
+ '--track'
1620
+ when 'direct', 'inherit'
1621
+ basic_option 'track', track
1622
+ end
1623
+ when :detach
1624
+ cmd << "--#{flag}"
1525
1625
  end
1526
- cmd << quote_option(c, target)
1527
- cmd << case (track ||= option('track', ignore: false))
1528
- when 'n', 'N', '0', 'false'
1529
- '--no-track'
1530
- when 'y', 'Y', '1', 'true'
1531
- '--track'
1532
- when 'direct', 'inherit'
1533
- basic_option('track', track)
1534
- end
1535
- when :detach, :merge
1536
- cmd << "--#{flag}"
1626
+ append_head commit
1627
+ end
1628
+ source
1629
+ end
1630
+
1631
+ def submodule(flag, opts = [], path: nil, url: nil)
1632
+ cmd, opts = git_session('submodule', opts: opts)
1633
+ op = OptionPartition.new(opts, OPT_GIT[:submodule].fetch(flag, []), cmd, project: self)
1634
+ case flag
1635
+ when :branch, :url
1636
+ op << '--'
1637
+ op.add_path(path) if path
1638
+ op.add_quote(url) if url
1639
+ else
1640
+ op.splice(path: true)
1537
1641
  end
1538
- cmd << '--force' if option('force')
1539
- append_head commit
1540
1642
  source
1541
1643
  end
1542
1644
 
@@ -1605,7 +1707,7 @@ module Squared
1605
1707
  cmd, opts = git_session('ls-remote', '--refs', opts: opts)
1606
1708
  cmd << "--#{flag}" unless flag == :remote
1607
1709
  op = OptionPartition.new(opts, OPT_GIT[:ls_remote], cmd, project: self)
1608
- op << shell_quote(remote) if remote
1710
+ op.add_quote(remote) if remote
1609
1711
  out, banner, from = source(io: true)
1610
1712
  print_item banner
1611
1713
  ret = write_lines(out, grep: op.extras, prefix: "refs/#{flag}/")
@@ -1615,6 +1717,7 @@ module Squared
1615
1717
  def ls_files(flag, opts = [])
1616
1718
  cmd, opts = git_session('ls-files', "--#{flag}", opts: opts)
1617
1719
  op = OptionPartition.new(opts, OPT_GIT[:ls_files], cmd, project: self)
1720
+ op.splice(path: true, pattern: true)
1618
1721
  out, banner, from = source(io: true)
1619
1722
  print_item banner
1620
1723
  ret = write_lines(out, grep: op.extras)
@@ -1623,10 +1726,16 @@ module Squared
1623
1726
 
1624
1727
  def git(flag, opts = [])
1625
1728
  cmd, opts = git_session(flag, opts: opts)
1626
- list = OPT_GIT[:git].fetch(flag, []) + OPT_GIT.fetch(flag, [])
1627
- op = OptionPartition.new(opts, list, cmd, project: self, no: OPT_GIT[:no][flag],
1628
- first: flag == :revert ? nil : matchpathspec)
1729
+ op = OptionPartition.new(opts, OPT_GIT[:git].fetch(flag, []) + OPT_GIT.fetch(flag, []), cmd,
1730
+ project: self, no: OPT_GIT[:no][flag], first: case flag
1731
+ when :blame, :revert
1732
+ nil
1733
+ else matchpathspec end)
1629
1734
  case flag
1735
+ when :blame
1736
+ raise_error 'no file found' unless (n = op.index { |s| (path + s).file? })
1737
+ op << '--' << shell_quote(path + op.delete_at(n))
1738
+ op.clear
1630
1739
  when :revert
1631
1740
  if VAL_GIT[:rebase][:send].any? { |val| op.arg?(val) }
1632
1741
  op.clear
@@ -1637,16 +1746,16 @@ module Squared
1637
1746
  end
1638
1747
  when :add, :clean
1639
1748
  if flag == :add && !op.arg?('pathspec-from-file')
1640
- grep, list = op.extras.partition { |val| val.start_with?(':') && val.end_with?(':') }
1641
- if list.empty? || !grep.empty?
1642
- red = color(:red)
1749
+ grep, list = op.partition { |val| OptionPartition.pattern?(val) }
1750
+ unless grep.empty? && !list.empty?
1643
1751
  grep.map! { |val| Regexp.new(val[1..-2]) }
1644
- files = status_data.map! do |a, b|
1645
- next if b.strip.empty? || (!grep.empty? && grep.none? { |pat| pat.match?(a) })
1752
+ files = [].tap do |out|
1753
+ status_data.each do |a, b|
1754
+ next if b.strip.empty? || (!grep.empty? && grep.none? { |pat| pat.match?(a) })
1646
1755
 
1647
- "#{sub_style(b, styles: red)} #{a}"
1756
+ out << "#{sub_style(b, styles: color(:red))} #{a}"
1757
+ end
1648
1758
  end
1649
- .compact
1650
1759
  unless files.empty?
1651
1760
  files = choice_index('Select files', files, multiple: true, force: true, trim: /^\S+\s/,
1652
1761
  accept: [['Add?', false, true]])
@@ -1654,7 +1763,7 @@ module Squared
1654
1763
  op.swap(list + files)
1655
1764
  end
1656
1765
  end
1657
- return source(git_session('status', '-s'), banner: false) unless append_pathspec(op.extras)
1766
+ return source(git_session('status -s'), banner: false) unless append_pathspec(op.extras)
1658
1767
 
1659
1768
  verbose = flag == :add && !op.arg?('verbose')
1660
1769
  print_success if success?(source) && verbose
@@ -1684,7 +1793,7 @@ module Squared
1684
1793
  private
1685
1794
 
1686
1795
  def source(cmd = @session, exception: true, io: false, sync: true, stdout: false, stderr: false, banner: true,
1687
- multiple: false, from: nil, **kwargs)
1796
+ multiple: false, hint: nil, from: nil, **kwargs)
1688
1797
  cmd = cmd.target if cmd.is_a?(OptionPartition)
1689
1798
  if io && banner == false
1690
1799
  from = nil
@@ -1702,7 +1811,8 @@ module Squared
1702
1811
  cmd = session_done cmd
1703
1812
  log&.info cmd
1704
1813
  banner = if banner
1705
- format_banner((banner.is_a?(String) ? banner : cmd).gsub(File.join(path, ''), ''), banner: true)
1814
+ banner = (banner.is_a?(String) ? banner : cmd).gsub(File.join(path, ''), '')
1815
+ format_banner(hint ? "#{banner} (#{hint})" : banner, banner: true)
1706
1816
  end
1707
1817
  on :first, from
1708
1818
  begin
@@ -1741,11 +1851,7 @@ module Squared
1741
1851
  end
1742
1852
  end
1743
1853
  rescue StandardError => e
1744
- log&.error e
1745
- ret = on(:error, from, e)
1746
- raise if exception && ret != true
1747
-
1748
- warn log_message(Logger::WARN, e, pass: true) if warning?
1854
+ on_error(e, from, pass: true)
1749
1855
  nil
1750
1856
  else
1751
1857
  on :last, from
@@ -1754,16 +1860,7 @@ module Squared
1754
1860
  end
1755
1861
 
1756
1862
  def write_lines(data, grep: [], prefix: nil, sub: nil, banner: nil, loglevel: nil, pass: false, first: false)
1757
- grep = unless grep.empty?
1758
- grep.map do |val|
1759
- if val.is_a?(Regexp)
1760
- val
1761
- else
1762
- val = ".*#{val}" if prefix && !val.sub!(/\A(\^|\\A)/, '')
1763
- Regexp.new("#{prefix}#{val == '*' ? '.+' : val}")
1764
- end
1765
- end
1766
- end
1863
+ grep = grep.empty? ? nil : matchmap(grep, prefix)
1767
1864
  sub = nil if stdin?
1768
1865
  ret = 0
1769
1866
  out = []
@@ -1837,8 +1934,7 @@ module Squared
1837
1934
  glob = kwargs.fetch(:include, [])
1838
1935
  pass = kwargs.fetch(:exclude, [])
1839
1936
  ret = {}
1840
- status_data(*args).each do |line|
1841
- file = line.first
1937
+ status_data(*args).each do |file,|
1842
1938
  next if !glob.empty? && glob.none? { |val| File.fnmatch?(val, file, File::FNM_DOTMATCH) }
1843
1939
  next if !pass.empty? && pass.any? { |val| File.fnmatch?(val, file, File::FNM_DOTMATCH) }
1844
1940
 
@@ -1848,17 +1944,17 @@ module Squared
1848
1944
  end
1849
1945
 
1850
1946
  def status_data(*args)
1851
- ret = []
1852
- git_spawn('status -z -uall', *args).split("\x0").each do |line|
1853
- next unless line =~ /^(.)(.) (.+)$/
1947
+ [].tap do |ret|
1948
+ git_spawn('status -z -uall', *args).split("\x0").each do |line|
1949
+ next unless line =~ /^(.)(.) (.+)$/
1854
1950
 
1855
- ret << [$3, $2, $1]
1951
+ ret << [$3, $2, $1]
1952
+ end
1856
1953
  end
1857
- ret
1858
1954
  end
1859
1955
 
1860
1956
  def append_pull(opts, list, target: @session, flag: nil, no: nil, remote: nil, from: nil)
1861
- target << '--force' if option('force', target: target)
1957
+ target << '--force' if option('force', 'f', target: target)
1862
1958
  append_submodules(target: target, from: from)
1863
1959
  return if !remote && opts.empty?
1864
1960
 
@@ -1976,12 +2072,11 @@ module Squared
1976
2072
  end
1977
2073
 
1978
2074
  def git_session(*cmd, opts: nil, worktree: true, **kwargs)
1979
- dir = worktree ? ["--work-tree=#{shell_quote(path)}", "--git-dir=#{shell_quote(gitpath)}"] : []
2075
+ dir = worktree ? [quote_option('work-tree', path), quote_option('git-dir', gitpath)] : []
1980
2076
  return session('git', *dir, *cmd, **kwargs) unless opts
1981
2077
 
1982
2078
  op = OptionPartition.new(opts, OPT_GIT[:common], dir, project: self)
1983
- ret = session('git', *op.to_a, *cmd, **kwargs)
1984
- [ret, op.extras]
2079
+ [session('git', *op.to_a, *cmd, **kwargs), op.extras]
1985
2080
  end
1986
2081
 
1987
2082
  def git_output(*cmd, **kwargs)