squared 0.4.6 → 0.4.8

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.
@@ -1,9 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'date'
4
- require 'time'
5
- require 'digest'
6
-
7
3
  module Squared
8
4
  module Workspace
9
5
  module Git
@@ -18,14 +14,14 @@ module Squared
18
14
  check = ->(proj) { proj.is_a?(Project::Git) && !proj.exclude?(Project::Git.ref) && git_clone?(proj.path) }
19
15
  if uri.is_a?(Array)
20
16
  base = name
21
- uri.each { |val| repo << proj if (proj = @project[val.to_s]) && check.(proj) }
17
+ uri.each { |val| repo << proj if (proj = @project[val.to_s]) && check.call(proj) }
22
18
  elsif uri
23
19
  data[name.to_s] = uri
24
20
  elsif name.is_a?(Enumerable)
25
21
  data = name.to_h
26
22
  elsif name.is_a?(String) && name.match?(GIT_PROTO)
27
23
  base = name
28
- @project.each_value { |proj| repo << proj if !proj.parent && check.(proj) }
24
+ @project.each_value { |proj| repo << proj if !proj.parent && check.call(proj) }
29
25
  else
30
26
  warn log_message(Logger::WARN, name, subject: 'git', hint: 'invalid', pass: true) if warning
31
27
  return self
@@ -141,7 +137,7 @@ module Squared
141
137
  private
142
138
 
143
139
  def rev_timenow
144
- DateTime.now.strftime('%Q').to_i + time_offset
140
+ Time.now.utc.strftime('%s%L').to_i
145
141
  end
146
142
  end
147
143
  Application.include Git
@@ -163,11 +159,12 @@ module Squared
163
159
  show: %w[s exit-code histogram].freeze
164
160
  }.freeze,
165
161
  fetch: {
166
- base: %w[multiple progress P|prune-tags refetch stdin u|update-head-ok recurse-submodules=b
162
+ base: %w[multiple progress P|prune-tags refetch stdin u|update-head-ok
167
163
  recurse-submodules-default=b].freeze,
168
164
  pull: %w[4 6 n t a|append atomic dry-run f|force k|keep n|negotiate-only prefetch p|prune q|quiet
169
165
  set-upstream unshallow update-shallow v|verbose deepen=i depth=i j|jobs=i negotiation-tip=q
170
- refmap=q o|server-option=q shallow-exclude=e shallow-since=b upload-pack=q].freeze
166
+ recurse-submodules=v refmap=q o|server-option=q shallow-exclude=e shallow-since=v
167
+ upload-pack=q].freeze
171
168
  }.freeze,
172
169
  log: {
173
170
  base: %w[all all-match alternate-refs author-date-order basic-regexp bisect boundary cherry cherry-mark
@@ -199,7 +196,7 @@ module Squared
199
196
  X|exclude-from=p exclude-per-directory=p format=q with-tree=q].freeze,
200
197
  ls_remote: %w[exit-code get-url q|quiet o|server-option=q symref sort=q upload-pack=q].freeze,
201
198
  mv: %w[k f|force n|dry-run v|verbose].freeze,
202
- pull: %w[e n allow-unrelated-histories ff-only S|gpg-sign=qq log=i r|rebase=b? s|strategy=b
199
+ pull: %w[e n allow-unrelated-histories ff-only S|gpg-sign=qq log=i r|rebase=v? s|strategy=b
203
200
  X|strategy-option=b].freeze,
204
201
  merge: %w[e n allow-unrelated-histories ff-only m=q q|quiet v|verbose cleanup=b F|file=p S|gpg-sign=qq
205
202
  into-name=e log=i s|strategy=b X|strategy-option=b].freeze,
@@ -300,7 +297,7 @@ module Squared
300
297
  end
301
298
 
302
299
  def tasks
303
- %i[pull rebase fetch clone stash status branch revbuild].freeze
300
+ %i[pull rebase autostash fetch clone stash status branch revbuild].freeze
304
301
  end
305
302
 
306
303
  def config?(val)
@@ -328,7 +325,7 @@ module Squared
328
325
  'rev' => %i[commit branch output parseopt build].freeze,
329
326
  'show' => %i[format oneline textconv].freeze,
330
327
  'stash' => %i[push pop apply drop clear list].freeze,
331
- 'tag' => %i[add delete list].freeze
328
+ 'tag' => %i[add sign delete list].freeze
332
329
  })
333
330
 
334
331
  def initialize(*, **)
@@ -345,7 +342,7 @@ module Squared
345
342
  return unless ref?(Git.ref)
346
343
 
347
344
  namespace name do
348
- @@tasks[Git.ref].each do |action, flags|
345
+ Git.subtasks do |action, flags|
349
346
  next if @pass.include?(action)
350
347
 
351
348
  namespace action do
@@ -386,12 +383,14 @@ module Squared
386
383
  tag flag, args.to_a
387
384
  end
388
385
  when :delete
389
- format_desc action, flag, 'name+'
386
+ format_desc action, flag, 'name+?'
390
387
  task flag do |_, args|
391
- refs = param_guard(action, flag, args: args.to_a)
388
+ if (refs = args.to_a).empty?
389
+ refs = choice_refs('Choose a tag', 'tags', multiple: true, accept: 'Delete?')
390
+ end
392
391
  tag(flag, refs: refs)
393
392
  end
394
- when :add
393
+ when :add, :sign
395
394
  format_desc action, flag, 'name,message?,commit?'
396
395
  task flag, [:name, :message, :commit] do |_, args|
397
396
  name = param_guard(action, flag, args: args, key: :name)
@@ -411,7 +410,7 @@ module Squared
411
410
  case flag
412
411
  when :view, :between, :contain
413
412
  if flag == :view && action == 'log'
414
- format_desc action, flag, '(^)commit/H0*,pathspec*,opts*'
413
+ format_desc action, flag, '(^)commit/H0*,opts*,pathspec*'
415
414
  task flag do |_, args|
416
415
  index = []
417
416
  args.to_a.each do |val|
