squared 0.4.16 → 0.4.18

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
@@ -74,7 +74,7 @@ module Squared
74
74
  @revdoc = JSON.parse(@revfile.read) if @revfile.exist?
75
75
  rescue StandardError => e
76
76
  @revfile = nil
77
- warn log_message(Logger::WARN, e, pass: true) if @warning
77
+ warn log_message(Logger::WARN, e, pass: true)
78
78
  self
79
79
  else
80
80
  @revdoc = {} unless @revdoc.is_a?(Hash)
@@ -244,7 +244,17 @@ module Squared
244
244
  pop: %w[index].freeze,
245
245
  apply: %w[index].freeze
246
246
  }.freeze,
247
- 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,
248
258
  tag: %w[n=im cleanup=b create-reflog i|ignore-case color=b? column=b contains=b? format=q merged=b?
249
259
  no-contains=b? no-merged=b? points-at=q sort=q].freeze,
250
260
  no: {
@@ -270,6 +280,8 @@ module Squared
270
280
  rev_parse: %w[flags].freeze,
271
281
  revert: %w[edit gpg-sign rerere-autoupdate].freeze,
272
282
  show: %w[standard-notes].freeze,
283
+ status: %w[ahead-behind column renames].freeze,
284
+ switch: %w[guess progress recurse-submodules track].freeze,
273
285
  tag: %w[column].freeze
274
286
  }.freeze
275
287
  }.freeze
@@ -289,12 +301,10 @@ module Squared
289
301
  include Rake::DSL
290
302
 
291
303
  def populate(ws, **)
292
- return if ws.series.exclude?(:pull, true)
293
-
294
- namespace(name = ws.task_name('git')) do
295
- all = ws.task_join(name, 'all')
304
+ return if ws.series.exclude?(:pull, true) || ws.size == 1
296
305
 
297
- 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?')
298
308
  task 'all' do |_, args|
299
309
  args = args.to_a
300
310
  cmd = if args.include?('stash')
@@ -311,6 +321,7 @@ module Squared
311
321
  cmd << ws.task_sync('build')
312
322
  Common::Utils.task_invoke(*cmd, **ws.invokeargs)
313
323
  end
324
+
314
325
  ws.series.sync << all
315
326
  ws.series.multiple << all
316
327
  end
@@ -332,25 +343,27 @@ module Squared
332
343
  'checkout' => %i[commit branch track detach path].freeze,
333
344
  'commit' => %i[add all amend amend-orig fixup].freeze,
334
345
  'diff' => %i[head branch files view between contain].freeze,
335
- 'fetch' => %i[origin remote].freeze,
346
+ 'fetch' => %i[origin remote all].freeze,
336
347
  'files' => %i[cached modified deleted others].freeze,
337
- 'git' => %i[add blame clean mv revert rm].freeze,
348
+ 'git' => %i[add blame clean mv revert rm status].freeze,
338
349
  'log' => %i[view between contain].freeze,
339
350
  'merge' => %i[commit no-commit send].freeze,
340
- 'pull' => %i[origin remote].freeze,
351
+ 'pull' => %i[origin remote all].freeze,
341
352
  'rebase' => %i[branch onto send].freeze,
342
353
  'refs' => %i[heads tags remote].freeze,
343
- 'reset' => %i[commit index patch mode].freeze,
354
+ 'reset' => %i[commit index patch mode undo].freeze,
344
355
  'restore' => %i[source staged worktree].freeze,
345
356
  'rev' => %i[commit build output].freeze,
346
357
  'show' => %i[format oneline textconv].freeze,
347
358
  'stash' => %i[push pop apply branch drop clear list].freeze,
348
- 'switch' => %i[create detach merge].freeze,
359
+ 'submodule' => %i[status update branch url sync].freeze,
360
+ 'switch' => %i[branch create detach].freeze,
349
361
  'tag' => %i[add sign delete list].freeze
350
362
  })
351
363
 
352
364
  def initialize(*, **)
353
365
  super
366
+ @submodule = basepath('.gitmodules').exist?
354
367
  initialize_ref Git.ref if gitpath.exist?
355
368
  end
356
369
 
@@ -360,7 +373,7 @@ module Squared
360
373
 
361
374
  def populate(*, **)
362
375
  super
363
- return unless ref?(Git.ref)
376
+ return unless ref?(Git.ref) || @only
364
377
 
365
378
  namespace name do
366
379
  Git.subtasks do |action, flags|
@@ -382,11 +395,34 @@ module Squared
382
395
  __send__(action, flag, args, remote: remote)
383
396
  end
384
397
  else
385
- format_desc action, flag, 'opts*'
398
+ format_desc(action, flag, 'opts*', after: flag == :all && action == 'pull' ? 'pattern*' : nil)
386
399
  task flag do |_, args|
387
400
  __send__ action, flag, args.to_a
388
401
  end
389
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
390
426
  when 'commit'
391
427
  case flag
392
428
  when :all
@@ -587,12 +623,12 @@ module Squared
587
623
  format_desc action, flag, 'ref,opts*'
588
624
  task flag, [:commit] do |_, args|
589
625
  commit = commithead args.commit
590
- if commit
591
- args = args.extras
592
- else
593
- commit, opts = choice_commit(values: ['Options'])
594
- args = OptionPartition.strip(opts)
595
- end
626
+ args = if commit
627
+ args.extras
628
+ else
629
+ commit, opts = choice_commit(values: ['Options'])
630
+ OptionPartition.strip(opts)
631
+ end
596
632
  checkout(flag, args, commit: commit)
