squared 0.8.4 → 0.8.6

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.
@@ -16,19 +16,19 @@ module Squared
16
16
  cgroup-parent=b iidfile=p label=q network=b no-cache-filter=b o|output=q platform=q q|quiet
17
17
  resource=q secret=qq shm-size=b ssh=qq t|tag=b target=b ulimit=q].freeze,
18
18
  bake: %w[print list=q set=qq var=qq].freeze,
19
- shared: %w[check load no-cache pull push allow=q call=b? f|file=p metadata-file=p policy=q progress=b
20
- provenance=q sbom=q].freeze
19
+ shared: %w[check load no-cache provenance pull push sbom allow=qq call=b? f|file=p metadata-file=p policy=q
20
+ progress=b].freeze
21
21
  }.freeze,
22
22
  compose: {
23
- common: %w[all-resources ansi=b compatibility dry-run env-file=p f|file=p parallel=n profile=b progress=b
23
+ common: %w[1 all-resources compatibility dry-run ansi=b env-file=p f|file=p parallel=n profile=b progress=b
24
24
  project-directory=p p|project-name=b].freeze,
25
25
  build: %w[check no-cache print pull push with-dependencies q|quiet build-arg=qq builder=b m|memory=b
26
26
  provenance=q sbom=q ssh=qq].freeze,
27
27
  create: %w[build force-recreate no-build no-recreate quiet-pull remove-orphans y|yes pull=b scale=i].freeze,
28
28
  exec: %w[d|detach privileged e|env=qq index=i T|no-tty user=b w|workdir=q].freeze,
29
29
  run: %w[build d|detach no-deps q|quiet quiet-build quiet-pull remove-orphans rm P|service-ports use-aliases
30
- cap-add=b cap-drop=b entrypoint=q e|env=qq env-from-file=p i|interactive=b? l|label=q name=b
31
- T|no-tty p|publish=q pull=b u|user=b v|volume=qq w|workdir=q].freeze,
30
+ cap-add=b cap-drop=b entrypoint=q e|env=qq env-from-file=p i|interactive l|label=q name=b T|no-tty
31
+ p|publish=q pull=b u|user=b v|volume=qq w|workdir=q].freeze,
32
32
  up: %w[abort-on-container-exit abort-on-container-failure always-recreate-deps attach-dependencies build
33
33
  d|detach force-recreate menu no-build no-color no-deps no-log-prefix no-recreate no-start quiet-build
34
34
  quiet-pull remove-orphans V|renew-anon-volumes timestamps wait w|watch y|yes attach=b
@@ -50,7 +50,7 @@ module Squared
50
50
  network-alias=b oom-score-adj=b pid=b pids-limit=n platform=q p|publish=q pull=b restart=b
51
51
  runtime=b security-opt=q shm-size=b stop-signal=b stop-timeout=i storage-opt=q sysctl=q tmpfs=q
52
52
  ulimit=q u|user=b userns=b uts=b v|volume=qq volume-driver=b volumes-from=b w|workdir=q].freeze,
53
- run: %w[d|detach detach-keys=q sig-proxy=b?].freeze,
53
+ run: %w[d|detach detach-keys=q sig-proxy].freeze,
54
54
  update: %w[blkio-weight=i cpu-period=i cpu-quota=i cpu-rt-period=i cpu-rt-runtime=i c|cpu-shares=i cpus=f
55
55
  cpuset-cpus=b cpuset-mems=b m|memory=b memory-reservation=b memory-swap=n pids-limit=n
56
56
  restart=q].freeze,
@@ -81,16 +81,17 @@ module Squared
81
81
  connect: %w[alias=b driver-opt=q gw-priority=n ip=b ip6=q link=b link-local-ip=q].freeze,
82
82
  create: %w[attachable config-only gateway=q ingress internal ipv4 ipv6 aux-address=q config-from=b
83
83
  d|driver=b ip-range=q ipam-driver=b ipam-opt=q label=q o|opt=q scope=b subnet=q].freeze,
84
- disconnect: %w[f|force].freeze
84
+ disconnect: %w[f|force].freeze,
85
+ rm: %w[f|force].freeze
85
86
  }.freeze