@@ -419,18 +418,18 @@ module Squared
419
418
  index << "HEAD~#{$1}"
420
419
  elsif (sha = commithash(val))
421
420
  index << sha
422
- elsif val.start_with?('^') || (!%r{^[.\\/]}.match?(val) && !%r{[\\/]$}.match?(val))
421
+ elsif val.start_with?('^')
423
422
  index << shell_quote(val)
424
423
  end
425
424
  end
426
- logx(flag, args.to_a.drop(index.size), index: index)
425
+ log!(flag, args.to_a.drop(index.size), index: index)
427
426
  end
428
427
  else
429
- format_desc action, flag, 'commit1,commit2,pathspec*,opts*'
428
+ format_desc action, flag, 'commit1,commit2,opts*,pathspec*'
430
429
  task flag, [:commit1, :commit2] do |_, args|
431
430
  commit1 = param_guard(action, flag, args: args, key: :commit1)
432
431
  commit2 = param_guard(action, flag, args: args, key: :commit2)
433
- __send__(action == 'log' ? :logx : :diff, flag, args.extras, range: [commit1, commit2])
432
+ __send__(action == 'log' ? :log! : :diff, flag, args.extras, range: [commit1, commit2])
434
433
  end
435
434
  end
436
435
  when :head
@@ -461,33 +460,41 @@ module Squared
461
460
  when 'checkout'
462
461
  case flag
463
462
  when :branch
464
- format_desc action, flag, 'name,create?=[bB],commit?,detach?=d'
463
+ format_desc action, flag, 'name?,create?=[bB],commit?,detach?=d'
465
464
  task flag, [:name, :create, :commit, :detach] do |_, args|
466
- branch = param_guard(action, flag, args: args, key: :name)
467
- create = args.create
468
- if args.commit == 'd'
469
- detach = 'd'
470
- commit = nil
471
- elsif create == 'd'
472
- create = nil
473
- commit = nil
474
- detach = 'd'
475
- elsif create && create.size > 1
476
- commit = create
477
- create = nil
478
- detach = args.commit
465
+ if (branch = args.name)
466
+ branch = param_guard(action, flag, args: args, key: :name)
467
+ create = args.create
468
+ if args.commit == 'd'
469
+ detach = 'd'
470
+ commit = nil
471
+ elsif create == 'd'
472
+ create = nil
473
+ commit = nil
474
+ detach = 'd'
475
+ elsif create && create.size > 1
476
+ commit = create
477
+ create = nil
478
+ detach = args.commit
479
+ else
480
+ detach = args.detach
481
+ commit = args.commit
482
+ end
483
+ param_guard(action, flag, args: { create: create }, key: :create, pat: /\Ab\z/i) if create
479
484
  else
480
- detach = args.detach
481
- commit = args.commit
485
+ branch = choice_refs 'Choose a branch to switch', 'heads'
482
486
  end
483
- param_guard(action, flag, args: { create: create }, key: :create, pat: /\Ab\z/i) if create
484
487
  checkout(flag, branch: branch, create: create, commit: commit, detach: detach)
485
488
  end
486
489
  when :track
487
- format_desc action, flag, 'origin,(^)name?'
490
+ format_desc action, flag, 'origin?,(^)name?'
488
491
  task flag, [:origin, :name] do |_, args|
489
- origin = param_guard(action, flag, args: args, key: :origin)
490
- checkout(flag, branch: args.name, origin: origin)
492
+ if (origin = args.origin)
493
+ branch = args.name
494
+ else
495
+ origin, branch = choice_refs('Choose a remote', 'remotes', values: ['Enter branch name'])
496
+ end
497
+ checkout(flag, branch: branch, origin: origin)
491
498
  end
492
499
  when :commit
493
500
  format_desc action, flag, 'branch/commit,opts*'
@@ -515,15 +522,21 @@ module Squared
515
522
  branch(flag, target: target, ref: args.ref)
516
523
  end
517
524
  when :set
518
- format_desc(action, flag, '(^)upstream,name?')
525
+ format_desc(action, flag, '(^)upstream?,name?')
519
526
  task flag, [:upstream, :name] do |_, args|
520
- upstream = param_guard(action, flag, args: args, key: :upstream)
521
- branch(flag, target: args.name, ref: upstream)
527
+ if (ref = args.upstream)
528
+ target = args.name
529
+ else
530
+ ref, target = choice_refs('Choose a remote', 'remotes', values: ['Enter branch name'])
531
+ end
532
+ branch(flag, target: target, ref: ref)
522
533
  end
523
534
  when :delete
524
- format_desc action, flag, '(^~)name+'
535
+ format_desc action, flag, '(^~)name+?'
525
536
  task flag do |_, args|
526
- refs = param_guard(action, flag, args: args.to_a)
537
+ if (refs = args.to_a).empty?
538
+ refs = choice_refs('Choose a branch', 'heads', multiple: true, accept: 'Delete?')
539
+ end
527
540
  branch(flag, refs: refs)
528
541
  end
529
542
  when :edit
@@ -699,16 +712,20 @@ module Squared
699
712
  end
700
713
 
701
714
  def pull(flag = nil, opts = [], sync: invoked_sync?('pull', flag), remote: nil)
702
- cmd, opts = git_session('pull', flag && "--#{flag}", opts: opts)
715
+ cmd, opts = git_session('pull', opts: opts)
703
716
  if flag == :rebase
717
+ cmd << '--rebase'
704
718
  cmd << '--autostash' if option('autostash')
705
- elsif (val = option('rebase', ignore: false))
706
- cmd << case val
707
- when '0'
708
- '--no-rebase'
709
- else
710
- VAL_GIT[:rebase][:value].include?(val) ? basic_option('rebase', val) : '--rebase'
711
- end
719
+ else
720
+ cmd << '--autostash' if flag == :autostash
721
+ if (val = option('rebase', ignore: false))
722
+ cmd << case val
723
+ when '0', 'false'
724
+ '--no-rebase'
725
+ else
726
+ VAL_GIT[:rebase][:value].include?(val) ? basic_option('rebase', val) : '--rebase'
727
+ end
728
+ end
712
729
  end