597
633
  end
598
634
  when :detach
@@ -682,10 +718,10 @@ module Squared
682
718
  when :create
683
719
  format_desc action, flag, '(^)name,ref?=HEAD|:'
684
720
  task flag, [:name, :commit] do |_, args|
685
- target = param_guard(action, flag, args: args, key: :name)
721
+ branch = param_guard(action, flag, args: args, key: :name)
686
722
  commit = commithead args.commit
687
723
  commit, track = choice_commit(values: ['Track? [Y|n]'], force: false) if commit == ':'
688
- switch(flag, target: target, commit: commit, track: track)
724
+ switch(flag, branch: branch, commit: commit, track: track)
689
725
  end
690
726
  when :detach
691
727
  format_desc action, flag, 'ref?=HEAD'
@@ -693,11 +729,16 @@ module Squared
693
729
  commit = commithead(args.commit) || choice_commit(force: false)
694
730
  switch(flag, commit: commit)
695
731
  end
696
- when :merge
697
- format_desc action, flag, 'branch?'
698
- task flag, [:branch] do |_, args|
699
- commit = args.branch || choice_refs('Choose a branch')
700
- 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'))
701
742
  end
702
743
  end
703
744
  when 'reset'
@@ -718,10 +759,10 @@ module Squared
718
759
  end
719
760
  print_success if success?(reset(flag, args, commit: commit))
720
761
  end
721
- when :index
722
- format_desc action, flag, 'opts*,pathspec*'
762
+ when :index, :undo
763
+ format_desc(action, flag, flag == :index ? 'opts*,pathspec*' : nil)
723
764
  task flag do |_, args|
724
- reset flag, args.to_a
765
+ reset(flag, flag == :index ? args.to_a : [])
725
766
  end
726
767
  when :mode
727
768
  format_desc action, flag, 'mode,ref?=HEAD|:'
@@ -763,28 +804,28 @@ module Squared
763
804
  when :branch
764
805
  format_desc action, flag, 'upstream,branch?=HEAD,opts*'
765
806
  task flag, [:upstream] do |_, args|
766
- if (upstream = args.upstream)
767
- args = args.extras
768
- else
769
- upstream, opts = choice_refs('Choose upstream branch', values: ['Options'])
770
- args = OptionPartition.strip(opts)
771
- end
807
+ args = if (upstream = args.upstream)
808
+ args.extras
809
+ else
810
+ upstream, opts = choice_refs('Choose upstream branch', values: ['Options'])
811
+ OptionPartition.strip(opts)
812
+ end
772
813
  rebase(flag, args, upstream: upstream)
773
814
  end
774
815
  when :onto
775
816
  format_desc action, flag, 'ref,upstream,branch?=HEAD'
776
817
  task flag, [:commit, :upstream, :branch] do |_, args|
777
818
  commit = commithead args.commit
778
- if commit
779
- upstream = param_guard(action, flag, args: args, key: :upstream)
780
- branch = args.branch
781
- args = []
782
- else
783
- commit = choice_refs 'Choose "onto" branch'
784
- target, opts = choice_commit(multiple: 2, values: ['Options'], reflog: false)
785
- branch, upstream = target
786
- args = OptionPartition.strip(opts)
787
- end
819
+ args = if commit
820
+ upstream = param_guard(action, flag, args: args, key: :upstream)
821
+ branch = args.branch
822
+ []
823
+ else
824
+ commit = choice_refs 'Choose "onto" branch'
825
+ target, opts = choice_commit(multiple: 2, values: ['Options'], reflog: false)
826
+ branch, upstream = target
827
+ OptionPartition.strip(opts)
828
+ end
788
829
  rebase(flag, args, commit: commit, upstream: upstream, branch: branch)
789
830
  end
790
831
  when :commit, :'no-commit'
@@ -820,7 +861,7 @@ module Squared
820
861
  rev_parse(flag, ref: ref, size: size)
821
862
  end
822
863
  when :build
823
- format_desc action, flag, OPT_GIT[:status]
864
+ format_desc action, flag, 'opts*'
824
865
  task flag do |_, args|
825
866
  revbuild flag, args.to_a
826
867
  end
@@ -837,7 +878,7 @@ module Squared
837
878
  ls_remote(flag, args.extras, remote: args.remote)
838
879
  end
839
880
  else
840
- format_desc action, flag, 'opts*,pattern*'
881
+ format_desc(action, flag, 'opts*,pattern*', after: action == 'files' ? 'pathspec*' : nil)
841
882
  task flag do |_, args|
842
883
  __send__(action == 'refs' ? :ls_remote : :ls_files, flag, args.to_a)
843
884
  end
@@ -848,13 +889,13 @@ module Squared
848
889
  format_desc action, flag, 'ref,opts*,pathspec*'
849
890
  task flag, [:commit] do |_, args|
850
891
  commit = commithead args.commit
851
- if commit
852
- args = args.extras
853
- else
854
- commit, opts, files = choice_commit(values: ['Options', ['Pathspec', true]])
855
- args = OptionPartition.strip(opts)
856
- files = files&.shellsplit
857
- end
892
+ args = if commit
893
+ args.extras
894
+ else
895
+ commit, opts, files = choice_commit(values: ['Options', ['Pathspec', true]])
896
+ files = files&.shellsplit
897
+ OptionPartition.strip(opts)
898
+ end
858
899
  restore(flag, args, commit: commit, files: files)