86
87
  }.freeze
87
88
  VAL_DOCKER = {
88
89
  run: {
89
90
  common: %w[source src destination dst target readonly ro].freeze,
90
- bind: %w[bind-propagation].freeze,
91
+ bind: %w[bind-create-src bind-propagation].freeze,
91
92
  volume: %w[volume-subpath volume-nocopy volume-opt].freeze,
92
93
  tmpfs: %w[tmpfs-size tmpfs-mode].freeze,
93
- image: %w[image-path].freeze
94
+ image: %w[image-subpath].freeze
94
95
  }.freeze,
95
96
  ls: {
96
97
  compose: %w[Name Image Command Service RunningFor Status Ports CreatedAt ExitCode Health ID Labels
@@ -127,7 +128,7 @@ module Squared
127
128
  'image' => %i[ls rm pull push tag save inspect history].freeze,
128
129
  'container' => %i[attach commit create diff exec inspect kill logs pause restart rm run start stats stop top
129
130
  unpause update].freeze,
130
- 'network' => %i[connect disconnect create].freeze,
131
+ 'network' => %i[connect disconnect create rm].freeze,
131
132
  'ls' => nil
132
133
  })
133
134
 
@@ -139,8 +140,7 @@ module Squared
139
140
  self.global = nil
140
141
  return unless dockerfile(kwargs[:file]).exist?
141
142
 
142
- @tag = env('TAG', strict: true,
143
- ignore: false) || kwargs[:tag] || tagname(@project, @version || 'latest')
143
+ @tag = env('TAG', strict: true, ignore: false) || kwargs[:tag] || tagname(@project, @version || 'latest')
144
144
  @context = kwargs[:context]
145
145
  @client = kwargs[:client]&.each_with_object([]) do |(key, val), out|
146
146
  val = case (key = key.to_s)
@@ -149,7 +149,7 @@ module Squared
149
149
  when 'tlscacert', 'tlscert', 'tlskey'
150
150
  basepath!(val, type: 'f')
151
151
  when 'context', 'host', 'log-level'
152
- quote_option(key, val)
152
+ quote_option key, val
153
153
  when 'debug', 'tls', 'tlsverify'
154
154
  out << key
155
155
  next
@@ -158,7 +158,7 @@ module Squared
158
158
  end
159
159
  @mounts = Array(kwargs[:mounts])
160
160
  @secrets = kwargs[:secrets]
161
- self.registry = tagjoin kwargs[:registry], kwargs[:username]
161
+ @registry = tagjoin kwargs[:registry], kwargs[:username]
162
162
  @oci = tagjoin kwargs[:oci], kwargs[:username]
163
163
  initialize_ref Docker.ref
164
164
  initialize_logger kwargs[:log]
@@ -359,11 +359,15 @@ module Squared
359
359
  end
360
360
  end
361
361
  when 'network'
362
- format_desc action, flag, 'target,opts*'
363
- task flag, [:target] do |_, args|
364
- target = flag == :create ? param_guard(action, flag, args: args, key: :target) : args.target
365
- if target
366
- network(flag, args.extras, target: target)
362
+ format_desc action, flag, 'name,opts*'
363
+ task flag, [:name] do |_, args|
364
+ name = if flag == :create || flag == :rm
365
+ param_guard(action, flag, args: args, key: :name)
366
+ else
367
+ args.name
368
+ end
369
+ if name
370
+ network(flag, args.extras, name: name)
367
371
  else
368
372
  choice_command flag
369
373
  end
@@ -530,6 +534,7 @@ module Squared
530
534
  list += ct[:create] if rc
531
535
  list += ct[:update] if rc ||= flag == :create
532
536
  op = OptionPartition.new(opts, list, cmd, args: rc || flag == :exec, **dockerinit)
537
+ option('entrypoint') { |val| op.add_option('entrypoint', val, escape: false, double: true) } if flag == :run
533
538
  from = symjoin 'container', flag
534
539
  case flag
535
540
  when :run, :create, :exec
@@ -557,23 +562,35 @@ module Squared
557
562
  end
558
563
  end
559
564
  case k
560
- when 'readonly', 'ro'
561
- out << k
565
+ when 'readonly', 'ro', 'volume-nocopy', 'bind-create-src'
566
+ out << k if %w[bind volume].include?(type)
562
567
  next
563
- when 'source', 'src', 'destination', 'dst', 'target', 'volume-subpath', 'image-path'
568
+ when 'source', 'src', 'destination', 'dst', 'target', 'volume-subpath', 'image-subpath'
564
569
  raise ArgumentError, message('no path value', k, hint: flag) unless v
565
570
 
566
- v = basepath v
571
+ case k
572
+ when 'source', 'src'
573
+ if type == 'bind'
574
+ v = basepath v
575
+ elsif type == 'tmpfs'
576
+ next
577
+ end
578
+ when 'destination', 'dst', 'target'
579
+ unless v.start_with?('/')
580
+ print_error(Logger::ERROR, "path is not absolute: #{v}", subject: from, hint: k)
581
+ next
582
+ end
583
+ end
567
584
  v = shell_quote(v, option: false, force: false) if q == ''
568
585
  end
569
586
  out << "#{k}=#{q}#{v}#{q}"
570
587
  elsif !silent?
571
- log_message('unrecognized option', subject: from, hint: k)
588
+ puts log_message('unrecognized option', subject: from, hint: k)
572
589
  end
573
590
  end
574
591
  raise TypeError, message('none specified', hint: flag) unless type
575
592
 
576
- cmd << "--mount type=#{type},#{args.join(',')}"
593
+ op << "--mount type=#{type},#{args.join(',')}"
577
594
  end
578
595
  end
579
596
  append_command(flag, id || tagmain, op.extras)
@@ -583,7 +600,7 @@ module Squared
583
600
  op.append(escape: true, strip: ':')
584
601
  when :commit
585
602
  latest = op.shift || tagmain
586
- cmd << id << latest
603
+ op << id << latest
587
604
  raise ArgumentError, message('unrecognized args', op.join(', '), hint: flag) unless op.empty?
588
605
  return unless confirm_command(cmd.to_s, title: from, target: id, as: latest)
589
606
 
@@ -604,7 +621,7 @@ module Squared
604
621
  else
605
622
  if op.empty?
606
623
  ps, status, no = filter_ps(flag, from: from)
607
- cmd << '--no-stream' if flag == :stats
624
+ op << '--no-stream' if flag == :stats
608
625
  list_image(flag, ps, no: no, hint: status, multiple: flag != :attach, from: from) do |img|
609
626
  run(cmd.temp(img), from: from)
610
627
  end
@@ -633,11 +650,12 @@ module Squared
633
650
  op = OptionPartition.new(opts, list, cmd, **dockerinit)
634
651
  exception = exception?
635
652
  banner = true
653
+ status = false
636
654
  from = symjoin 'image', flag
637
655
  case flag
638
656
  when :ls
639
657
  if opts.size == op.size
640
- index = 0
658
+ index = -1
641
659
  name = nil
642
660
  opts.reverse_each do |val|
643
661
  next unless (arg = OptionPartition.parse_arg!('name', val))
@@ -648,6 +666,7 @@ module Squared
648
666
  end
649
667
  list_image(:run, filter: filter, from: from) do |val|
650
668
  args = if name
669
+ index += 1
651
670
  opts.dup << "name=#{index == 0 ? name : "#{name}-#{index}"}"
652
671
  elsif choice!(opts, only: true)
653
672
  OptionPartition.strip(readline('Enter arguments', force: false))
@@ -659,7 +678,6 @@ module Squared
659
678
  break
660
679
  end
661
680
  container(:run, args, id: val)
662
- index += 1
663
681
  end
664
682
  return
665
683
  end
@@ -679,16 +697,15 @@ module Squared
679
697
  banner = false
680
698
  end
681
699
  when :tag, :save
682
- found = false
683
700
  list_image(flag, filter: filter, from: from) do |val|
684
701
  op << val
685
- found = true
702
+ status = true
686
703
  if flag == :tag
687
704
  op << tagname(project, op.first)
688
705
  break
689
706
  end
690
707
  end
691
- raise ArgumentError, message('target not specified', hint: flag) unless found
708
+ raise ArgumentError, message('target not specified', hint: flag) unless status
692
709
  when :pull
693
710
  if !id
694
711
  id = tagmain
@@ -699,7 +716,7 @@ module Squared
699
716
  registry = op.shift
700
717
  registry ||= option('registry') || self.registry unless id.include?('/')
701
718
  end
702
- cmd << shell_quote(tagjoin(registry, id))
719
+ op << shell_quote(tagjoin(registry, id))
703
720
  when :push
704
721
  id ||= option('tag', ignore: false) || op.shift || tagmain
705
722
  registry ||= option('registry') || op.shift || self.registry
@@ -725,16 +742,17 @@ module Squared
725
742
  op.append(escape: true, strip: ':')
726
743
  end
727
744
  end
728
- success?(run(sync: sync, exception: exception, banner: banner, from: from), flag == :tag || flag == :save)
745
+ success?(run(sync: sync, exception: exception, banner: banner, from: from), status)
729
746
  end
730
747
 
731
- def network(flag, opts = [], target: nil)
748
+ def network(flag, opts = [], target: nil, name: target)
732
749
  cmd, opts = docker_session('network', flag, opts: opts)
733
750
  op = OptionPartition.new(opts, OPT_DOCKER[:network].fetch(flag, []), cmd, **dockerinit)
734
751
  .clear
735
752
  from = symjoin 'network', flag
736
- if flag == :create
737
- op.add_quote(target)
753
+ case flag
754
+ when :create, :rm
755
+ op.add_quote(name)
738
756
  run(from: from)
739
757
  else
740
758
  list_image(flag, docker_output('ps -a'), from: from) do |id|
@@ -752,13 +770,13 @@ module Squared
752
770
  end
753
771
 
754
772
  def compose?(file = dockerfile)
755
- return file == 3 || file == 4 if file.is_a?(Numeric)
773
+ return file.between?(3, 4) if file.is_a?(Numeric)
756
774
 
757
775
  COMPOSEFILE.include?(File.basename(file))
758
776
  end
759
777
 
760
778
  def bake?(file = dockerfile)
761
- return file == 1 || file == 2 if file.is_a?(Numeric)
779
+ return file.between?(1, 2) if file.is_a?(Numeric)
762
780
 
763
781
  BAKEFILE.include?(File.basename(file))
764
782
  end
@@ -770,11 +788,11 @@ module Squared
770
788
  val.size > 1 ? val : val.first
771
789
  elsif val == true
772
790
  DIR_DOCKER.find { |file| exist?(file) }
773
- elsif val != 'Dockerfile'
791
+ elsif val != DIR_DOCKER.last
774
792
  val
775
793
  end
776
794
  end
777
- basepath((@file.is_a?(Array) ? @file.first : @file) || 'Dockerfile')
795
+ basepath((@file.is_a?(Array) ? @file.first : @file) || DIR_DOCKER.last)
778
796
  end
779
797
 
780
798
  private
@@ -790,7 +808,7 @@ module Squared
790
808
  yield data
791
809
  end
792
810
  rescue => e
793
- log.debug e
811
+ print_error(e, subject: name, hint: 'compose')
794
812
  end
795
813
  end
796
814
 
@@ -873,7 +891,7 @@ module Squared
873
891
  target
874
892
  end
875
893
 
876
- def filter_ps(flag, meth = nil, from: meth || :'container:ps')
894
+ def filter_ps(flag, meth = nil, column: nil, from: meth || :'container:ps')
877
895
  no = false
878
896
  status = case flag.to_sym
879
897
  when :start
@@ -899,6 +917,8 @@ module Squared
899
917
  end
900
918
  cmd = docker_output("#{from.to_s.split(':').first} ps -a",
901
919
  *status.map { |s| quote_option('filter', "status=#{s}") })
920
+ return IO.popen(cmd.temp("--format=\"{{.#{column}}}\"")).map(&:chomp) if column
921
+
902
922
  [cmd, status, no]
903
923
  end
904
924
 
@@ -914,8 +934,8 @@ module Squared
914
934
  elsif filter.match?(/[:_-]$/)
915
935
  /\b#{Regexp.escape(filter)}/
916
936
  else
917
- filter = filter.empty? ? '([:_-]|$)' : "[:_-]#{filter}"
918
- /\b(#{dnsname(name)}|#{tagname(project)}|#{tagmain.split(':', 2).first})#{filter}/
937
+ names = [dnsname(name), subdomainname(name), tagname(project), tagmain.split(':').first]
938
+ /\b(#{names.join('|')})#{filter.empty? ? '([:_-]|$)' : "[:_-]#{filter}"}/
919
939
  end
920
940
  IO.popen(cmd.temp('--format=json')).each do |line|
921
941
  data = JSON.parse(line)
@@ -1079,7 +1099,7 @@ module Squared
1079
1099
  val = val.compact
1080
1100
  .join(':')
1081
1101
  .split(':')
1082
- .map { |s| charname(s.sub(/^\W+/, '')) }
1102
+ .map { |s| subdomainname(s) }
1083
1103
  ret = val.join(':')
1084
1104
  return ret unless ret.size > max
1085
1105
 
@@ -1091,8 +1111,16 @@ module Squared
1091
1111
  ret[0...max]
1092
1112
  end
1093
1113
 
1094
- def dnsname(val)
1095
- charname(val[/^[^a-z\d]*(.*?)[^a-z\d]*$/i, 1].gsub(/-{2,}/, '-'))[0..62].downcase
1114
+ def dnsname(val, max: 63, char: nil)
1115
+ ret = val.sub(/^[^a-z]+/i, '')
1116
+ .sub(/[^a-z\d]+$/i, '')
1117
+ .gsub(/[^a-z\d#{char}]+/i, '-')
1118
+ .gsub(/-{2,}/, '-')[0...max]
1119
+ char ? ret : ret.downcase
1120
+ end
1121
+
1122
+ def subdomainname(val)
1123
+ dnsname(val, max: 128, char: '.').gsub(/\.{2,}/, '.')
1096
1124
  end
1097
1125
 
1098
1126
  def charname(val)
@@ -1104,7 +1132,7 @@ module Squared
1104
1132
  end
1105
1133
 
1106
1134
  def dockerinit
1107
- { project: self, strict: strict?, explicit: '=', bool: %w[true false 1 0] }
1135
+ { project: self, strict: strict?, explicit: '=', bool: %w[1 0 true false] }
1108
1136
  end
1109
1137
 
1110
1138
  def emptyargs(list, hint = nil)
@@ -158,10 +158,10 @@ module Squared
158
158
  no-replace-objects noglob-pathspecs paginate attr-source=b config-env=q exec-path=p
159
159
  namespace=p].freeze,
160
160
  add: %w[A e|edit f|force ignore-errors ignore-missing i|interactive n|dry-run p|patch pathspec-file-nul
161
- refresh renormalize sparse u|update v|verbose chmod=b pathspec-from-file=p].freeze,
162
- branch: %w[a|all create-reflog i|ignore-case omit-empty q|quiet r|remotes v|verbose=+ abbrev=i color=b
163
- column=b contains=b format=q merged=b no-contains=b no-merged=b points-at=b u|set-upstream-to=b
164
- sort=q t|track=b].freeze,
161
+ refresh renormalize resolved sparse u|update v|verbose chmod=b pathspec-from-file=p].freeze,
162
+ branch: %w[a|all create-reflog dry-run i|ignore-case omit-empty q|quiet r|remotes v|verbose=+ abbrev=i color=b
163
+ column=b contains=b delete-merged=q forked=q format=q merged=b no-contains=b no-merged=b
164
+ points-at=b u|set-upstream-to=b sort=q t|track=b].freeze,
165
165
  checkout: %w[l d|detach f|force ignore-other-worktrees ignore-skip-worktree-bits m|merge p|patch
166
166
  pathspec-file-nul q|quiet ours theirs conflict=b orphan=b pathspec-from-file=p t|track=b].freeze,
167
167
  clone: %w[also-filter-submodules bare dissociate l|local mirror no-hardlinks n|no-checkout no-tags o|origin=b
@@ -214,8 +214,9 @@ module Squared
214
214
  i|regexp-ignore-case remove-empty reverse right-only simplify-by-decoration simplify-merges
215
215
  single-worktree show-pulls source sparse stdin topo-order g|walk-reflogs after=q ancestry-path=b?
216
216
  author=q before=q branches=q? committer=q decorate=b decorate-refs=q decorate-refs-exclude=q
217
- exclude=q exclude-hidden=b glob=q grep=q grep-reflog=q n|max-count=i max-parents=i min-parents=i
218
- no-walk=b? remotes=q? since=q since-as-filter=q skip=i tags=q? until=q].freeze,
217
+ exclude=q exclude-hidden=b glob=q grep=q grep-reflog=q n|max-count=i max-count-oldest=i
218
+ max-parents=i min-parents=i no-walk=b? remotes=q? since=q since-as-filter=q skip=i tags=q?
219
+ until=q].freeze,
219
220
  format: %w[t children combined-all-paths dd oneline left-right no-diff-merges parents relative-date
220
221
  show-notes-by-default show-signature date=q diff-merges=b encoding=b expand-tabs=i format=q
221
222
  notes=b pretty=q? show-linear-break=q?].freeze,
@@ -280,7 +281,7 @@ module Squared
280
281
  push: %w[a|all u|include-untracked k|keep-index no-keep-index no-include-untracked pathspec-file-nul p|patch
281
282
  S|staged m|message=q pathspec-from-file=p].freeze,
282
283
  pop: %w[index].freeze,
283
- apply: %w[index].freeze
284
+ apply: %w[index label-base=b label-ours=b label-theirs=b].freeze
284
285
  }.freeze,
285
286
  status: %w[z u=bm? b|branch long s|short show-stash v|verbose=+ column=b find-renames=i? ignore-submodules=b?
286
287
  ignored=b? porcelain=b? untracked-files=b?].freeze,
@@ -967,10 +968,13 @@ module Squared
967
968
  when :commit, :'no-commit'
968
969
  format_desc action, flag, 'branch|:,ours|theirs?,opts*'
969
970
  task flag, [:branch] do |_, args|
970
- branch = args.branch
971
- current = git_spawn('branch --show-current').chomp
971
+ branch = if args.branch == ':'
972
+ choice_refs('Choose a branch', accept: "#{action.capitalize}?")
973
+ else
974
+ args.branch
975
+ end
976
+ current = git_value 'branch --show-current'
972
977
  if branch
973
- branch = choice_refs('Choose a branch', accept: "#{action.capitalize}?") if branch == ':'
974
978
  opts = args.extras
975
979
  if has_value!(opts, 'ours')
976
980
  opts << 's=ort' << 'X=ours'
@@ -1222,7 +1226,7 @@ module Squared
1222
1226
  (heads.dup << cur).each_with_index do |branch, i|
1223
1227
  next unless (i < heads.size && cur != branch) || i == heads.size
1224
1228
 
1225
- git_spawn 'switch --quiet', force && '--force', shell_quote(branch)
1229
+ git_spawn('switch --quiet', force && '--force', shell_quote(branch))
1226
1230
  pull(nil, opts, sync: false, hint: branch) if heads.include?(branch)
1227
1231
  end
1228
1232
  return
@@ -1298,13 +1302,13 @@ module Squared
1298
1302
  if flag
1299
1303
  return unless dir && ref
1300
1304
 
1301
- url = git_spawn("config --get remote.#{remotetrack}.url")
1305
+ url = git_value "config --get remote.#{remotetrack}.url"
1302
1306
  cmd, opts = git_session('clone', worktree: false, opts: opts)
1303
1307
  op = OptionPartition.new(opts, OPT_GIT[:clone], cmd, project: self, strict: strict?,
1304
1308
  no: OPT_GIT[:no][:clone])
1305
1309
  op.add_option('b', ref)
1306
1310
  .delim
1307
- .add_quote(url.chomp)
1311
+ .add_quote(url)
1308
1312
  .add_path(dir)
1309
1313
  .clear
1310
1314
  source
@@ -1497,7 +1501,7 @@ module Squared
1497
1501
  end
1498
1502
  return
1499
1503
  end
1500
- sha = git_spawn('rev-parse --verify HEAD').chomp
1504
+ sha = git_value 'rev-parse --verify HEAD'
1501
1505
  return if sha.empty?
1502
1506
 
1503
1507
  sync = kwargs.fetch(:sync) { invoked_sync?('revbuild', flag) }
@@ -1511,7 +1515,7 @@ module Squared
1511
1515
  else
1512
1516
  args = parse_env('GIT_OPTIONS')
1513
1517
  .grep(/\A--#{Regexp.union(*VAL_GIT[:revbuild])}/)
1514
- .concat(VAL_GIT[:revbuild].map { |key| option(key, prefix: 'git') { |val| basic_option(key, val) } })
1518
+ .concat(VAL_GIT[:revbuild].map { |key| option(key, prefix: 'git') { |val| basic_option key, val } })
1515
1519
  .compact
1516
1520
  OptionPartition.uniq!(args)
1517
1521
  end
@@ -1532,7 +1536,7 @@ module Squared
1532
1536
  start = time_epoch
1533
1537
  ret = build(*@output, sync: sync, from: from)
1534
1538
  rescue => e
1535
- print_error(e, pass: true)
1539
+ print_error(e, subject: name, pass: true)
1536
1540
  else
1537
1541
  if ret == false && !kwargs[:pass]
1538
1542
  print_status('revbuild', subject: name, start: start, loglevel: Logger::WARN, from: :failed)
@@ -1560,7 +1564,7 @@ module Squared
1560
1564
  .clear(pass: false)
1561
1565
  when :commit
1562
1566
  exit 1 unless ref
1563
- branch ||= git_spawn('name-rev --name-only', ref).chomp
1567
+ branch ||= git_value('name-rev --name-only', ref)
1564
1568
  raise ArgumentError, message('could not resolve upstream branch', hint: ref) if branch.empty?
1565
1569
 
1566
1570
  op.append(remotetrack, "#{ref}:#{branch}")
@@ -1755,7 +1759,7 @@ module Squared
1755
1759
  end
1756
1760
  if (n = op.index('>'))
1757
1761
  patch = op.slice!(n, 2)[1]
1758
- elsif !op.exist?(op.last)
1762
+ elsif !op.last.include?('*') && !op.exist?(op.last)
1759
1763
  patch ||= op.pop
1760
1764
  end
1761
1765
  append_pathspec op.extras
@@ -1967,7 +1971,8 @@ module Squared
1967
1971
  list_result(ret, 'branches', banner: banner, from: from)
1968
1972
  return
1969
1973
  else
1970
- if (head = git_spawn('rev-parse --abbrev-ref HEAD').chomp).empty?
1974
+ head = git_value 'rev-parse --abbrev-ref HEAD'
1975
+ if head.empty?
1971
1976
  ret = 0
1972
1977
  else
1973
1978
  git_spawn 'fetch --all --prune --quiet' if option('sync')
@@ -1995,7 +2000,7 @@ module Squared
1995
2000
  end
1996
2001
  on :last, from
1997
2002
  end
1998
- print_error(name, 'no ref found', subject: 'branch', hint: 'head', pass: true) if ret == 0
2003
+ print_error('no ref found', subject: name, hint: 'branch: head', pass: true) if ret == 0
1999
2004
  return
2000
2005
  end
2001
2006
  return unless success?(source(stdout: stdout), !ref && flag == :create) && !ref && remote && target
@@ -2059,7 +2064,7 @@ module Squared
2059
2064
  cmd << '--textconv'
2060
2065
  append_value(files.flat_map { |val| Dir[val] }
2061
2066
  .select { |val| projectpath?(val) }
2062
- .map { |val| shell_quote("HEAD:#{val}") })
2067
+ .map { |val| "HEAD:#{val}" })
2063
2068
  source(banner: false)
2064
2069
  return
2065
2070
  when :oneline
@@ -2073,11 +2078,11 @@ module Squared
2073
2078
  else
2074
2079
  opts << format if format
2075
2080
  end
2076
- list = OPT_GIT[:show] + OPT_GIT[:diff][:show] + OPT_GIT[:log][:diff] + OPT_GIT[:log][:diff_context]
2081
+ list = OPT_GIT[:show] + OPT_GIT[:diff][:show] + collect_hash(OPT_GIT[:log], pass: [:base])
2077
2082
  op = OptionPartition.new(opts, list, cmd,
2078
2083
  project: self, strict: strict?,
2079
2084
  no: OPT_GIT[:no][:show] + collect_hash(OPT_GIT[:no][:log], pass: [:base]))
2080
- op.append(delim: true)
2085
+ op.append
2081
2086
  source(exception: false, banner: flag != :oneline)
2082
2087
  end
2083
2088
 
@@ -2094,7 +2099,7 @@ module Squared
2094
2099
  else
2095
2100
  ref
2096
2101
  end)