713
730
  append_pull(opts, OPT_GIT[:pull] + OPT_GIT[:fetch][:pull],
714
731
  no: OPT_GIT[:no][:pull] + OPT_GIT[:no][:fetch][:pull], remote: remote, flag: flag)
@@ -727,15 +744,15 @@ module Squared
727
744
  cmd, opts = git_session('rebase', opts: opts)
728
745
  case flag
729
746
  when :branch
730
- branch = option_sanitize(opts, OPT_GIT[:rebase], no: OPT_GIT[:no][:rebase]).first
731
- case branch.size
747
+ op = OptionPartition.new(opts, OPT_GIT[:rebase], cmd, project: self, no: OPT_GIT[:no][:rebase])
748
+ case op.size
732
749
  when 0
733
750
  append_head
734
751
  when 1, 2
735
- append_value(branch, delim: true)
752
+ op.append(delim: true)
736
753
  else
737
- append_value([branch.pop, branch.pop].reverse, delim: true)
738
- option_clear(branch, pass: false)
754
+ op.append(op.pop(2), delim: true)
755
+ .clear(pass: false)
739
756
  end
740
757
  when :onto
741
758
  return unless upstream
@@ -752,12 +769,16 @@ module Squared
752
769
  source
753
770
  end
754
771
 
772
+ def autostash(*, sync: invoked_sync?('autostash'), **)
773
+ pull(:autostash, sync: sync)
774
+ end
775
+
755
776
  def fetch(flag = nil, opts = [], sync: invoked_sync?('fetch', flag), remote: nil)
756
777
  cmd, opts = git_session('fetch', opts: opts)
757
- append_pull(opts, collect_hash(OPT_GIT[:fetch]), no: collect_hash(OPT_GIT[:no][:fetch]),
758
- remote: remote, flag: flag)
778
+ append_pull(opts, collect_hash(OPT_GIT[:fetch]),
779
+ no: collect_hash(OPT_GIT[:no][:fetch]), remote: remote, flag: flag)
759
780
  cmd << '--all' if !remote && !session_arg?('multiple') && option('all')
760
- cmd << '--verbose' if verbose && !session_arg('quiet')
781
+ cmd << '--verbose' if verbose && !session_arg?('quiet')
761
782
  source(sync: sync, **threadargs)
762
783
  end
763
784
 
@@ -786,25 +807,25 @@ module Squared
786
807
  def stash(flag = nil, opts = [], sync: invoked_sync?('stash', flag))
787
808
  if flag
788
809
  cmd, opts = git_session('stash', flag, opts: opts)
789
- refs = option_sanitize(opts, OPT_GIT[:stash][:common] + OPT_GIT[:stash].fetch(flag, [])).first
810
+ op = OptionPartition.new(opts, OPT_GIT[:stash][:common] + OPT_GIT[:stash].fetch(flag, []), cmd,
811
+ project: self)
790
812
  case flag
791
813
  when :push
792
- append_pathspec refs
814
+ append_pathspec op.extras
793
815
  when :pop, :apply, :drop
794
- unless refs.empty?
795
- cmd << shell_escape(refs.pop)
796
- option_clear refs
816
+ unless op.empty?
817
+ op << shell_escape(op.pop)
818
+ op.clear
797
819
  end
798
820
  when :clear
799
- if confirm("Remove #{sub_style('all', styles: theme[:active])} the stash entries? [y/N] ", 'N')
821
+ if confirm("Remove #{sub_style('all', styles: theme[:active])} stash entries? [y/N] ", 'N')
800
822
  source(stdout: true)
801
823
  end
802
824
  return
803
825
  when :list
804
826
  out, banner, from = source(io: true)
805
827
  print_item banner
806
- ret = write_lines(out)
807
- list_result(ret, 'objects', from: from)
828
+ list_result(write_lines(out), 'objects', from: from)
808
829
  return
809
830
  end
810
831
  else
@@ -855,22 +876,23 @@ module Squared
855
876
  end
856
877
  unless workspace.closed
857
878
  if @revbuild
858
- statusargs.().each { |key, val| @revbuild[key] += val }
879
+ statusargs.call.each { |key, val| @revbuild[key] += val }
859
880
  else
860
- @revbuild = statusargs.()
881
+ @revbuild = statusargs.call
861
882
  end
862
883
  return
863
884
  end
864
- sha = git_spawn('rev-parse --verify HEAD').first.to_s.chomp
885
+ sha = git_spawn('rev-parse --verify HEAD').chomp
865
886
  return if sha.empty?
866
887
 
867
- args = []
868
- kwargs = kwargs.key?(:include) || kwargs.key?(:exclude) ? statusargs.() : @revbuild || {}
888
+ kwargs = kwargs.key?(:include) || kwargs.key?(:exclude) ? statusargs.call : @revbuild || {}
869
889
  case flag
870
890
  when :build
871
- opts = option_sanitize(opts, OPT_GIT[:status], target: args).first
872
- option_clear(opts, append: true)
891
+ op = OptionPartition.new(opts, OPT_GIT[:status], project: self)
892
+ op.clear(append: true)
893
+ args = op.to_a
873
894
  else
895
+ args = []
874
896
  args << basic_option('untracked-files', flag) if (flag = option('untracked-files', prefix: 'git'))
875
897
  args << basic_option('ignore-submodules', flag) if (flag = option('ignore-submodules', prefix: 'git'))
876
898
  args << basic_option('ignored', flag) if (flag = option('ignored', prefix: 'git'))
@@ -904,13 +926,14 @@ module Squared
904
926
  cmd, opts = git_session('reset', opts: opts)
905
927
  case flag
906
928
  when :commit, :index