859
900
  end
860
901
  when :staged, :worktree
@@ -882,10 +923,13 @@ module Squared
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,13 +953,12 @@ 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)
958
+ cmd << '--autostash' if option('autostash')
914
959
  if flag == :rebase
915
960
  cmd << '--rebase'
916
- cmd << '--autostash' if option('autostash')
917
961
  else
918
- cmd << '--autostash' if flag == :autostash
919
962
  if (val = option('rebase', ignore: false))
920
963
  cmd << case val
921
964
  when '0', 'false'
@@ -924,15 +967,51 @@ module Squared
924
967
  VAL_GIT[:rebase][:value].include?(val) ? basic_option('rebase', val) : '--rebase'
925
968
  end
926
969
  end
970
+ case flag
971
+ when :all
972
+ unless git_spawn('status -s -z --untracked-files=all').empty?
973
+ if confirm('Stash local changes? [Y/n] ', 'Y')
974
+ git_spawn 'stash push --keep-index --quiet'
975
+ elsif !(force = confirm('Force checkout? [y/N] ', 'N'))
976
+ return
977
+ end
978
+ end
979
+ op = OptionPartition.new(opts, OPT_GIT[:pull], cmd, project: self, no: OPT_GIT[:no][:pull])
980
+ reg = if op.empty?
981
+ []
982
+ else
983
+ opts = op.uniq(opts)
984
+ matchmap op
985
+ end
986
+ session_done op.target
987
+ heads = []
988
+ cur = nil
989
+ foreachref('heads', format: '%(if)%(HEAD)%(then)* %(end)%(refname:short)').each do |line|
990
+ line.chomp!
991
+ cur ||= line.delete_prefix!('* ')
992
+ heads << line if matchany?(line, reg)
993
+ end
994
+ raise_error('head not found', hint: 'for-each-ref') unless cur
995
+ opts << 'ff-only' if opts.empty? && !option('ff-only', equals: '0')
996
+ (heads.dup << cur).each_with_index do |branch, index|
997
+ next unless (index < heads.size && cur != branch) || index == heads.size
998
+
999
+ git_spawn 'switch --quiet', force && '--force', shell_quote(branch)
1000
+ pull(nil, opts, sync: false, hint: branch) if heads.include?(branch)
1001
+ end
1002
+ return
1003
+ when :autostash
1004
+ cmd << '--autostash'
1005
+ end
927
1006
  end
928
1007
  append_pull(opts, OPT_GIT[:pull] + OPT_GIT[:fetch][:pull],
929
1008
  no: OPT_GIT[:no][:pull] + OPT_GIT[:no][:fetch][:pull], remote: remote, flag: flag, from: :pull)
930
- source(sync: sync, sub: if verbose
1009
+ source(sync: sync, sub: if stdout?
931
1010
  [
932
1011
  { pat: /^(.+)(\|\s+\d+\s+)([^-]*)(-+)(.*)$/, styles: color(:red), index: 4 },
933
1012
  { pat: /^(.+)(\|\s+\d+\s+)(\++)(.*)$/, styles: color(:green), index: 3 }
934
1013
  ]
935
- end, **threadargs)
1014
+ end, hint: hint, **threadargs)
936
1015
  end
937
1016
 
938
1017
  def rebase(flag = nil, opts = [], sync: invoked_sync?('rebase', flag), commit: nil, upstream: nil, branch: nil,
@@ -956,6 +1035,10 @@ module Squared
956
1035
  cmd << upstream
957
1036
  append_head branch
958
1037
  else
1038
+ unless gitpath('REBASE_HEAD').exist?
1039
+ puts log_message(Logger::INFO, name, 'no rebase in progress', hint: command) if stdout?
1040
+ return
1041
+ end
959
1042
  return unless VAL_GIT[:rebase][:send].include?(command)
960
1043
 
961
1044
  cmd << "--#{command}"
@@ -969,6 +1052,7 @@ module Squared
969
1052
 
970
1053
  def fetch(flag = nil, opts = [], sync: invoked_sync?('fetch', flag), remote: nil)
971
1054
  opts = git_session('fetch', opts: opts).last
1055
+ opts << 'all' if flag == :all || option('all')
972
1056
  append_pull(opts, collect_hash(OPT_GIT[:fetch]), no: collect_hash(OPT_GIT[:no][:fetch]),
973
1057
  remote: remote, flag: flag, from: :fetch)
974
1058
  source(sync: sync, **threadargs)
@@ -1008,15 +1092,15 @@ module Squared
1008
1092
  cmd, opts = git_session('stash', flag, opts: opts)
1009
1093
  list = OPT_GIT[:stash][:common] + OPT_GIT[:stash].fetch(flag, [])
1010
1094
  if flag == :list
1011
- list += collect_hash(OPT_GIT[:log])
1012
- no = collect_hash(OPT_GIT[:no][:log])
1095
+ list += collect_hash OPT_GIT[:log]
1096
+ no = collect_hash OPT_GIT[:no][:log]
1013
1097
  end
1014
1098
  op = OptionPartition.new(opts, list, cmd, project: self, no: no, first: flag == :push ? matchpathspec : nil)
1015
1099
  case flag
1016
1100
  when :push
1017
1101
  append_pathspec op.extras
1018
1102
  when :pop, :apply, :drop, :branch
1019
- if op.extras.delete(':')
1103
+ if op.remove(':')
1020
1104
  if flag == :branch
1021
1105
  if op.empty?
1022
1106
  values = [['Branch name', true]]
@@ -1051,30 +1135,35 @@ module Squared
1051
1135
  end
1052
1136
  else
1053
1137
  git_session('stash', 'push', opts: opts)
1054
- append_option(OPT_GIT[:stash][:push].grep_v(/[=|]/), no: true, ignore: false)
1138
+ append_option(OptionPartition.select(OPT_GIT[:stash][:push], no: false), no: true, ignore: false)
1055
1139
  append_message
1056
1140
  end
1057
1141
  source(banner: !quiet?, sync: sync, **threadargs)
1058
1142
  end
1059
1143
 
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)
1144
+ def status(flag = nil, opts = [])
1145
+ cmd, opts = git_session('status', opts: opts)
1146
+ if flag
1147
+ op = OptionPartition.new(opts, OPT_GIT[:status], cmd, project: self, no: OPT_GIT[:no][:status])
1148
+ append_pathspec op.extras
1149
+ else
1150
+ cmd << (option('long') ? '--long' : '--short')
1151
+ cmd << '--branch' if option('branch')
1152
+ if (val = option('ignore-submodules', ignore: false))
1153
+ cmd << basic_option('ignore-submodules', case val
1154
+ when '0', 'none'
1155
+ 'none'
1156
+ when '1', 'untracked'
1157
+ 'untracked'
1158
+ when '2', 'dirty'
1159
+ 'dirty'
1160
+ else
1161
+ 'all'
1162
+ end)
1163
+ end
1164
+ append_pathspec
1075
1165
  end