2097
- return git_spawn(cmd, exception: false).chomp
2102
+ return git_value(cmd, exception: false)
2098
2103
  when :output
2099
2104
  if opts.delete('sq-quote')
2100
2105
  cmd << '--sq-quote'
@@ -2153,8 +2158,7 @@ module Squared
2153
2158
  when :'sparse-checkout'
2154
2159
  cmd << 'set'
2155
2160
  end
2156
- op = OptionPartition.new(opts, list, cmd, project: self, strict: strict?,
2157
- no: OPT_GIT[:no][flag], single: /\A\d+\z/,
2161
+ op = OptionPartition.new(opts, list, cmd, project: self, strict: strict?, no: OPT_GIT[:no][flag],
2158
2162
  first: case flag
2159
2163
  when :blame, :revert, :'sparse-checkout' then nil
2160
2164
  else matchpathspec
@@ -2303,7 +2307,7 @@ module Squared
2303
2307
  run_e(cmd, stderr: stderr, banner: banner, **kwargs)
2304
2308
  end
2305
2309
  rescue Timeout::Error => e
2306
- print_error(Logger::ERROR, cmd, subject: name, hint: e)
2310
+ print_error(Logger::ERROR, e, subject: name, hint: cmd)
2307
2311
  exit 1 unless exception?(Logger::DEBUG, Logger::INFO)
2308
2312
  rescue => e
2309
2313
  on_error(e, pass: true)
@@ -2349,7 +2353,7 @@ module Squared
2349
2353
  ret = choice_index(msg, git_spawn(cmd, stdout: false), column: /^(\S+)/, force: force, **kwargs)
2350
2354
  case ret
2351
2355
  when Array
2352
- ret.map { |val| val&.stripstyle }
2356
+ ret.map { |val| val.is_a?(Array) ? val.map { |s| s&.stripstyle } : val&.stripstyle }
2353
2357
  when String
2354
2358
  ret.stripstyle
2355
2359
  else
@@ -2527,6 +2531,10 @@ module Squared
2527
2531
  stdout: stdout, banner: banner, **kwargs)
2528
2532
  end
2529
2533
 
2534
+ def git_value(*cmd, exception: true)
2535
+ git_spawn(*cmd, exception: exception).chomp
2536
+ end
2537
+
2530
2538
  def git_grep(cmd, *grep, equals: true, **kwargs)
2531
2539
  ret = []
2532
2540
  git_spawn(cmd, **kwargs).lines(chomp: true).each do |line|