907
- files = option_sanitize(opts, OPT_GIT[:reset] + VAL_GIT[:reset], no: OPT_GIT[:no][:reset]).first
929
+ op = OptionPartition.new(opts, OPT_GIT[:reset] + VAL_GIT[:reset], cmd,
930
+ project: self, no: OPT_GIT[:no][:reset])
908
931
  if flag == :commit
909
- append_value(commit, delim: true)
910
- option_clear(files, pass: false)
932
+ op.append(commit, delim: true)
933
+ .clear(pass: false)
911
934
  ref = false
912
935
  else
913
- (refs ||= []).concat(files)
936
+ (refs ||= []).concat(op.extras)
914
937
  end
915
938
  when :mode
916
939
  return unless VAL_GIT[:reset].include?(mode)
@@ -922,8 +945,6 @@ module Squared
922
945
  end
923
946
  when :patch
924
947
  cmd << '--patch'
925
- else
926
- return
927
948
  end
928
949
  unless ref == false
929
950
  append_commit(ref, head: true)
@@ -939,26 +960,26 @@ module Squared
939
960
  when :branch
940
961
  cmd << '--detach' if detach == 'd' || option('detach')
941
962
  append_option('track', equals: true)
942
- cmd << (create ? shell_option(create, branch) : branch) << commit
963
+ cmd << (create ? quote_option(create, branch) : branch) << commit
943
964
  when :track
944
965
  if branch
945
966
  if branch.start_with?('^')
946
967
  opt = 'B'
947
968
  branch = branch[1..-1]
948
969
  end
949
- cmd << shell_option(opt || 'b', branch)
970
+ cmd << quote_option(opt || 'b', branch)
950
971
  end
951
- cmd << '--track' << origin
972
+ cmd << '--track' << shell_quote(origin)
952
973
  when :detach
953
974
  cmd << '--detach' << commit
954
975
  else
955
- refs = option_sanitize(opts, OPT_GIT[:checkout], no: OPT_GIT[:no][:checkout]).first
976
+ op = OptionPartition.new(opts, OPT_GIT[:checkout], cmd, project: self, no: OPT_GIT[:no][:checkout])
956
977
  if flag == :commit
957
- append_value(commit, delim: true)
958
- option_clear(refs, pass: false)
978
+ op.append(commit, delim: true)
979
+ .clear(pass: false)
959
980
  else
960
981
  append_head
961
- append_pathspec refs
982
+ append_pathspec op.extras
962
983
  end
963
984
  end
964
985
  source
@@ -967,12 +988,13 @@ module Squared
967
988
  def tag(flag, opts = [], refs: [], message: nil, commit: nil)
968
989
  cmd, opts = git_session('tag', opts: opts)
969
990
  case flag
970
- when :add
971
- if option('sign')
991
+ when :add, :sign
992
+ if flag == :sign || option('sign')
972
993
  cmd << '--sign'
973
994
  elsif !session_arg?('s', 'sign', 'u', 'local-user')
974
995
  cmd << '--annotate'
975
996
  end
997
+ cmd << '--force' if option('force')
976
998
  if !commit && message && (sha = commithash(message))
977
999
  commit = sha
978
1000
  else
@@ -981,12 +1003,11 @@ module Squared
981
1003
  append_value refs
982
1004
  append_head commit
983
1005
  when :list
984
- cmd << '--list'
985
- grep = option_sanitize(opts, OPT_GIT[:tag], no: OPT_GIT[:no][:tag]).first
1006
+ op = OptionPartition.new(opts, OPT_GIT[:tag], cmd << '--list', project: self, no: OPT_GIT[:no][:tag])
986
1007
  out, banner, from = source(io: true)
987
1008
  print_item banner
988
- ret = write_lines(out, grep: grep)
989
- list_result(ret, 'tags', from: from, grep: grep)
1009
+ ret = write_lines(out, grep: op.extras)
1010
+ list_result(ret, 'tags', from: from, grep: op.extras)
990
1011
  return
991
1012
  when :delete
992
1013
  cmd << '--delete'
@@ -997,57 +1018,58 @@ module Squared
997
1018
  source
998
1019
  end
999
1020
 
1000
- def logx(flag, opts = [], range: [], index: [])
1021
+ def log!(flag, opts = [], range: [], index: [])
1001
1022
  cmd, opts = git_session('log', opts: opts)
1002
- refs = option_sanitize(opts, collect_hash(OPT_GIT[:log]), no: collect_hash(OPT_GIT[:no][:log])).first
1023
+ op = OptionPartition.new(opts, collect_hash(OPT_GIT[:log]), cmd,
1024
+ project: self, no: collect_hash(OPT_GIT[:no][:log]))
1003
1025
  case flag
1004
1026
  when :between, :contain
1005
- cmd << shell_quote(range.join(flag == :between ? '..' : '...'))
1027
+ op << shell_quote(range.join(flag == :between ? '..' : '...'))
1006
1028
  else
1007
- cmd.merge(index)
1029
+ op.merge(index)
1008
1030
  end
1009
1031
  append_nocolor
1010
- append_pathspec refs
1032
+ append_pathspec op.extras
1011
1033
  source(exception: false)
1012
1034
  end
1013
1035
 
1014
1036
  def diff(flag, opts = [], refs: [], branch: nil, range: [], index: [])
1015
1037
  cmd, opts = git_session('diff', opts: opts)
1016
- files = option_sanitize(opts, collect_hash(OPT_GIT[:diff]) + OPT_GIT[:log][:diff],
1017
- no: OPT_GIT[:no][:log][:diff]).first
1038
+ op = OptionPartition.new(opts, collect_hash(OPT_GIT[:diff]) + OPT_GIT[:log][:diff], cmd,
1039
+ project: self, no: OPT_GIT[:no][:log][:diff])
1018
1040
  case flag
1019
1041
  when :files, :view, :between, :contain
1020
- cmd.delete('--cached')
1042
+ op.delete('--cached')
1021
1043
  end
1022
1044
  append_nocolor
1023
1045
  if flag == :files
1024
- cmd << '--no-index'
1046
+ op << '--no-index'
1025
1047
  append_pathspec(refs, parent: true)