1076
- append_pathspec
1077
- if verbose
1166
+ if stdout?
1078
1167
  r = color(:red)
1079
1168
  g = color(:green)
1080
1169
  sub = if session_arg?('short')
@@ -1115,7 +1204,7 @@ module Squared
1115
1204
  kwargs = kwargs.key?(:include) || kwargs.key?(:exclude) ? statusargs.call : @revbuild || {}
1116
1205
  case flag
1117
1206
  when :build
1118
- op = OptionPartition.new(opts, OPT_GIT[:status], project: self)
1207
+ op = OptionPartition.new(opts, %w[ignore-submodules=b? ignored=b? untracked-files=b?], project: self)
1119
1208
  op.clear(append: true)
1120
1209
  args = op.to_a
1121
1210
  else
@@ -1127,7 +1216,7 @@ module Squared
1127
1216
  if (cur = workspace.rev_entry(name)) && cur['revision'] == sha && !env('REVBUILD_FORCE')
1128
1217
  files = status_digest(*args, **kwargs)
1129
1218
  if cur['files'].size == files.size && cur['files'].find { |key, val| files[key] != val }.nil?
1130
- if verbose
1219
+ if stdout?
1131
1220
  if (since = workspace.rev_timesince(name, 'build'))
1132
1221
  puts log_message(Logger::INFO, name, 'no changes', subject: 'revbuild', hint: "#{since} ago")
1133
1222
  else
@@ -1172,6 +1261,9 @@ module Squared
1172
1261
  when :patch
1173
1262
  cmd << '--patch'
1174
1263
  append_pathspec(refs, pass: false)
1264
+ when :undo
1265
+ cmd << '--hard HEAD@{1}'
1266
+ ref = false
1175
1267
  end
1176
1268
  unless ref == false
1177
1269
  append_commit(ref, head: true)
@@ -1182,7 +1274,7 @@ module Squared
1182
1274
 
1183
1275
  def checkout(flag, opts = [], branch: nil, origin: nil, create: nil, commit: nil, detach: nil, merge: false)
1184
1276
  cmd, opts = git_session('checkout', opts: opts)
1185
- append_option 'force', 'merge'
1277
+ append_option 'force', 'f', 'merge'
1186
1278
  case flag
1187
1279
  when :branch
1188
1280
  cmd << '--detach' if detach == 'd' || option('detach')
@@ -1224,7 +1316,7 @@ module Squared
1224
1316
  elsif !session_arg?('s', 'sign', 'u', 'local-user')
1225
1317
  cmd << '--annotate'
1226
1318
  end
1227
- cmd << '--force' if option('force')
1319
+ cmd << '--force' if option('force', 'f')
1228
1320
  if !commit && message && (sha = commithash(message))
1229
1321
  commit = sha
1230
1322
  message = nil
@@ -1255,7 +1347,7 @@ module Squared
1255
1347
  first: matchpathspec)
1256
1348
  case flag
1257
1349
  when :between, :contain
1258
- op << shell_quote(range.join(flag == :between ? '..' : '...'))
1350
+ op.add_quote(range.join(flag == :between ? '..' : '...'))
1259
1351
  else
1260
1352
  op.merge(index)
1261
1353
  end
@@ -1278,19 +1370,18 @@ module Squared
1278
1370
  op << '--no-index'
1279
1371
  append_pathspec(refs, parent: true)
1280
1372
  else
1373
+ op << '--merge-base' if option('merge-base')
1281
1374
  case flag
1282
1375
  when :view
1283
- op << '--merge-base' if option('merge-base')
1284
1376
  op.merge(range)
1285
1377
  when :between, :contain
1286
1378
  op.delete('--merge-base')
1287
- op << shell_quote(range.join(flag == :between ? '..' : '...'))
1379
+ op.add_quote(range.join(flag == :between ? '..' : '...'))
1288
1380
  else
1289
- op << '--merge-base' if option('merge-base')
1290
- op << shell_quote(branch) if branch
1381
+ op.add_quote(branch) if branch
1291
1382
  if !index.empty?
1292
1383
  if op.arg?('cached')
1293
- raise_error("one commit only: #{index.join(', ')}", hint: '--cached') if index.size > 1
1384
+ raise_error("one commit only: #{index.join(', ')}", hint: 'cached') if index.size > 1
1294
1385
  op << index.first
1295
1386
  else
1296
1387
  op.merge(index)
@@ -1342,16 +1433,16 @@ module Squared
1342
1433
  branch, origin, hint = line.split('...')
1343
1434
  if hint && !hint.match?(/^\[(\D+0,\D+0)\]$/)
1344
1435
  raise_error('work tree is not usable', hint: hint[1..-2])
1345
- elsif origin.empty?
1436
+ elsif !origin || origin.empty?
1346
1437
  return nil if pass
1347
1438
  break if dryrun
1348
1439
 
1349
1440
  unless (origin = option('upstream', prefix: 'git', ignore: false))
1350
1441
  if (origin = choice_refs('Choose an upstream', 'remotes', attempts: 1, force: false))
1351
1442
  git_spawn 'branch', quote_option('set-upstream-to', origin)
1352
- else
1353
- origin = readline('Enter an upstream', force: true)
1443
+ break
1354
1444
  end
1445
+ origin = readline('Enter an upstream', force: true)
1355
1446
  end
1356
1447
  raise_error('missing remote name', hint: origin) unless origin.include?('/')
1357
1448
  upstream = true
@@ -1398,6 +1489,10 @@ module Squared
1398
1489
  append_commit(*op.extras)
1399
1490
  end
1400
1491
  else
1492
+ unless gitpath('MERGE_HEAD').exist?
1493
+ puts log_message(Logger::INFO, name, 'no merge in progress', hint: command) if stdout?
1494
+ return
1495
+ end
1401
1496
  return unless VAL_GIT[:merge][:send].include?(command)
1402
1497
 
1403
1498
  cmd << "--#{command}"
@@ -1420,7 +1515,7 @@ module Squared
1420
1515
  '--track'
1421
1516
  end
1422
1517
  end
1423
- cmd << '--force' if option('force')
1518
+ cmd << '--force' if option('force', 'f')
1424
1519
  cmd << shell_quote(target)
1425
1520
  cmd << shell_quote(ref) if ref
1426
1521
  when :track
@@ -1435,14 +1530,12 @@ module Squared
1435
1530
  end
1436
1531
  when :delete
1437
1532
  remote&.each do |val|
1438
- source git_output('push', '--delete', *val.split('/', 2).map { |s| shell_quote(s) })
1533
+ source git_output('push --delete', *val.split('/', 2).map { |s| shell_quote(s) })
1439
1534
  end
1440
1535
  force, list = refs.partition { |val| val.match?(/^[~^]/) }
1441
1536
  force.each do |val|
1442
- dr = val[0, 2]
1443
- d = dr.include?('^') ? '-D' : '-d'
1444
- r = '-r' if dr.include?('~')
1445
- source git_output('branch', d, r, shell_quote(val.sub(/^[~^]+/, '')))
1537
+ r = '-r' if val.delete!('~')
1538
+ source git_output('branch', val.delete!('^') ? '-D' : '-d', r, shell_quote(val))
1446
1539
  end
1447
1540
  return if list.empty?
1448
1541
 
@@ -1450,8 +1543,9 @@ module Squared
1450
1543
  append_value list
1451
1544
  remote = nil
1452
1545
  when :move, :copy
1453
- flag = "-#{flag.to_s[0]}"
1454
- cmd << (option('force') ? flag.upcase : flag)
1546
+ s = "-#{flag.to_s[0]}"
1547
+ s.upcase! if option('force', 'f')
1548
+ cmd << s
1455
1549
  refs.compact.each { |val| cmd << shell_quote(val) }
1456
1550
  stdout = true
1457
1551
  when :current
@@ -1461,7 +1555,7 @@ module Squared
1461
1555
  when :list
1462
1556
  op = OptionPartition.new(opts, OPT_GIT[:branch], cmd << '--list',
1463
1557
  project: self, no: OPT_GIT[:no][:branch], single: /\Av+\z/)
1464
- op.each { |val| op << shell_quote(val) }
1558
+ op.each { |val| op.add_quote(val) }
1465
1559
  out, banner, from = source(io: true)
1466
1560
  print_item banner