1026
1048
  else
1027
1049
  case flag
1028
1050
  when :view
1029
- cmd << '--merge-base' if option('merge-base')
1030
- cmd << shell_quote(range.first, quote: true) << shell_quote(range.last, quote: true)
1051
+ op << '--merge-base' if option('merge-base')
1052
+ op << shell_quote(range.first, quote: true) << shell_quote(range.last, quote: true)
1031
1053
  when :between, :contain
1032
- cmd.delete('--merge-base')
1033
- cmd << shell_quote(range.join(flag == :between ? '..' : '...'))
1054
+ op.delete('--merge-base')
1055
+ op << shell_quote(range.join(flag == :between ? '..' : '...'))
1034
1056
  else
1035
- cmd << '--merge-base' if option('merge-base')
1036
- cmd << shell_quote(branch) if branch
1057
+ op << '--merge-base' if option('merge-base')
1058
+ op << shell_quote(branch) if branch
1037
1059
  if !index.empty?
1038
- if session_arg?('cached')
1060
+ if op.arg?('cached')
1039
1061
  raise_error("one commit only: #{index.join(', ')}", hint: '--cached') if index.size > 1
1040
- cmd << index.first
1062
+ op << index.first
1041
1063
  else
1042
- cmd.merge(index)
1064
+ op.merge(index)
1043
1065
  end
1044
1066
  elsif (n = option('index', ignore: false))
1045
- cmd << "HEAD~#{n}"
1067
+ op << "HEAD~#{n}"
1046
1068
  end
1047
1069
  end
1048
- append_pathspec files
1070
+ append_pathspec op.extras
1049
1071
  end
1050
- source(exception: session_arg?('exit-code'))
1072
+ source(exception: op.arg?('exit-code'))
1051
1073
  end
1052
1074
 
1053
1075
  def commit(flag, *, refs: [], message: nil, pass: false)
@@ -1069,7 +1091,7 @@ module Squared
1069
1091
  branch = nil
1070
1092
  upstream = nil
1071
1093
  git_spawn 'fetch --no-tags --quiet'
1072
- git_spawn('branch -vv --list', stdout: false).first.each do |val|
1094
+ git_spawn('branch -vv --list', stdout: false).each do |val|
1073
1095
  next unless (r = /^\*\s(\S+)\s+(\h+)(?:\s\[(.+?)(?=\]\s)\])?\s/.match(val))
1074
1096
 
1075
1097
  branch = r[1]
@@ -1077,7 +1099,7 @@ module Squared
1077
1099
  origin = r[3][%r{^(.+)/#{Regexp.escape(branch)}$}, 1]
1078
1100
  else
1079
1101
  unless (origin = option('repository', prefix: 'git', ignore: false))
1080
- out = git_spawn('log -n1 --format=%h%d').first
1102
+ out = git_spawn 'log -n1 --format=%h%d'
1081
1103
  if out =~ /^#{r[2]} \(HEAD -> #{Regexp.escape(branch)}, (.+?)\)$/
1082
1104
  split_escape($1).each do |s|
1083
1105
  next unless s.end_with?("/#{branch}")
@@ -1122,10 +1144,10 @@ module Squared
1122
1144
  cmd, opts = git_session('merge', opts: opts)
1123
1145
  case flag
1124
1146
  when :commit, :'no-commit'
1125
- refs = option_sanitize(opts, OPT_GIT[:merge], no: OPT_GIT[:no][:merge]).first
1126
- raise_error 'no branch/commit' if refs.empty?
1127
- cmd << "--#{flag}" << '--'
1128
- append_commit(*refs)
1147
+ op = OptionPartition.new(opts, OPT_GIT[:merge], cmd, project: self, no: OPT_GIT[:no][:merge])
1148
+ raise_error 'no branch/commit' if op.empty?
1149
+ op << "--#{flag}" << '--'
1150
+ append_commit(*op.extras)
1129
1151
  else
1130
1152
  return unless VAL_GIT[:merge][:send].include?(command)
1131
1153
 
@@ -1141,7 +1163,7 @@ module Squared
1141
1163
  when :create
1142
1164
  if (arg = option('track', ignore: false))
1143
1165
  cmd << case arg
1144
- when '0'
1166
+ when '0', 'false'
1145
1167
  '--no-track'
1146
1168
  when 'direct', 'inherit'
1147
1169
  basic_option('track', arg)
@@ -1162,17 +1184,17 @@ module Squared
1162
1184
  end
1163
1185
  ref = nil
1164
1186
  when :delete
1165
- force, list = refs.partition { |val| val.match?(/^[\^~]/) }
1187
+ force, list = refs.partition { |val| val.match?(/^[~^]/) }
1166
1188
  force.each do |val|
1167
1189
  dr = val[0, 3]
1168
1190
  d = dr.include?('^') ? '-D' : '-d'
1169
- r = dr.include?('~') ? '-r' : nil
1191
+ r = '-r' if dr.include?('~')
1170
1192
  source git_output('branch', d, r, shell_quote(val.sub(/^[\^~]+/, '')))
1171
1193
  end
1172
1194
  return if list.empty?
1173
1195
 
1174
1196
  cmd << '-d'
1175
- list.each { |val| cmd << shell_quote(val) }
1197
+ append_value list
1176
1198
  when :move, :copy
1177
1199
  flag = "-#{flag.to_s[0]}"
1178
1200
  cmd << (option('force') ? flag.upcase : flag)
@@ -1182,20 +1204,22 @@ module Squared
1182
1204
  cmd << '--edit-description'
1183
1205
  when :current
1184
1206
  cmd << '--show-current'
1207
+ source(banner: verbosetype > 1, stdout: true)
1208
+ return
1185
1209
  when :list
1186
- opts = option_sanitize(opts, OPT_GIT[:branch], no: OPT_GIT[:no][:branch], single: /^v+$/).first
1187
- cmd << '--list'
1188
- opts.each { |val| cmd << shell_quote(val) }
1210
+ op = OptionPartition.new(opts, OPT_GIT[:branch], cmd << '--list',
1211
+ project: self, no: OPT_GIT[:no][:branch], single: /^v+$/)
1212
+ op.each { |val| op << shell_quote(val) }
1189
1213
  out, banner, from = source(io: true)
1190
1214
  print_item banner
1191
1215
  ret = write_lines(out, sub: [
1192
- { pat: /^(\*\s+)(\S+)(\s*)$/, styles: color(:green), index: 2 },
1216
+ { pat: /^(\*\s+)(\S+)(.*)$/, styles: color(:green), index: 2 },
1193
1217
  { pat: %r{^(\s*)(remotes/\S+)(.*)$}, styles: color(:red), index: 2 }
1194
1218
  ])
1195
1219
  list_result(ret, 'branches', from: from)
1196
1220
  return
1197
1221
  else
1198
- head = git_spawn('rev-parse --abbrev-ref HEAD').first.chomp
1222
+ head = git_spawn('rev-parse --abbrev-ref HEAD').chomp
1199
1223
  if head.empty?
1200
1224
  ret = 0
1201
1225
  else
@@ -1211,8 +1235,8 @@ module Squared
1211
1235
  if (r1 = r[1]) && r1 =~ /^(.+):(?: ([a-z]+) (\d+),)? ([a-z]+) (\d+)$/
1212
1236
  write = ->(s1, s2) { "#{s1.capitalize.rjust(7)}: #{sub_style(s2, styles: theme[:warn])}" }
1213
1237
  r1 = $1
1214
- r2 = $2 && write.($2, $3)
1215
- r3 = write.($4, $5)
1238
+ r2 = $2 && write.call($2, $3)
1239
+ r3 = write.call($4, $5)
1216
1240
  end
1217
1241
  r1 = nil if r1 == "origin/#{data[0]}"
1218
1242
  [" Branch: #{a + (r1 ? " (#{r1})" : '')}", r2, r3, " Commit: #{b}", "Message: #{r[2]}"]
@@ -1232,9 +1256,9 @@ module Squared
1232
1256
  end
1233
1257
 
1234
1258
  def restore(flag, opts = [])
1235
- opts = git_session('restore', "--#{flag}", opts: opts).last
1236
- refs = option_sanitize(opts, OPT_GIT[:restore], no: OPT_GIT[:no][:restore]).first
1237
- append_pathspec(refs, pass: false)
1259
+ cmd, opts = git_session('restore', "--#{flag}", opts: opts)
1260
+ op = OptionPartition.new(opts, OPT_GIT[:restore], cmd, project: self, no: OPT_GIT[:no][:restore])
1261
+ append_pathspec(op.extras, pass: false)
1238
1262
  source(sync: false, stderr: true)
1239
1263
  end
1240
1264
 
@@ -1264,13 +1288,14 @@ module Squared
1264
1288
  end
1265
1289
  end
1266
1290
  end
1267
- refs = option_sanitize(opts, OPT_GIT[:show] + OPT_GIT[:diff][:show] + OPT_GIT[:log][:diff],
1268
- no: OPT_GIT[:no][:show] + collect_hash(OPT_GIT[:no][:log], pass: [:base])).first
1269
- unless val == 'oneline' && session_arg?('abbrev-commit')
1270
- cmd << basic_option('abbrev', val) if (val = option('abbrev')) && val.to_i > 0
1291
+ op = OptionPartition.new(opts, OPT_GIT[:show] + OPT_GIT[:diff][:show] + OPT_GIT[:log][:diff], cmd,
1292
+ project: self,
1293
+ no: OPT_GIT[:no][:show] + collect_hash(OPT_GIT[:no][:log], pass: [:base]))
1294
+ unless val == 'oneline' && op.arg?('abbrev-commit')
1295
+ op << basic_option('abbrev', val) if (val = option('abbrev')) && val.to_i > 0
1271
1296
  banner = true
1272
1297
  end
1273
- append_value(refs, delim: true)
1298
+ op.append(delim: true)
1274
1299
  source(exception: false, banner: banner)
1275
1300
  end
1276
1301
 
@@ -1289,60 +1314,60 @@ module Squared
1289
1314
  cmd << '--abbrev-ref'
1290
1315
  append_commit(ref, head: true)
1291
1316
  else
1292
- args = option_sanitize(opts, OPT_GIT[:rev_parse][flag]).first
1293
- append_value(args, escape: session_arg?('sq-quote'))
1317
+ op = OptionPartition.new(opts, OPT_GIT[:rev_parse][flag], cmd, project: self)
1318
+ op.append(escape: op.arg?('sq-quote'))
1294
1319
  end
1295
- source(banner: verbosity > 0)
1320
+ source(banner: verbosetype > 1)
1296
1321
  end
1297
1322
 
1298
1323
  def ls_remote(flag, opts = [], remote: nil)
1299
1324
  cmd, opts = git_session('ls-remote', '--refs', opts: opts)
1300
1325
  cmd << "--#{flag}" unless flag == :remote
1301
- grep = option_sanitize(opts, OPT_GIT[:ls_remote]).first
1302
- cmd << shell_quote(remote) if remote
1326
+ op = OptionPartition.new(opts, OPT_GIT[:ls_remote], cmd, project: self)
1327
+ op << shell_quote(remote) if remote
1303
1328
  out, banner, from = source(io: true)
1304
1329
  print_item banner
1305
- ret = write_lines(out, grep: grep)
1306
- list_result(ret, flag.to_s, from: from, grep: grep)
1330
+ ret = write_lines(out, grep: op.extras)
1331
+ list_result(ret, flag.to_s, from: from, grep: op.extras)
1307
1332
  end
1308
1333
 
1309
1334
  def ls_files(flag, opts = [])
1310
- opts = git_session('ls-files', "--#{flag}", opts: opts).last
1311
- grep = option_sanitize(opts, OPT_GIT[:ls_files]).first
1335
+ cmd, opts = git_session('ls-files', "--#{flag}", opts: opts)
1336
+ op = OptionPartition.new(opts, OPT_GIT[:ls_files], cmd, project: self)
1312
1337
  out, banner, from = source(io: true)
1313
1338
  print_item banner
1314
- ret = write_lines(out, grep: grep)
1315
- list_result(ret, 'files', from: from, grep: grep)
1339
+ ret = write_lines(out, grep: op.extras)
1340
+ list_result(ret, 'files', from: from, grep: op.extras)
1316
1341
  end
1317
1342
 
1318
1343
  def git(flag, opts = [])
1319
1344
  cmd, opts = git_session(flag, opts: opts)
1320
- refs = option_sanitize(opts, OPT_GIT[flag], no: OPT_GIT[:no][flag]).first
1345
+ op = OptionPartition.new(opts, OPT_GIT[flag], cmd, project: self, no: OPT_GIT[:no][flag])
1321
1346
  sync = false
1322
1347
  stderr = true
1323
1348
  case flag
1324
1349
  when :clean
1325
- refs = projectmap(refs)
1350
+ refs = projectmap(op.extras)
1326
1351
  unless refs.empty?
1327
- cmd << '--'
1328
- cmd.merge(refs)
1352
+ op << '--'
1353
+ op.merge(refs)
1329
1354
  end
1330
1355
  sync = true
1331
1356
  stderr = false
1332
1357
  when :revert
1333
- if VAL_GIT[:rebase][:send].any? { |val| session_arg?(val) }
1334
- option_clear refs
1335
- elsif refs.empty?
1358
+ if VAL_GIT[:rebase][:send].any? { |val| op.arg?(val) }
1359
+ op.clear
1360
+ elsif op.empty?
1336
1361
  raise_error 'no commit given'
1337
1362
  else
1338
- append_commit(*refs)
1363
+ append_commit(*op.extras)
1339
1364
  end
1340
1365
  when :mv
1341
- refs = projectmap(refs)
1366
+ refs = projectmap(op.extras)
1342
1367
  raise_error 'no source/destination' unless refs.size > 1
1343
- cmd.merge(refs)
1368
+ op.merge(refs)
1344
1369
  when :rm
1345
- append_pathspec(refs, expect: true)
1370
+ append_pathspec(op.extras, expect: true)
1346
1371
  end
1347
1372
  source(sync: sync, stderr: stderr)
1348
1373
  end
@@ -1379,8 +1404,11 @@ module Squared
1379
1404
  format_banner((banner.is_a?(String) ? banner : cmd).gsub(File.join(path, ''), ''), banner: true)
1380
1405
  end
1381
1406
  begin
1382
- return [stdout ? `#{cmd}` : IO.popen(cmd), banner, from] if io
1407
+ if io
1408
+ return `#{cmd}` if stdout
1383
1409
 
1410
+ return banner ? [IO.popen(cmd), banner, from] : IO.popen(cmd)
1411
+ end
1384
1412
  if stdin? ? sync : stdout
1385
1413
  print_item banner unless multiple
1386
1414
  ret = `#{cmd}`
@@ -1469,19 +1497,35 @@ module Squared
1469
1497
  on :last, from
1470
1498
  end
1471
1499
 
1472
- def status_digest(*args, algorithm: Digest::SHA256, **kwargs)
1500
+ def choice_refs(msg, type, format: nil, sort: '-creatordate', count: ARG[:CHOICE], short: true, **kwargs)
1501
+ unless format
1502
+ format = +"%(refname#{short ? ':short' : ''})"
1503
+ case type
1504
+ when 'heads', 'tags'
1505
+ format += '%(if)%(HEAD)%(then) *%(end)'
1506
+ trim = /\s+\*\z/
1507
+ end
1508
+ end
1509
+ cmd = git_output 'for-each-ref', quote_option('format', format)
1510
+ cmd << quote_option('sort', sort) if sort
1511
+ cmd << shell_option('count', count) if count
1512
+ cmd << "refs/#{type}"
1513
+ choice_index(msg, source(cmd, io: true, banner: false), trim: trim, **kwargs)
1514
+ end
1515
+
1516
+ def status_digest(*args, algorithm: nil, **kwargs)
1517
+ require 'digest'
1518
+ algorithm ||= Digest::SHA256
1473
1519
  glob = kwargs.fetch(:include, [])
1474
1520
  pass = kwargs.fetch(:exclude, [])
1475
1521
  ret = {}
1476
- git_spawn('status -s --porcelain', *args, stdout: false)
1477
- .first
1478
- .each do |line|
1479
- next unless (file = line[/^[A-Z ?!]{3}"?(.+?)"?$/, 1])
1480
- next if !glob.empty? && glob.none? { |val| File.fnmatch?(val, file, File::FNM_DOTMATCH) }
1481
- next if !pass.empty? && pass.any? { |val| File.fnmatch?(val, file, File::FNM_DOTMATCH) }
1482
-
1483
- ret[file] = algorithm.hexdigest(File.read(basepath(file)))
1484
- end
1522
+ git_spawn('status -s --porcelain', *args, stdout: false).each do |line|
1523
+ next unless (file = line[/^[A-Z ?!]{3}"?(.+?)"?$/, 1])
1524
+ next if !glob.empty? && glob.none? { |val| File.fnmatch?(val, file, File::FNM_DOTMATCH) }
1525
+ next if !pass.empty? && pass.any? { |val| File.fnmatch?(val, file, File::FNM_DOTMATCH) }
1526
+
1527
+ ret[file] = algorithm.hexdigest(File.read(basepath(file)))
1528
+ end
1485
1529
  ret
1486
1530
  end
1487
1531
 
@@ -1489,43 +1533,46 @@ module Squared
1489
1533
  source(git_output(*cmd), io: true, banner: false, stdout: stdout)
1490
1534
  end
1491
1535
 
1492
- def append_pull(opts, list, target: @session, no: nil, flag: nil, remote: nil)
1493
- cmd << '--force' if option('force')
1494
- rsm = append_submodules(target: target)
1495
- out = []
1536
+ def append_pull(opts, list, target: @session, flag: nil, no: nil, remote: nil)
1537
+ target << '--force' if option('force', target: target)
1538
+ append_submodules(target: target)
1539
+ return if !remote && opts.empty?
1540
+
1496
1541
  refspec = []
1497
- opts, pat = option_sanitize(opts, remote ? list + ['refspec=b'] : list, target: target, no: no)
1498
- opts.each do |opt|
1499
- if opt =~ pat
1542
+ op = OptionPartition.new(opts, remote ? list + ['refspec=v'] : list, target, project: self, no: no)
1543
+ op.each do |opt|
1544
+ if opt =~ op.values
1500
1545
  case $1
1501
1546
  when 'rebase'
1502
- target << basic_option($1, $2) if VAL_GIT[:rebase][:value].include?($2)
1547
+ op << basic_option($1, $2) if VAL_GIT[:rebase][:value].include?($2)
1503
1548
  when 'shallow-since'
1504
1549
  next unless (val = Date.parse($2))
1505
1550
 
1506
- target << quote_option($1, val.strftime('%F %T'))
1551
+ op << quote_option($1, val.strftime('%F %T'))
1507
1552
  when 'recurse-submodules'
1508
- target << basic_option($1, $2) unless rsm
1553
+ op << basic_option($1, $2) unless op.arg?('recurse-submodules')
1509
1554
  when 'refspec'
1510
1555
  refspec << shell_escape($2, quote: true)
1511
1556
  end
1557
+ elsif op.arg?('--multiple')
1558
+ op.found << opt
1512
1559
  else
1513
- out << opt
1560
+ op.errors << opt
1514
1561
  end
1515
1562
  end
1516
1563
  if remote
1517
- append_value(remote, target: target, delim: true)
1518
- if (val = option('refspec', strict: true))
1519
- append_value(split_escape(val), target: target)
1564
+ op.append(remote, delim: true)
1565
+ if (val = option('refspec', target: target, strict: true))
1566
+ op.append(*split_escape(val))
1520
1567
  else
1521
- target.merge(refspec)
1568
+ op.merge(refspec)
1522
1569
  end
1523
- target.delete('--all')
1524
- elsif target.include?('--multiple')
1525
- target.merge(out.map! { |opt| shell_escape(opt, quote: true) })
1570
+ op.delete('--all')
1571
+ elsif op.arg?('--multiple')
1572
+ op.swap.merge(op.map! { |opt| shell_escape(opt, quote: true) })
1526
1573
  return
1527
1574
  end
1528
- option_clear(out, target: target, subject: flag.to_s) if flag
1575
+ op.clear(errors: true, subject: flag.to_s) if flag
1529
1576
  end
1530
1577
 
1531
1578
  def append_commit(*val, target: @session, head: false)
@@ -1538,10 +1585,10 @@ module Squared
1538
1585
  end
1539
1586
 
1540
1587
  def append_pathspec(files = [], target: @session, expect: false, parent: false, pass: true)
1541
- if session_arg?('pathspec-from-file')
1588
+ if session_arg?('pathspec-from-file', target: target)
1542
1589
  option_clear files
1543
1590
  else
1544
- if files.empty? && (val = option('pathspec'))
1591
+ if files.empty? && (val = option('pathspec', target: target))
1545
1592
  files = split_escape(val)
1546
1593
  end
1547
1594
  files = projectmap(files, parent: parent, pass: pass)
@@ -1564,7 +1611,7 @@ module Squared
1564
1611
  end
1565
1612
 
1566
1613
  def append_submodules(from = nil, target: @session)
1567
- return unless (val = option('recurse-submodules', ignore: false))
1614
+ return unless (val = option('recurse-submodules', target: target, ignore: false))
1568
1615
 
1569
1616
  if from == :clone
1570
1617
  projectmap(split_escape(val)).each do |path|
@@ -1573,7 +1620,7 @@ module Squared
1573
1620
  target
1574
1621
  else
1575
1622
  target << case val
1576
- when 'no', '0'
1623
+ when 'no', '0', 'false'
1577
1624
  '--no-recurse-submodules'
1578
1625
  when 'yes', 'on-demand'
1579
1626
  "--recurse-submodules#{from == :reset ? '' : "=#{val}"}"
@@ -1587,9 +1634,9 @@ module Squared
1587
1634
  dir = worktree ? ["--work-tree=#{shell_quote(path)}", "--git-dir=#{shell_quote(gitpath)}"] : []
1588
1635
  return session('git', *dir, *cmd, **kwargs) unless opts
1589
1636
 
1590
- opts = option_sanitize(opts, OPT_GIT[:common], target: dir).first
1591
- ret = session('git', *dir, *cmd, **kwargs)
1592
- [ret, opts]
1637
+ op = OptionPartition.new(opts, OPT_GIT[:common], dir, project: self)
1638
+ ret = session('git', *op.to_a, *cmd, **kwargs)
1639
+ [ret, op.extras]
1593
1640
  end
1594
1641
 
1595
1642
  def git_output(*cmd, **kwargs)
@@ -1597,24 +1644,15 @@ module Squared
1597
1644
  end
1598
1645
 
1599
1646
  def dryrun?(*, target: @session, **)
1600
- !!target&.include?('--dry-run')
1647
+ return false unless target
1648
+
1649
+ target.include?('--dry-run')
1601
1650
  end
1602
1651
 
1603
1652
  def quiet?(target: @session)
1604
1653
  return false unless target
1605
1654
 
1606
- target.include?('--quiet') || (target.include?('-q') && target.first == 'git')
1607
- end
1608
-
1609
- def verbosity
1610
- case verbose
1611
- when true
1612
- 0
1613
- when Numeric
1614
- verbose
1615
- else
1616
- -1
1617
- end
1655
+ target.include?('--quiet') || (target.include?('-q') && stripext(target.first) == 'git')
1618
1656
  end
1619
1657
 
1620
1658
  def gitpath