1467
1561
  ret = write_lines(out, sub: [
@@ -1471,12 +1565,12 @@ module Squared
1471
1565
  list_result(ret, 'branches', from: from)
1472
1566
  return
1473
1567
  else
1474
- head = git_spawn('rev-parse --abbrev-ref HEAD').chomp
1475
- if head.empty?
1568
+ if (head = git_spawn('rev-parse --abbrev-ref HEAD').chomp).empty?
1476
1569
  ret = 0
1477
1570
  else
1478
1571
  git_spawn 'fetch --all --prune --quiet' if option('sync')
1479
- out, banner, from = source(cmd << '-vv --no-abbrev --list', io: true)
1572
+ cmd << '-vv --no-abbrev --list'
1573
+ out, banner, from = source(io: true)
1480
1574
  ret = write_lines(out, grep: [/^\*\s+#{Regexp.escape(head)}\s/], banner: banner, first: true) do |line|
1481
1575
  next line if stdin?
1482
1576
 
@@ -1507,33 +1601,55 @@ module Squared
1507
1601
  if !ref
1508
1602
  print_success if flag == :create
1509
1603
  elsif remote && target
1510
- source git_output('push', '-u', shell_quote(ref.split('/', 2).first), shell_quote(target))
1604
+ source git_output('push -u', shell_quote(ref.split('/', 2).first), shell_quote(target))
1511
1605
  end
1512
1606
  end
1513
1607
 
1514
- def switch(flag, opts = [], target: nil, commit: nil, track: nil)
1515
- cmd = git_session('switch', opts: opts).first
1516
- case flag
1517
- when :create
1518
- c = 'c'
1519
- if target.start_with?('^')
1520
- target = target[1..-1]
1521
- c = c.upcase
1608
+ def switch(flag, opts = [], branch: nil, commit: nil, track: nil)
1609
+ cmd, opts = git_session('switch', opts: opts)
1610
+ cmd << '--force' if option('force', 'f')
1611
+ if flag == :branch
1612
+ op = OptionPartition.new(opts, OPT_GIT[:switch], cmd, project: self, no: OPT_GIT[:no][:switch])
1613
+ op.add_quote(branch)
1614
+ else
1615
+ case flag
1616
+ when :create
1617
+ c = 'c'
1618
+ if target.start_with?('^')
1619
+ target = target[1..-1]
1620
+ c = c.upcase
1621
+ end
1622
+ cmd << quote_option(c, target)
1623
+ cmd << case (track ||= option('track', ignore: false))
1624
+ when 'n', 'N', '0', 'false'
1625
+ '--no-track'
1626
+ when 'y', 'Y', '1', 'true'
1627
+ '--track'
1628
+ when 'direct', 'inherit'
1629
+ basic_option 'track', track
1630
+ end
1631
+ when :detach
1632
+ cmd << "--#{flag}"
1522
1633
  end
1523
- cmd << quote_option(c, target)
1524
- cmd << case (track ||= option('track', ignore: false))
1525
- when 'n', 'N', '0', 'false'
1526
- '--no-track'
1527
- when 'y', 'Y', '1', 'true'
1528
- '--track'
1529
- when 'direct', 'inherit'
1530
- basic_option('track', track)
1531
- end
1532
- when :detach, :merge
1533
- cmd << "--#{flag}"
1634
+ append_head commit
1635
+ end
1636
+ source
1637
+ end
1638
+
1639
+ def submodule(flag, opts = [], path: nil, url: nil)
1640
+ cmd, opts = git_session('submodule', opts: opts)
1641
+ op = OptionPartition.new(opts, OPT_GIT[:submodule].fetch(flag, []), cmd, project: self)
1642
+ case flag
1643
+ when :branch, :url
1644
+ op.adjoin("set-#{flag}")
1645
+ op << '--'
1646
+ op.add_path(path) if path
1647
+ op.add_quote(url) if url
1648
+ else
1649
+ op.adjoin(flag)
1650
+ op << '--recursive' if option('recursive', 'r')
1651
+ op.splice(path: true)
1534
1652
  end
1535
- cmd << '--force' if option('force')
1536
- append_head commit
1537
1653
  source
1538
1654
  end
1539
1655
 
@@ -1556,19 +1672,15 @@ module Squared
1556
1672
  source(banner: false)
1557
1673
  return
1558
1674
  when :oneline
1559
- format = 'oneline'
1675
+ format = flag.to_s
1560
1676
  end
1561
- if format
1562
- case (val = format.downcase)
1563
- when 'oneline', 'short', 'medium', 'full', 'fuller', 'reference', 'email', 'raw'
1564
- cmd << basic_option('format', val)
1565
- else
1566
- if format.match?(/^t?format:/) || format.include?('%')
1567
- cmd << quote_option('pretty', format)
1568
- else
1569
- opts << format
1570
- end
1571
- end
1677
+ case format
1678
+ when 'oneline', 'short', 'medium', 'full', 'fuller', 'reference', 'email', 'raw'
1679
+ cmd << basic_option('format', format)
1680
+ when /(?:^t?format:|%)/
1681
+ cmd << quote_option('pretty', format)
1682
+ else
1683
+ opts << format if format
1572
1684
  end
1573
1685
  op = OptionPartition.new(opts, OPT_GIT[:show] + OPT_GIT[:diff][:show] + OPT_GIT[:log][:diff], cmd,
1574
1686
  project: self,
@@ -1602,7 +1714,7 @@ module Squared
1602
1714
  cmd, opts = git_session('ls-remote', '--refs', opts: opts)
1603
1715
  cmd << "--#{flag}" unless flag == :remote
1604
1716
  op = OptionPartition.new(opts, OPT_GIT[:ls_remote], cmd, project: self)
1605
- op << shell_quote(remote) if remote
1717
+ op.add_quote(remote) if remote
1606
1718
  out, banner, from = source(io: true)
1607
1719
  print_item banner
1608
1720
  ret = write_lines(out, grep: op.extras, prefix: "refs/#{flag}/")
@@ -1612,6 +1724,7 @@ module Squared
1612
1724
  def ls_files(flag, opts = [])
1613
1725
  cmd, opts = git_session('ls-files', "--#{flag}", opts: opts)
1614
1726
  op = OptionPartition.new(opts, OPT_GIT[:ls_files], cmd, project: self)
1727
+ op.splice(path: true, pattern: true)
1615
1728
  out, banner, from = source(io: true)
1616
1729
  print_item banner
1617
1730
  ret = write_lines(out, grep: op.extras)
@@ -1640,15 +1753,16 @@ module Squared
1640
1753
  end
1641
1754
  when :add, :clean
1642
1755
  if flag == :add && !op.arg?('pathspec-from-file')
1643
- grep, list = op.partition { |val| val.start_with?(':') && val.end_with?(':') }
1756
+ grep, list = op.partition { |val| OptionPartition.pattern?(val) }
1644
1757
  unless grep.empty? && !list.empty?
1645
1758
  grep.map! { |val| Regexp.new(val[1..-2]) }
1646
- files = status_data.map! do |a, b|
1647
- next if b.strip.empty? || (!grep.empty? && grep.none? { |pat| pat.match?(a) })
1759
+ files = [].tap do |out|
1760
+ status_data.each do |a, b|
1761
+ next if b.strip.empty? || (!grep.empty? && grep.none? { |pat| pat.match?(a) })
1648
1762
 
1649
- "#{sub_style(b, styles: color(:red))} #{a}"
1763
+ out << "#{sub_style(b, styles: color(:red))} #{a}"
1764
+ end
1650
1765
  end
1651
- .compact
1652
1766
  unless files.empty?
1653
1767
  files = choice_index('Select files', files, multiple: true, force: true, trim: /^\S+\s/,
1654
1768
  accept: [['Add?', false, true]])
@@ -1656,7 +1770,7 @@ module Squared
1656
1770
  op.swap(list + files)
1657
1771
  end
1658
1772
  end
1659
- return source(git_session('status', '-s'), banner: false) unless append_pathspec(op.extras)
1773
+ return source(git_session('status -s'), banner: false) unless append_pathspec(op.extras)
1660
1774
 
1661
1775
  verbose = flag == :add && !op.arg?('verbose')
1662
1776
  print_success if success?(source) && verbose
@@ -1686,7 +1800,7 @@ module Squared
1686
1800
  private
1687
1801
 
1688
1802
  def source(cmd = @session, exception: true, io: false, sync: true, stdout: false, stderr: false, banner: true,
1689
- multiple: false, from: nil, **kwargs)
1803
+ multiple: false, hint: nil, from: nil, send: :system, **kwargs)
1690
1804
  cmd = cmd.target if cmd.is_a?(OptionPartition)
1691
1805
  if io && banner == false
1692
1806
  from = nil
@@ -1704,7 +1818,8 @@ module Squared
1704
1818
  cmd = session_done cmd
1705
1819
  log&.info cmd
1706
1820
  banner = if banner
1707
- format_banner((banner.is_a?(String) ? banner : cmd).gsub(File.join(path, ''), ''), banner: true)
1821
+ banner = (banner.is_a?(String) ? banner : cmd).gsub(File.join(path, ''), '')
1822
+ format_banner(hint ? "#{banner} (#{hint})" : banner, banner: true)
1708
1823
  end
1709
1824
  on :first, from
1710
1825
  begin
@@ -1725,7 +1840,7 @@ module Squared
1725
1840
  end
1726
1841
  elsif !kwargs[:sub] && (sync || (!exception && !stderr))
1727
1842
  print_item banner unless multiple
1728
- ret = shell(cmd, exception: exception)
1843
+ ret = shell(cmd, name: send, exception: exception)
1729
1844
  else
1730
1845
  require 'open3'
1731
1846
  if stderr
@@ -1752,16 +1867,7 @@ module Squared
1752
1867
  end
1753
1868
 
1754
1869
  def write_lines(data, grep: [], prefix: nil, sub: nil, banner: nil, loglevel: nil, pass: false, first: false)
1755
- grep = unless grep.empty?
1756
- grep.map do |val|
1757
- if val.is_a?(Regexp)
1758
- val
1759
- else
1760
- val = ".*#{val}" if prefix && !val.sub!(/\A(\^|\\A)/, '')
1761
- Regexp.new("#{prefix}#{val == '*' ? '.+' : val}")
1762
- end
1763
- end
1764
- end
1870
+ grep = grep.empty? ? nil : matchmap(grep, prefix)
1765
1871
  sub = nil if stdin?
1766
1872
  ret = 0
1767
1873
  out = []
@@ -1792,7 +1898,7 @@ module Squared
1792
1898
  def list_result(size, type, grep: [], action: 'found', from: nil)
1793
1899
  if size == 0
1794
1900
  puts empty_status("No #{type} were #{action}", 'grep', grep.join(', '))
1795
- elsif verbose
1901
+ elsif stdout?
1796
1902
  styles = theme.fetch(:banner, []).reject { |s| s.to_s.end_with?('!') }
1797
1903
  styles << :bold if styles.size <= 1
1798
1904
  puts print_footer("#{size} #{size == 1 ? type.sub(/(?:(?<!l)e)?s\z/, '') : type}",
@@ -1835,8 +1941,7 @@ module Squared
1835
1941
  glob = kwargs.fetch(:include, [])
1836
1942
  pass = kwargs.fetch(:exclude, [])
1837
1943
  ret = {}
1838
- status_data(*args).each do |line|
1839
- file = line.first
1944
+ status_data(*args).each do |file,|
1840
1945
  next if !glob.empty? && glob.none? { |val| File.fnmatch?(val, file, File::FNM_DOTMATCH) }
1841
1946
  next if !pass.empty? && pass.any? { |val| File.fnmatch?(val, file, File::FNM_DOTMATCH) }
1842
1947
 
@@ -1846,17 +1951,17 @@ module Squared
1846
1951
  end
1847
1952
 
1848
1953
  def status_data(*args)
1849
- ret = []
1850
- git_spawn('status -z -uall', *args).split("\x0").each do |line|
1851
- next unless line =~ /^(.)(.) (.+)$/
1954
+ [].tap do |ret|
1955
+ git_spawn('status -z -uall', *args).split("\x0").each do |line|
1956
+ next unless line =~ /^(.)(.) (.+)$/
1852
1957
 
1853
- ret << [$3, $2, $1]
1958
+ ret << [$3, $2, $1]
1959
+ end
1854
1960
  end
1855
- ret
1856
1961
  end
1857
1962
 
1858
1963
  def append_pull(opts, list, target: @session, flag: nil, no: nil, remote: nil, from: nil)
1859
- target << '--force' if option('force', target: target)
1964
+ target << '--force' if option('force', 'f', target: target)
1860
1965
  append_submodules(target: target, from: from)
1861
1966
  return if !remote && opts.empty?
1862
1967
 
@@ -1868,11 +1973,9 @@ module Squared
1868
1973
  when 'rebase'
1869
1974
  op << basic_option($1, $2) if VAL_GIT[:rebase][:value].include?($2)
1870
1975
  when 'shallow-since'
1871
- next unless (val = Date.parse($2))
1872
-
1873
- op << quote_option($1, val.strftime('%F %T'))
1976
+ op.append?($1) { Date.parse($2)&.strftime('%F %T') }
1874
1977
  when 'recurse-submodules'
1875
- op << basic_option($1, $2) unless op.arg?('recurse-submodules')
1978
+ op.append?($1, $2, type: :basic)
1876
1979
  when 'refspec'
1877
1980
  refspec << shell_escape($2, quote: true)
1878
1981
  end
@@ -1882,7 +1985,7 @@ module Squared
1882
1985
  op.errors << opt
1883
1986
  end
1884
1987
  end
1885
- op << '--verbose' if (flag || from == :fetch) && verbose && !op.arg?('quiet')
1988
+ op << '--verbose' if (flag || from == :fetch) && stdout? && !op.arg?('quiet')
1886
1989
  if remote
1887
1990
  op.append(remote, delim: true)
1888
1991
  if (val = option('refspec', target: target, strict: true))
@@ -1895,7 +1998,7 @@ module Squared
1895
1998
  op.swap.merge(op.map! { |opt| shell_escape(opt, quote: true) })
1896
1999
  return
1897
2000
  elsif option('all')
1898
- cmd << '--all'
2001
+ op << '--all'
1899
2002
  end
1900
2003
  op.clear(errors: true, subject: flag.to_s) if flag
1901
2004
  end
@@ -1974,20 +2077,28 @@ module Squared
1974
2077
  end
1975
2078
 
1976
2079
  def git_session(*cmd, opts: nil, worktree: true, **kwargs)
1977
- dir = worktree ? ["--work-tree=#{shell_quote(path)}", "--git-dir=#{shell_quote(gitpath)}"] : []
2080
+ dir = worktree ? [quote_option('work-tree', path), quote_option('git-dir', gitpath)] : []
1978
2081
  return session('git', *dir, *cmd, **kwargs) unless opts
1979
2082
 
1980
2083
  op = OptionPartition.new(opts, OPT_GIT[:common], dir, project: self)
1981
- ret = session('git', *op.to_a, *cmd, **kwargs)
1982
- [ret, op.extras]
2084
+ [session('git', *op.to_a, *cmd, **kwargs), op.extras]
1983
2085
  end
1984
2086
 
1985
2087
  def git_output(*cmd, **kwargs)
1986
2088
  git_session(*cmd, main: false, options: false, **kwargs)
1987
2089
  end
1988
2090
 
1989
- def git_spawn(*cmd, stdout: true)
1990
- source(cmd.first.is_a?(Set) ? cmd.first : git_output(*cmd), io: true, banner: false, stdout: stdout)
2091
+ def git_spawn(*cmd, exception: true, io: true, sync: true, stdout: true, banner: false, **kwargs)
2092
+ kwargs[:send] = if sync
2093
+ :system
2094
+ else
2095
+ exception = false
2096
+ io = false
2097
+ stdout = false
2098
+ :spawn
2099
+ end
2100
+ source(cmd.first.is_a?(Set) ? cmd.first : git_output(*cmd), exception: exception, io: io, sync: sync,
2101
+ stdout: stdout, banner: banner, **kwargs)
1991
2102
  end
1992
2103
 
1993
2104
  def dryrun?(*, target: @session, **)
@@ -2002,8 +2113,8 @@ module Squared
2002
2113
  target.include?('--quiet') || (target.include?('-q') && stripext(target.first) == 'git')
2003
2114
  end
2004
2115
 
2005
- def gitpath
2006
- path + '.git'
2116
+ def gitpath(*args)
2117
+ path.join('.git', *args)
2007
2118
  end
2008
2119
 
2009
2120
  def repotrack(origin, branch, quote: true)