squared 0.3.5 → 0.4.0

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,5 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'date'
4
+ require 'time'
5
+ require 'digest'
6
+
3
7
  module Squared
4
8
  module Workspace
5
9
  module Git
@@ -7,7 +11,9 @@ module Squared
7
11
  GIT_PROTO = %r{^(?:https?|ssh|git|file)://}i.freeze
8
12
  private_constant :GIT_REPO, :GIT_PROTO
9
13
 
10
- def git(name, uri = nil, base: nil, repo: [], options: {})
14
+ attr_reader :revfile
15
+
16
+ def git(name, uri = nil, base: nil, repo: [], options: {}, cache: nil)
11
17
  data = {}
12
18
  check = ->(proj) { proj.is_a?(Project::Git) && !proj.exclude?(Project::Git.ref) && git_clone?(proj.path) }
13
19
  if uri.is_a?(Array)
@@ -17,15 +23,15 @@ module Squared
17
23
  data[name.to_s] = uri
18
24
  elsif name.is_a?(Enumerable)
19
25
  data = name.to_h
20
- elsif name.is_a?(String) && name =~ GIT_PROTO
26
+ elsif name.is_a?(String) && name.match?(GIT_PROTO)
21
27
  base = name
22
28
  @project.each_value { |proj| repo << proj if !proj.parent && check.(proj) }
23
29
  else
24
- warn log_message(Logger::WARN, name, subject: 'git', hint: 'invalid') if warning
30
+ warn log_message(Logger::WARN, name, subject: 'git', hint: 'invalid', pass: true) if warning
25
31
  return self
26
32
  end
27
33
  if base
28
- base = base =~ GIT_PROTO ? "#{base.chomp('/')}/" : @root.join(base)
34
+ base = base.match?(GIT_PROTO) ? "#{base.chomp('/')}/" : @root.join(base)
29
35
  repo.each do |target|
30
36
  if target.is_a?(Project::Git)
31
37
  data[target.localname] = target.project
@@ -55,6 +61,23 @@ module Squared
55
61
  (GIT_REPO[main] ||= {})[key] = [uri.to_s, opts]
56
62
  (@kind[key] ||= []) << Project::Git
57
63
  end
64
+ if cache == true
65
+ revbuild
66
+ elsif cache
67
+ revbuild(file: cache)
68
+ end
69
+ self
70
+ end
71
+
72
+ def revbuild(file: nil)
73
+ @revfile = @home.join(file || "#{@main}.revb")
74
+ @revdoc = JSON.parse(@revfile.read) if @revfile.exist?
75
+ rescue StandardError => e
76
+ @revfile = nil
77
+ warn log_message(Logger::WARN, e, pass: true) if @warning
78
+ self
79
+ else
80
+ @revdoc = {} unless @revdoc.is_a?(Hash)
58
81
  self
59
82
  end
60
83
 
@@ -62,23 +85,76 @@ module Squared
62
85
  (ret = GIT_REPO[main]) && ret[name]
63
86
  end
64
87
 
88
+ def rev_entry(*keys, val: nil, create: true)
89
+ return unless @revdoc
90
+ return @revdoc.dig(*keys) unless val
91
+
92
+ data = @revdoc
93
+ last = keys.pop
94
+ for key in keys
95
+ if data[key].is_a?(Hash)
96
+ data = data[key]
97
+ elsif create
98
+ data = data[key] = {}
99
+ else
100
+ return
101
+ end
102
+ end
103
+ data[last] = val
104
+ end
105
+
106
+ def rev_timeutc(*keys)
107
+ rev_entry(*keys, val: rev_timenow)
108
+ end
109
+
110
+ def rev_timesince(*keys, clock: false)
111
+ epoch = rev_timenow - rev_entry(*keys).to_i
112
+ rescue StandardError
113
+ nil
114
+ else
115
+ time_format(epoch, clock: clock)
116
+ end
117
+
118
+ def rev_clear(name)
119
+ rev_write if rev_entry(name, 'revision', val: '', create: false)
120
+ end
121
+
122
+ def rev_write(name = nil, data = nil, utc: nil)
123
+ return unless @revfile
124
+
125
+ if name
126
+ data&.each { |key, val| rev_entry(name, key, val: val) }
127
+ rev_timeutc(name, utc) if utc
128
+ end
129
+ File.write(@revfile, JSON.pretty_generate(@revdoc))
130
+ end
131
+
65
132
  def git_clone?(path, name = nil)
66
133
  return false if name && !git_repo(name)
67
134
 
68
135
  !path.exist? || path.empty?
69
136
  end
137
+
138
+ private
139
+
140
+ def rev_timenow
141
+ DateTime.now.strftime('%Q').to_i + time_offset
142
+ end
70
143
  end
71
144
  Application.include Git
72
145
 
73
146
  module Project
74
147
  class Git < Base
75
148
  OPT_GIT = {
149
+ common: %w[bare p|paginate P|no-pager glob-pathspecs icase-pathspecs literal-pathspecs no-optional-locks
150
+ no-replace-objects noglob-pathspecs c=q config-env=q exec-path=p namespace=p].freeze,
76
151
  branch: %w[a|all create-reflog i|ignore-case q|quiet r|remotes v|verbose abbrev=i color=b column=b
77
- contains=e format=q merged=e no-contains=e no-merged=e points-at=e u|set-upstream-to=e sort=q
152
+ contains=b format=q merged=b no-contains=b no-merged=b points-at=e u|set-upstream-to=e sort=q
78
153
  t|track=b].freeze,
79
154
  checkout: %w[l d|detach f|force ignore-other-worktrees ignore-skip-worktree-bits m|merge p|patch
80
155
  pathspec-file-nul q quiet orphan=e ours theirs conflict=b pathspec-from-file=p
81
156
  t|track=b].freeze,
157
+ clean: %w[d x X f|force i|interactive n|dry-run q|quiet e|exlcude=q].freeze,
82
158
  diff: {
83
159
  base: %w[0 1|base 2|ours 3|theirs].freeze,
84
160
  show: %w[s exit-code histogram].freeze
@@ -88,7 +164,7 @@ module Squared
88
164
  recurse-submodules-default=b].freeze,
89
165
  pull: %w[4 6 n t a|append atomic dry-run f|force k|keep n|negotiate-only prefetch p|prune q|quiet
90
166
  set-upstream unshallow update-shallow v|verbose deepen=i depth=i j|jobs=i negotiation-tip=q
91
- refmap=q o|server-option=e shallow-exclude=e shallow-since=b upload-pack=e].freeze
167
+ refmap=q o|server-option=q shallow-exclude=e shallow-since=b upload-pack=q].freeze
92
168
  }.freeze,
93
169
  log: {
94
170
  base: %w[all all-match alternate-refs author-date-order basic-regexp bisect boundary cherry cherry-mark
@@ -96,7 +172,7 @@ module Squared
96
172
  first-parent F|fixed-strings follow full-diff full-history ignore-missing invert-grep left-only
97
173
  merge log-size no-max-parents no-min-parents not P|perl-regexp reflog i|regexp-ignore-case
98
174
  remove-empty reverse right-only simplify-by-decoration simplify-merges single-worktree show-pulls
99
- source sparse stdin topo-order g|walk-reflogs after=q ancestry-path=e? author=q before=q
175
+ source sparse stdin topo-order g|walk-reflogs after=q ancestry-path=b? author=q before=q
100
176
  branches=q? committer=q decorate=b decorate-refs=q decorate-refs-exclude=q exclude=q
101
177
  exclude-hidden=b? glob=q grep=q grep-reflog=q L=q n|max-count=i max-parents=i min-parents=i
102
178
  no-walk=b? remotes=q? since=q since-as-filter=q skip=i tags=q? until=q].freeze,
@@ -107,9 +183,9 @@ module Squared
107
183
  W|function-context w|ignore-all-space ignore-blank-lines ignore-cr-at-eol ignore-space-at-eol
108
184
  b|ignore-space-change D|irreversible-delete graph ita-invisible-in-index minimal name-only
109
185
  name-status no-color-moved-ws no-prefix no-renames numstat patch-with-raw patch-with-stat patience
110
- pickaxe-all pickaxe-regex raw shortstat summary a|text abbrev=i? anchored=q B|break-rewrites=e?
186
+ pickaxe-all pickaxe-regex raw shortstat summary a|text abbrev=i? anchored=q B|break-rewrites=b?
111
187
  color=b color-moved=b color-moved-ws=b color-words=q? diff-algorithm=b diff-filter=e? X|dirstat=q?
112
- dirstat-by-file=q? dst-prefix=q C|find-copies=i? find-object=e M|find-renames=i?
188
+ dirstat-by-file=q? dst-prefix=q C|find-copies=i? find-object=b M|find-renames=i?
113
189
  I|ignore-matching-lines=q ignore-submodules=b inter-hunk-context=i line-prefix=q output=p
114
190
  output-indicator-context=q output-indicator-new=q output-indicator-old=q relative=p rotate-to=p
115
191
  skip-to=p src-prefix=q stat=q? stat-width=i stat-name-width=i stat-count=i submodule=b? U|unified=i
@@ -118,25 +194,32 @@ module Squared
118
194
  ls_files: %w[z debug deduplicate directory eol error-unmatch exclude-standard full-name k|killed
119
195
  no-empty-directory recurse-submodules sparse s|stage u|unmerged abbrev=i x|exclude=q
120
196
  X|exclude-from=p exclude-per-directory=p format=q with-tree=q].freeze,
121
- ls_remote: %w[exit-code get-url q|quiet o|server-option=e symref sort=q upload-pack=e].freeze,
122
- pull: %w[e n allow-unrelated-histories ff-only S|gpg-sign=e log=i r|rebase=b? s|strategy=b
123
- X|strategy-option=e].freeze,
197
+ ls_remote: %w[exit-code get-url q|quiet o|server-option=q symref sort=q upload-pack=q].freeze,
198
+ mv: %w[k f|force n|dry-run v|verbose].freeze,
199
+ pull: %w[e n allow-unrelated-histories ff-only S|gpg-sign=qq log=i r|rebase=b? s|strategy=b
200
+ X|strategy-option=b].freeze,
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
202
+ into-name=e log=i s|strategy=b X|strategy-option=b].freeze,
124
203
  rebase: %w[n C=i allow-empty-message apply committer-date-is-author-date edit-todo f|force-rebase ignore-date
125
204
  ignore-whitespace i|interactive keep-base m merge no-ff q|quiet quit r|rebase-merges=b?
126
- reset-author-date root show-current-patch signoff v|verbose empty=b S|gpg-sign=b onto=e
127
- s|strategy=b X|strategy-option=b whitespace=e].freeze,
205
+ reset-author-date root show-current-patch signoff v|verbose empty=b S|gpg-sign=qq onto=e
206
+ s|strategy=b X|strategy-option=b whitespace=b].freeze,
128
207
  reset: %w[N pathspec-file-nul q|quiet pathspec-from-file=p].freeze,
129
208
  restore: %w[ignore-unmerged ignore-skip-worktree-bits m|merge ours p|patch pathspec-file-nul S|staged theirs
130
209
  W|worktree conflict=b pathspec-from-file=p s|source=q].freeze,
210
+ revert: %w[e abort continue no-commit quit reference skip cleanup=b S|gpg-sign=qq m|mainline=i s|signoff
211
+ strategy=b X|strategy-option=b].freeze,
131
212
  rev_parse: {
132
213
  output: %w[absolute-git-dir all flags git-common-dir git-dir is-bare-repository is-inside-git-dir
133
214
  is-inside-work-tree is-shallow-repository local-env-vars no-flags no-revs not q|quiet sq
134
215
  revs-only shared-index-path show-cdup show-prefix show-toplevel show-superproject-working-tree
135
- sq-quote symbolic symbolic-full-name verify abbrev-ref=b? after=q before=q default=e
216
+ sq-quote symbolic symbolic-full-name verify abbrev-ref=b? after=q before=q default=q
136
217
  disambiguate=b exclude=q exclude-hidden=b glob=q git-path=p path-format=b? prefix=q branches=q?
137
218
  remotes=q? resolve-git-dir=p short=i? show-object-format=b? since=q tags=q? until=q].freeze,
138
219
  parseopt: %w[keep-dashdash stop-at-non-option stuck-long].freeze
139
220
  }.freeze,
221
+ rm: %w[r cached f|force n|dry-run ignore-unmatch pathspec-file-nul q|quiet sparse v|verbose
222
+ pathspec-from-file=p].freeze,
140
223
  show: %w[t combined-all-paths no-diff-merges remerge-diff show-signature diff-merges=b encoding=b
141
224
  expand-tabs=i notes=q show-notes=q?].freeze,
142
225
  stash: {
@@ -146,7 +229,8 @@ module Squared
146
229
  pop: %w[index].freeze,
147
230
  apply: %w[index].freeze
148
231
  }.freeze,
149
- tag: %w[create-reflog column=b contains=e? format=q merged=e? n=i no-contains=e? no-merged=e? points-at=q
232
+ status: %w[untracked-files=b? u|ignore-submodules=m? ignored=b?],
233
+ tag: %w[create-reflog column=b contains=b? format=q merged=b? n=i no-contains=b? no-merged=b? points-at=q
150
234
  sort=q].freeze,
151
235
  no: {
152
236
  fetch: {
@@ -162,20 +246,26 @@ module Squared
162
246
  tag: %w[column].freeze,
163
247
  branch: %w[color-moved column color track].freeze,
164
248
  checkout: %w[overwrite-ignore guess overlay progress recurse-submodules track].freeze,
249
+ merge: %w[autostash edit ff gpg-sign log progress overwrite-ignore rerere-autoupdate signoff squash stat
250
+ verify verify-signatures].freeze,
165
251
  rebase: %w[autosquash autostash fork-point gpg-sign keep-empty reapply-cherry-picks reschedule-failed-exec
166
252
  rerere-autoupdate stat update-refs verify].freeze,
167
253
  reset: %w[refresh].freeze,
168
254
  restore: %w[overlay progress recurse-submodules].freeze,
255
+ revert: %w[edit gpg-sign rerere-autoupdate].freeze,
169
256
  show: %w[standard-notes].freeze
170
257
  }.freeze
171
- }
258
+ }.freeze
172
259
  VAL_GIT = {
260
+ merge: {
261
+ send: %w[continue abort quit].freeze
262
+ }.freeze,
173
263
  rebase: {
174
264
  send: %w[continue skip abort quit].freeze,
175
265
  value: %w[true false merges interactive].freeze
176
266
  }.freeze,
177
267
  reset: %w[soft mixed hard merge keep recurse-submodules no-recurse-submodules].freeze
178
- }
268
+ }.freeze
179
269
  private_constant :OPT_GIT, :VAL_GIT
180
270
 
181
271
  class << self
@@ -207,7 +297,7 @@ module Squared
207
297
  end
208
298
 
209
299
  def tasks
210
- %i[pull rebase fetch clone stash status].freeze
300
+ %i[pull rebase fetch clone stash status branch revbuild].freeze
211
301
  end
212
302
 
213
303
  def batchargs
@@ -225,16 +315,17 @@ module Squared
225
315
  'branch' => %i[create set delete move copy list edit current].freeze,
226
316
  'checkout' => %i[commit branch track detach path].freeze,
227
317
  'commit' => %i[add all amend amend-orig].freeze,
228
- 'diff' => %i[head cached branch files view between contain].freeze,
318
+ 'diff' => %i[head branch files view between contain].freeze,
229
319
  'fetch' => %i[origin remote].freeze,
230
320
  'files' => %i[cached modified deleted others ignored].freeze,
321
+ 'git' => %i[clean mv restore revert rm].freeze,
231
322
  'log' => %i[view between contain].freeze,
323
+ 'merge' => %i[commit no-commit send].freeze,
232
324
  'pull' => %i[origin remote].freeze,
233
325
  'rebase' => %i[branch onto send].freeze,
234
326
  'refs' => %i[heads tags remote].freeze,
235
327
  'reset' => %i[commit index patch mode].freeze,
236
- 'restore' => %i[source worktree staged overlay].freeze,
237
- 'rev' => %i[commit branch output parseopt].freeze,
328
+ 'rev' => %i[commit branch output parseopt build].freeze,
238
329
  'show' => %i[format oneline].freeze,
239
330
  'stash' => %i[push pop apply drop list].freeze,
240
331
  'tag' => %i[add delete list].freeze
@@ -242,7 +333,7 @@ module Squared
242
333
 
243
334
  def initialize(*, **)
244
335
  super
245
- initialize_ref(Git.ref) if gitpath.exist?
336
+ initialize_ref Git.ref if gitpath.exist?
246
337
  end
247
338
 
248
339
  def ref
@@ -287,19 +378,6 @@ module Squared
287
378
  commit(flag, refs: refs)
288
379
  end
289
380
  end
290
- when 'restore'
291
- if flag == :source
292
- format_desc action, flag, 'tree,opts*,pathspec*'
293
- task flag, [:tree] do |_, args|
294
- tree = param_guard(action, flag, args: args, key: :tree)
295
- restore(flag, args.extras, tree: tree)
296
- end
297
- else
298
- format_desc action, flag, 'opts*,pathspec+'
299
- task flag do |_, args|
300
- restore flag, args.to_a
301
- end
302
- end
303
381
  when 'tag'
304
382
  case flag
305
383
  when :list
@@ -333,9 +411,19 @@ module Squared
333
411
  case flag
334
412
  when :view, :between, :contain
335
413
  if flag == :view && action == 'log'
336
- format_desc action, flag, '(^)commit*,pathspec*,opts*'
414
+ format_desc action, flag, '(^)commit/H0*,pathspec*,opts*'
337
415
  task flag do |_, args|
338
- logx flag, args.to_a
416
+ index = []
417
+ args.to_a.each do |val|
418
+ if val =~ /^H(\d+)$/
419
+ index << "HEAD~#{$1}"
420
+ elsif (sha = commithash(val))
421
+ index << sha
422
+ elsif val.start_with?('^') || (!%r{^[.\\/]}.match?(val) && !%r{[\\/]$}.match?(val))
423
+ index << shell_quote(val)
424
+ end
425
+ end
426
+ logx(flag, args.to_a.drop(index.size), index: index)
339
427
  end
340
428
  else
341
429
  format_desc action, flag, 'commit1,commit2,pathspec*,opts*'
@@ -345,10 +433,16 @@ module Squared
345
433
  __send__(action == 'log' ? :logx : :diff, flag, args.extras, range: [commit1, commit2])
346
434
  end
347
435
  end
348
- when :head, :cached
349
- format_desc action, flag, 'opts*,pathspec*'
436
+ when :head
437
+ format_desc action, flag, 'commit/H0*,opts*,pathspec*'
350
438
  task flag do |_, args|
351
- diff flag, args.to_a
439
+ index = []
440
+ args.to_a.each do |val|
441
+ break unless val =~ /^H(\d+)$/ || (sha = commithash(val))
442
+
443
+ index << ($1 ? "HEAD~#{$1}" : sha)
444
+ end
445
+ diff(flag, args.to_a.drop(index.size), index: index)
352
446
  end
353
447
  when :branch
354
448
  format_desc action, flag, 'name,opts*,pathspec*'
@@ -493,7 +587,7 @@ module Squared
493
587
  show args.format, args.extras
494
588
  end
495
589
  end
496
- when 'rebase'
590
+ when 'rebase', 'merge'
497
591
  case flag
498
592
  when :branch
499
593
  format_desc action, flag, 'opts*,upstream?,branch?'
@@ -508,11 +602,18 @@ module Squared
508
602
  upstream = param_guard(action, flag, args: args, key: :upstream)
509
603
  rebase(flag, commit: commit, upstream: upstream, branch: args.branch)
510
604
  end
605
+ when :commit, :'no-commit'
606
+ format_desc action, flag, 'branch/commit+,opts*'
607
+ task flag do |_, args|
608
+ args = param_guard(action, flag, args: args.to_a)
609
+ merge flag, args
610
+ end
511
611
  when :send
512
- format_desc(action, flag, VAL_GIT[:rebase][:send], arg: nil)
612
+ format_desc(action, flag, VAL_GIT[action.to_sym][:send], arg: nil)
513
613
  task flag, [:command] do |_, args|
514
- command = param_guard(action, flag, args: args, key: :command)
515
- rebase(flag, command: command)
614
+ command = param_guard(action, flag, args: args, key: :command,
615
+ values: VAL_GIT[action.to_sym][:send])
616
+ __send__(action, flag, command: command)
516
617
  end
517
618
  end
518
619
  when 'rev'
@@ -533,6 +634,11 @@ module Squared
533
634
  task flag, [:ref] do |_, args|
534
635
  rev_parse(flag, ref: args.ref)
535
636
  end
637
+ when :build
638
+ format_desc action, flag, OPT_GIT[:status]
639
+ task flag do |_, args|
640
+ revbuild flag, args.to_a
641
+ end
536
642
  else
537
643
  format_desc action, flag, 'opts*,args*'
538
644
  task flag do |_, args|
@@ -552,6 +658,18 @@ module Squared
552
658
  __send__(action == 'refs' ? :ls_remote : :ls_files, flag, args.to_a)
553
659
  end
554
660
  end
661
+ when 'git'
662
+ format_desc(action, flag, 'opts*', before: case flag
663
+ when :rm
664
+ 'source+,destination'
665
+ when :revert
666
+ 'commit+'
667
+ else
668
+ 'pathspec*'
669
+ end)
670
+ task flag do |_, args|
671
+ git(flag, args.to_a)
672
+ end
555
673
  end
556
674
  end
557
675
  end
@@ -564,8 +682,18 @@ module Squared
564
682
  super
565
683
  end
566
684
 
685
+ def depend(*, **)
686
+ workspace.rev_clear name
687
+ super
688
+ end
689
+
690
+ def clean(*, **)
691
+ workspace.rev_clear name
692
+ super
693
+ end
694
+
567
695
  def pull(flag = nil, opts = [], sync: invoked_sync?('pull', flag), remote: nil)
568
- cmd = git_session 'pull', flag && "--#{flag}"
696
+ cmd, opts = git_session('pull', flag && "--#{flag}", opts: opts)
569
697
  if (val = option('rebase', ignore: false))
570
698
  cmd << case val
571
699
  when '0'
@@ -578,8 +706,8 @@ module Squared
578
706
  no: OPT_GIT[:no][:pull] + OPT_GIT[:no][:fetch][:pull], remote: remote, flag: flag)
579
707
  source(sync: sync, sub: if verbose
580
708
  [
581
- { pat: /^(.+)(\|\s+\d+\s+)([^-]*)(-+)(.*)$/, styles: :red, index: 4 },
582
- { pat: /^(.+)(\|\s+\d+\s+)(\++)(-*)(.*)$/, styles: :green, index: 3 }
709
+ { pat: /^(.+)(\|\s+\d+\s+)([^-]*)(-+)(.*)$/, styles: color(:red), index: 4 },
710
+ { pat: /^(.+)(\|\s+\d+\s+)(\++)(-*)(.*)$/, styles: color(:green), index: 3 }
583
711
  ]
584
712
  end, **threadargs)
585
713
  end
@@ -588,7 +716,7 @@ module Squared
588
716
  command: nil)
589
717
  return pull(:rebase, sync: sync) unless flag
590
718
 
591
- cmd = git_session 'rebase'
719
+ cmd, opts = git_session('rebase', opts: opts)
592
720
  case flag
593
721
  when :branch
594
722
  branch = option_sanitize(opts, OPT_GIT[:rebase], no: OPT_GIT[:no][:rebase]).first
@@ -617,7 +745,7 @@ module Squared
617
745
  end
618
746
 
619
747
  def fetch(flag = nil, opts = [], sync: invoked_sync?('fetch', flag), remote: nil)
620
- cmd = git_session 'fetch'
748
+ cmd, opts = git_session('fetch', opts: opts)
621
749
  cmd << '--all' if !remote && !opts.include?('multiple') && option('all')
622
750
  cmd << '--verbose' if verbose && !opts.include?('quiet')
623
751
  append_pull(opts, collect_hash(OPT_GIT[:fetch]), no: collect_hash(OPT_GIT[:no][:fetch]),
@@ -649,9 +777,8 @@ module Squared
649
777
 
650
778
  def stash(flag = nil, opts = [], sync: invoked_sync?('stash', flag))
651
779
  if flag
652
- cmd = git_session 'stash', flag
653
- list = OPT_GIT[:stash][:common] + OPT_GIT[:stash].fetch(flag, [])
654
- refs = option_sanitize(opts, list).first
780
+ cmd, opts = git_session('stash', flag, opts: opts)
781
+ refs = option_sanitize(opts, OPT_GIT[:stash][:common] + OPT_GIT[:stash].fetch(flag, [])).first
655
782
  case flag
656
783
  when :push
657
784
  append_pathspec refs
@@ -666,14 +793,14 @@ module Squared
666
793
  return
667
794
  end
668
795
  else
669
- git_session 'stash', 'push'
796
+ git_session('stash', 'push', opts: opts)
670
797
  append_option(%w[all keep-index include-untracked staged].freeze, no: true, ignore: false)
671
798
  append_message option('message', 'm', ignore: false)
672
799
  end
673
800
  source(banner: !quiet?, sync: sync, **threadargs)
674
801
  end
675
802
 
676
- def status(*, sync: invoked_sync?('status'), **)
803
+ def status(*)
677
804
  cmd = git_session 'status'
678
805
  cmd << (option('long') ? '--long' : '--short')
679
806
  if (val = option('ignore-submodules', ignore: false))
@@ -690,24 +817,76 @@ module Squared
690
817
  end
691
818
  append_pathspec
692
819
  out, banner, from = source(io: true)
693
- if sync
694
- print_item banner
695
- banner = nil
696
- end
697
820
  ret = write_lines(out, banner: banner, sub: if verbose
821
+ r = color(:red)
822
+ g = color(:green)
698
823
  [
699
- { pat: /^(.)([A-Z?!])(.+)$/, styles: :red, index: 2 },
700
- { pat: /^([A-Z?!])(.+)$/, styles: :green },
701
- { pat: /^(\?\?)(.+)$/, styles: :red },
824
+ { pat: /^(.)([A-Z?!])(.+)$/, styles: r, index: 2 },
825
+ { pat: /^([A-Z?!])(.+)$/, styles: g },
826
+ { pat: /^(\?\?)(.+)$/, styles: r },
702
827
  { pat: /^(## )(.+)(\.{3})(.+)$/,
703
- styles: [nil, :green, nil, :red], index: -1 }
828
+ styles: [nil, g, nil, r], index: -1 }
704
829
  ]
705
830
  end)
706
831
  list_result(ret, 'files', from: from, action: 'modified')
707
832
  end
708
833
 
834
+ def revbuild(flag = nil, opts = [], sync: invoked_sync?('revbuild', flag), **kwargs)
835
+ statusargs = lambda do
836
+ {
837
+ include: relativepath(as_a(kwargs[:include]), all: true),
838
+ exclude: relativepath(as_a(kwargs[:exclude]), all: true)
839
+ }
840
+ end
841
+ unless workspace.closed
842
+ if @revbuild
843
+ statusargs.().each { |key, val| @revbuild[key] += val }
844
+ else
845
+ @revbuild = statusargs.()
846
+ end
847
+ return
848
+ end
849
+ sha = source(git_output('rev-parse --verify HEAD'), io: true, banner: false, stdout: true).first.to_s.chomp
850
+ return if sha.empty?
851
+
852
+ args = []
853
+ kwargs = kwargs.key?(:include) || kwargs.key?(:exclude) ? statusargs.() : @revbuild || {}
854
+ case flag
855
+ when :build
856
+ opts = option_sanitize(opts, OPT_GIT[:status], target: args).first
857
+ option_clear opts
858
+ else
859
+ args << basic_option('untracked-files', flag) if (flag = option('untracked-files', prefix: 'git'))
860
+ args << basic_option('ignore-submodules', flag) if (flag = option('ignore-submodules', prefix: 'git'))
861
+ args << basic_option('ignored', flag) if (flag = option('ignored', prefix: 'git'))
862
+ end
863
+ if (cur = workspace.rev_entry(name)) && cur['revision'] == sha
864
+ files = status_digest(*args, **kwargs)
865
+ if cur['files'].size == files.size && cur['files'].find { |key, val| files[key] != val }.nil?
866
+ if verbose
867
+ if (since = workspace.rev_timesince(name, 'build'))
868
+ puts log_message(Logger::INFO, name, 'no changes', subject: 'revbuild', hint: "#{since} ago")
869
+ else
870
+ workspace.rev_timeutc(name, 'build')
871
+ end
872
+ end
873
+ return
874
+ end
875
+ end
876
+ start = epochtime
877
+ build(@output, sync: sync, from: :'git:revbuild')
878
+ rescue StandardError => e
879
+ warn log_message(Logger::WARN, e, pass: true) if warning?
880
+ else
881
+ if verbose
882
+ msg = sub_style('completed', styles: theme[:active])
883
+ puts log_message(Logger::INFO, name, msg, subject: 'revbuild', hint: time_format(epochtime - start))
884
+ end
885
+ workspace.rev_write(name, { 'revision' => sha, 'files' => status_digest(*args, **kwargs) }, utc: 'build')
886
+ end
887
+
709
888
  def reset(flag, opts = [], refs: nil, ref: nil, mode: nil, commit: nil)
710
- cmd = git_session 'reset'
889
+ cmd, opts = git_session('reset', opts: opts)
711
890
  case flag
712
891
  when :commit, :index
713
892
  out = option_sanitize(opts, OPT_GIT[:reset] + VAL_GIT[:reset], no: OPT_GIT[:no][:reset]).first
@@ -732,21 +911,19 @@ module Squared
732
911
  return
733
912
  end
734
913
  unless ref == false
735
- append_commit ref
914
+ append_commit(ref, head: true)
736
915
  append_pathspec refs if refs
737
916
  end
738
917
  source
739
918
  end
740
919
 
741
920
  def checkout(flag, opts = [], branch: nil, origin: nil, create: nil, commit: nil, detach: nil)
742
- cmd = git_session 'checkout'
921
+ cmd, opts = git_session('checkout', opts: opts)
743
922
  append_option 'force', 'merge'
744
923
  case flag
745
924
  when :branch
746
925
  cmd << '--detach' if detach == 'd' || option('detach')
747
- if (val = option('track'))
748
- cmd << shell_option('track', val)
749
- end
926
+ append_option('track', equals: true)
750
927
  cmd << if create
751
928
  shell_option(create, branch)
752
929
  else
@@ -778,7 +955,7 @@ module Squared
778
955
  end
779
956
 
780
957
  def tag(flag, opts = [], refs: [], message: nil, commit: nil)
781
- cmd = git_session 'tag'
958
+ cmd, opts = git_session('tag', opts: opts)
782
959
  case flag
783
960
  when :add
784
961
  if option('sign')
@@ -786,8 +963,8 @@ module Squared
786
963
  elsif !session_arg?('s', 'sign', 'u', 'local-user')
787
964
  cmd << '--annotate'
788
965
  end
789
- if !commit && message && (hash = commithash(message))
790
- commit = hash
966
+ if !commit && message && (sha = commithash(message))
967
+ commit = sha
791
968
  else
792
969
  append_message message
793
970
  end
@@ -810,41 +987,27 @@ module Squared
810
987
  source
811
988
  end
812
989
 
813
- def logx(flag, opts = [], range: [])
814
- cmd = git_session 'log'
815
- files = option_sanitize(opts, collect_hash(OPT_GIT[:log]), no: collect_hash(OPT_GIT[:no][:log])).first
990
+ def logx(flag, opts = [], range: [], index: [])
991
+ cmd, opts = git_session('log', opts: opts)
992
+ refs = option_sanitize(opts, collect_hash(OPT_GIT[:log]), no: collect_hash(OPT_GIT[:no][:log])).first
816
993
  case flag
817
994
  when :between, :contain
818
995
  cmd << shell_quote(range.join(flag == :between ? '..' : '...'))
819
996
  else
820
- commit, files = files.partition do |val|
821
- val.start_with?('^') || (!%r{^[.\\/]}.match?(val) && !%r{[\\/]$}.match?(val)) || commithash(val)
822
- end
823
- cmd.merge(commit.map { |val| commithash(val) || shell_quote(val) }) unless commit.empty?
997
+ cmd.merge(index)
824
998
  end
825
999
  append_nocolor
826
- append_pathspec files
1000
+ append_pathspec refs
827
1001
  source(exception: false)
828
1002
  end
829
1003
 
830
- def diff(flag, opts = [], refs: [], branch: nil, range: [])
831
- cmd = git_session 'diff'
1004
+ def diff(flag, opts = [], refs: [], branch: nil, range: [], index: [])
1005
+ cmd, opts = git_session('diff', opts: opts)
832
1006
  files = option_sanitize(opts, collect_hash(OPT_GIT[:diff]) + OPT_GIT[:log][:diff],
833
1007
  no: OPT_GIT[:no][:log][:diff]).first
834
1008
  case flag
835
1009
  when :files, :view, :between, :contain
836
1010
  cmd.delete('--cached')
837
- else
838
- items = files.dup
839
- sha = nil
840
- files.clear
841
- items.each do |val|
842
- if (s = commithash(val))
843
- (sha ||= []).push(s)
844
- else
845
- files << val
846
- end
847
- end
848
1011
  end
849
1012
  append_nocolor
850
1013
  if flag == :files
@@ -859,15 +1022,14 @@ module Squared
859
1022
  cmd.delete('--merge-base')
860
1023
  cmd << shell_quote(range.join(flag == :between ? '..' : '...'))
861
1024
  else
862
- cmd << '--cached' if flag == :cached
863
1025
  cmd << '--merge-base' if option('merge-base')
864
1026
  cmd << shell_quote(branch) if branch
865
- if sha
1027
+ if !index.empty?
866
1028
  if session_arg?('cached')
867
- raise_error('diff', sha.join(', '), hint: 'one commit') if sha.size > 1
868
- cmd << sha.first
1029
+ raise_error("one commit only: #{index.join(', ')}", hint: '--cached') if index.size > 1
1030
+ cmd << index.first
869
1031
  else
870
- cmd.merge(sha)
1032
+ cmd.merge(index)
871
1033
  end
872
1034
  elsif (n = option('index'))
873
1035
  cmd << "HEAD~#{n}"
@@ -884,12 +1046,12 @@ module Squared
884
1046
  if !message && !amend
885
1047
  return if pass
886
1048
 
887
- raise_error('commit', 'GIT_MESSAGE="description"', hint: 'missing')
1049
+ raise_error('missing message', hint: 'GIT_MESSAGE="description"')
888
1050
  end
889
1051
  pathspec = if flag == :all || (amend && refs.size == 1 && refs.first == '*')
890
1052
  '--all'
891
1053
  elsif (refs = projectmap(refs)).empty?
892
- raise_error('commit', 'pathspec', hint: 'missing')
1054
+ raise_error 'no qualified pathspec'
893
1055
  else
894
1056
  "-- #{refs.join(' ')}"
895
1057
  end
@@ -898,13 +1060,15 @@ module Squared
898
1060
  upstream = nil
899
1061
  source(git_output('fetch --no-tags --quiet'), io: true, banner: false)
900
1062
  source(git_output('branch -vv --list'), io: true, banner: false).first.each do |val|
901
- next unless (data = /^\*\s(\S+)\s+(\h+)(?:\s\[(.+?)(?=\]\s)\])?\s/.match(val))
1063
+ next unless (r = /^\*\s(\S+)\s+(\h+)(?:\s\[(.+?)(?=\]\s)\])?\s/.match(val))
902
1064
 
903
- branch = data[1]
904
- if !data[3]
1065
+ branch = r[1]
1066
+ if r[3]
1067
+ origin = r[3][%r{^(.+)/#{Regexp.escape(branch)}$}, 1]
1068
+ else
905
1069
  unless (origin = option('repository', prefix: 'git', ignore: false))
906
- out = source(git_output('log -n1 --format=%h%d'), io: true, stdout: true, banner: false).first
907
- if out =~ /^#{data[2]} \(HEAD -> #{Regexp.escape(branch)}, (.+?)\)$/
1070
+ out = source(git_output('log -n1 --format=%h%d'), io: true, banner: false, stdout: true).first
1071
+ if out =~ /^#{r[2]} \(HEAD -> #{Regexp.escape(branch)}, (.+?)\)$/
908
1072
  split_escape($1).each do |val|
909
1073
  next unless val.end_with?("/#{branch}")
910
1074
 
@@ -914,12 +1078,10 @@ module Squared
914
1078
  end
915
1079
  end
916
1080
  upstream = true if origin
917
- elsif data[3] =~ %r{^(.+)/#{Regexp.escape(branch)}$}
918
- origin = $1
919
1081
  end
920
1082
  break
921
1083
  end
922
- raise_error('commit', 'work tree is not usable') unless origin && branch
1084
+ raise_error 'work tree is not usable' unless origin && branch
923
1085
  cmd = git_session('commit', option('dry-run') && '--dry-run', options: false)
924
1086
  if amend
925
1087
  cmd << '--amend'
@@ -946,8 +1108,24 @@ module Squared
946
1108
  source b
947
1109
  end
948
1110
 
949
- def branch(flag, opts = [], refs: [], ref: nil, target: nil)
950
- cmd = git_session 'branch'
1111
+ def merge(flag, opts = [], command: nil)
1112
+ cmd, opts = git_session('merge', opts: opts)
1113
+ case flag
1114
+ when :commit, :'no-commit'
1115
+ refs = option_sanitize(opts, OPT_GIT[:merge], no: OPT_GIT[:no][:merge]).first
1116
+ raise_error 'no branch/commit' if refs.empty?
1117
+ cmd << "--#{flag}" << '--'
1118
+ append_commit(*refs)
1119
+ else
1120
+ return unless VAL_GIT[:merge][:send].include?(command)
1121
+
1122
+ cmd << "--#{command}"
1123
+ end
1124
+ source
1125
+ end
1126
+
1127
+ def branch(flag = nil, opts = [], refs: [], ref: nil, target: nil)
1128
+ cmd, opts = git_session('branch', opts: opts)
951
1129
  stdout = false
952
1130
  case flag
953
1131
  when :create
@@ -974,7 +1152,7 @@ module Squared
974
1152
  end
975
1153
  ref = nil
976
1154
  when :delete
977
- force, list = refs.partition { |val| val =~ /^[\^~]/ }
1155
+ force, list = refs.partition { |val| val.match?(/^[\^~]/) }
978
1156
  force.each do |val|
979
1157
  dr = val[0, 3]
980
1158
  d = dr.include?('^') ? '-D' : '-d'
@@ -994,10 +1172,9 @@ module Squared
994
1172
  cmd << '--edit-description'
995
1173
  when :current
996
1174
  cmd << '--show-current'
997
- else
998
- opts = option_sanitize(opts, OPT_GIT[:branch], no: OPT_GIT[:no][:branch]).first
1175
+ when :list
999
1176
  grep = []
1000
- opts.each do |opt|
1177
+ option_sanitize(opts, OPT_GIT[:branch], no: OPT_GIT[:no][:branch]).first.each do |opt|
1001
1178
  if opt =~ /^(v+)$/
1002
1179
  cmd << "-#{$1}"
1003
1180
  else
@@ -1009,42 +1186,46 @@ module Squared
1009
1186
  out, banner, from = source(io: true)
1010
1187
  print_item banner
1011
1188
  ret = write_lines(out, sub: [
1012
- { pat: /^(\*\s+)(\S+)(\s*)$/, styles: :green, index: 2 },
1013
- { pat: %r{^(\s*)(remotes/\S+)(.*)$}, styles: :red, index: 2 }
1189
+ { pat: /^(\*\s+)(\S+)(\s*)$/, styles: color(:green), index: 2 },
1190
+ { pat: %r{^(\s*)(remotes/\S+)(.*)$}, styles: color(:red), index: 2 }
1014
1191
  ])
1015
1192
  list_result(ret, 'branches', from: from)
1016
1193
  return
1194
+ else
1195
+ head = source(git_output('rev-parse --abbrev-ref HEAD'), io: true, banner: false, stdout: true).first.chomp
1196
+ if head.empty?
1197
+ ret = 0
1198
+ else
1199
+ out, banner, from = source(cmd << '-vv --no-abbrev --list', io: true)
1200
+ ret = write_lines(out, grep: /^\*\s+#{Regexp.escape(head)}\s/, banner: banner, first: true) do |line|
1201
+ next line if stdin?
1202
+
1203
+ data = line.sub(/^\*\s+/, '').split(/\s+/)
1204
+ a = sub_style(data[0], styles: theme[:inline])
1205
+ b = sub_style(data[1], styles: theme[:extra])
1206
+ r = /\A(?:\[(.+?)(?=\]\s)\]\s)?(.+)\z/m.match(data[2..-1].join(' '))
1207
+ [" Branch: #{a + (r[1] ? " (#{r[1]})" : '')}", " Commit: #{b}", "Message: #{r[2]}"].compact.join("\n")
1208
+ end
1209
+ on :last, from
1210
+ end
1211
+ if ret == 0
1212
+ warn log_message(Logger::WARN, name, 'no ref found', subject: 'branch', hint: 'head', pass: true)
1213
+ end
1214
+ return
1017
1215
  end
1018
1216
  cmd << shell_escape(target) if target
1019
1217
  cmd << shell_escape(ref) if ref
1020
1218
  source(stdout: stdout)
1021
1219
  end
1022
1220
 
1023
- def restore(flag, opts = [], tree: nil)
1024
- cmd = git_session 'restore'
1025
- refs = option_sanitize(opts, OPT_GIT[:restore], no: OPT_GIT[:no][:restore]).first
1026
- if flag == :source
1027
- cmd << '--patch' if refs.empty?
1028
- cmd << shell_option('source', tree)
1029
- else
1030
- cmd << "--#{flag}"
1031
- end
1032
- if session_arg?('p', 'patch')
1033
- option_clear refs
1034
- else
1035
- append_pathspec(refs, expect: true)
1036
- end
1037
- source(sync: false, stderr: true)
1038
- end
1039
-
1040
1221
  def show(format, opts = [])
1041
- cmd = git_session 'show'
1222
+ cmd, opts = git_session('show', opts: opts)
1042
1223
  if format
1043
1224
  case (val = format.downcase)
1044
1225
  when 'oneline', 'short', 'medium', 'full', 'fuller', 'reference', 'email', 'raw'
1045
1226
  cmd << basic_option('format', val)
1046
1227
  else
1047
- if format =~ /^t?format:/ || format.include?('%')
1228
+ if format.match?(/^t?format:/) || format.include?('%')
1048
1229
  cmd << quote_option('pretty', format)
1049
1230
  else
1050
1231
  opts << format
@@ -1062,18 +1243,19 @@ module Squared
1062
1243
  end
1063
1244
 
1064
1245
  def rev_parse(flag, opts = [], ref: nil, size: nil)
1065
- cmd = git_session 'rev-parse', if flag == :parseopt
1066
- '--parseopt'
1067
- elsif opts.delete('sq-quote')
1068
- '--sq-quote'
1069
- end
1246
+ cmd, opts = git_session('rev-parse', opts: opts)
1247
+ cmd << if flag == :parseopt
1248
+ '--parseopt'
1249
+ elsif opts.delete('sq-quote')
1250
+ '--sq-quote'
1251
+ end
1070
1252
  case flag
1071
1253
  when :commit
1072
1254
  cmd << ((n = size.to_i) > 0 ? basic_option('short', [n, 5].max) : '--verify')
1073
- append_commit ref
1255
+ append_commit(ref, head: true)
1074
1256
  when :branch
1075
1257
  cmd << '--abbrev-ref'
1076
- append_commit ref
1258
+ append_commit(ref, head: true)
1077
1259
  else
1078
1260
  args = option_sanitize(opts, OPT_GIT[:rev_parse][flag]).first
1079
1261
  append_value(args, escape: session_arg?('sq-quote'))
@@ -1082,7 +1264,7 @@ module Squared
1082
1264
  end
1083
1265
 
1084
1266
  def ls_remote(flag, opts = [], remote: nil)
1085
- cmd = git_session 'ls-remote', '--refs'
1267
+ cmd, opts = git_session('ls-remote', '--refs', opts: opts)
1086
1268
  cmd << "--#{flag}" unless flag == :remote
1087
1269
  grep = option_sanitize(opts, OPT_GIT[:ls_remote]).first
1088
1270
  cmd << shell_quote(remote) if remote
@@ -1093,7 +1275,7 @@ module Squared
1093
1275
  end
1094
1276
 
1095
1277
  def ls_files(flag, opts = [])
1096
- git_session 'ls-files', "--#{flag}"
1278
+ opts = git_session('ls-files', "--#{flag}", opts: opts).last
1097
1279
  grep = option_sanitize(opts, OPT_GIT[:ls_files]).first
1098
1280
  out, banner, from = source(io: true)
1099
1281
  print_item banner
@@ -1101,10 +1283,51 @@ module Squared
1101
1283
  list_result(ret, 'files', from: from, grep: grep)
1102
1284
  end
1103
1285
 
1286
+ def git(flag, opts = [])
1287
+ cmd, opts = git_session(flag, opts: opts)
1288
+ refs = option_sanitize(opts, OPT_GIT[flag], no: OPT_GIT[:no][flag]).first
1289
+ refs = projectmap(refs) unless flag == :revert
1290
+ sync = false
1291
+ stderr = true
1292
+ case flag
1293
+ when :clean
1294
+ unless refs.empty?
1295
+ cmd << '--'
1296
+ cmd.merge(refs)
1297
+ end
1298
+ sync = true
1299
+ stderr = false
1300
+ when :restore
1301
+ if session_arg?('p', 'patch')
1302
+ option_clear refs
1303
+ else
1304
+ append_pathspec(refs, expect: true)
1305
+ end
1306
+ when :revert
1307
+ if VAL_GIT[:rebase][:send].any? { |val| session_arg?(val) }
1308
+ option_clear refs
1309
+ elsif refs.empty?
1310
+ raise_error 'no commit given'
1311
+ else
1312
+ append_commit(*refs)
1313
+ end
1314
+ when :mv
1315
+ raise_error 'no source/destination' unless refs.size > 1
1316
+ cmd.merge(refs)
1317
+ when :rm
1318
+ append_pathspec(refs, expect: true)
1319
+ end
1320
+ source(sync: sync, stderr: stderr)
1321
+ end
1322
+
1104
1323
  def clone?
1105
1324
  ref?(workspace.baseref) && workspace.git_clone?(path, name) ? 1 : false
1106
1325
  end
1107
1326
 
1327
+ def revbuild?
1328
+ build? && !!workspace.revfile
1329
+ end
1330
+
1108
1331
  def enabled?(*, **kwargs)
1109
1332
  super || (kwargs[:base] == false && !!clone?)
1110
1333
  end
@@ -1117,7 +1340,7 @@ module Squared
1117
1340
  if cmd.respond_to?(:done)
1118
1341
  if io && banner == false
1119
1342
  from = nil
1120
- elsif !from && (from = cmd.drop(1).find { |val| val =~ /^[a-z][a-z\-]{2,}$/ })
1343
+ elsif !from && (from = cmd.drop(1).find { |val| val.match?(/^[a-z][a-z\-]{2,}$/) })
1121
1344
  from = :"git:#{from}"
1122
1345
  end
1123
1346
  banner &&= cmd.temp { |val| val.start_with?('--work-tree') || val.start_with?('--git-dir') }
@@ -1163,13 +1386,13 @@ module Squared
1163
1386
  ret = on(:error, from, e)
1164
1387
  raise if exception && ret != true
1165
1388
 
1166
- warn log_message(Logger::WARN, e) if warning?
1389
+ warn log_message(Logger::WARN, e, pass: true) if warning?
1167
1390
  else
1168
1391
  on :last, from
1169
1392
  end
1170
1393
  end
1171
1394
 
1172
- def write_lines(data, banner: nil, loglevel: nil, grep: nil, sub: nil, pass: false)
1395
+ def write_lines(data, banner: nil, loglevel: nil, grep: nil, sub: nil, pass: false, first: false)
1173
1396
  grep = as_a(grep).map do |val|
1174
1397
  next val if val.is_a?(Regexp)
1175
1398
 
@@ -1182,6 +1405,7 @@ module Squared
1182
1405
  data.each do |line|
1183
1406
  next if grep&.none? { |pat| pat.match?(line) }
1184
1407
 
1408
+ line = yield line if block_given?
1185
1409
  if loglevel
1186
1410
  log&.add loglevel, line
1187
1411
  else
@@ -1193,8 +1417,9 @@ module Squared
1193
1417
  end
1194
1418
  end
1195
1419
  ret += 1
1420
+ break if first
1196
1421
  end
1197
- print_item banner, out if banner && (ret > 0 || !pass)
1422
+ print_item banner, out if banner && (ret > 0 || (!pass && !first))
1198
1423
  ret
1199
1424
  end
1200
1425
 
@@ -1217,6 +1442,21 @@ module Squared
1217
1442
  on :last, from
1218
1443
  end
1219
1444
 
1445
+ def status_digest(*args, algorithm: Digest::SHA256, **kwargs)
1446
+ glob = kwargs.fetch(:include, [])
1447
+ pass = kwargs.fetch(:exclude, [])
1448
+ ret = {}
1449
+ out = source(git_output('status -s --porcelain', *args), io: true, banner: false).first
1450
+ out.each do |line|
1451
+ next unless (file = line[/^[A-Z ?!]{3}"?(.+?)"?$/, 1])
1452
+ next if !glob.empty? && glob.none? { |val| File.fnmatch?(val, file, File::FNM_DOTMATCH) }
1453
+ next if !pass.empty? && pass.any? { |val| File.fnmatch?(val, file, File::FNM_DOTMATCH) }
1454
+
1455
+ ret[file] = algorithm.hexdigest(File.read(basepath(file)))
1456
+ end
1457
+ ret
1458
+ end
1459
+
1220
1460
  def append_pull(opts, list, target: @session, no: nil, flag: nil, remote: nil)
1221
1461
  cmd << '--force' if option('force')
1222
1462
  rsm = append_submodules(target: target)
@@ -1256,9 +1496,13 @@ module Squared
1256
1496
  option_clear(out, target: target, subject: flag.to_s) if flag
1257
1497
  end
1258
1498
 
1259
- def append_commit(val, target: @session)
1260
- val = val.to_s.strip
1261
- target << (val.empty? ? append_head(target: target) || 'HEAD' : val)
1499
+ def append_commit(*val, target: @session, head: false)
1500
+ val.compact!
1501
+ if !val.empty?
1502
+ val.each { |ref| target << (commithash(ref) || shell_quote(ref)) }
1503
+ elsif head
1504
+ target << (append_head(target: target) || 'HEAD')
1505
+ end
1262
1506
  end
1263
1507
 
1264
1508
  def append_pathspec(files = [], target: @session, expect: false, parent: false)
@@ -1270,9 +1514,9 @@ module Squared
1270
1514
  end
1271
1515
  files = projectmap(files, parent: parent)
1272
1516
  if !files.empty?
1273
- target << "-- #{files.join(' ')}"
1517
+ target << '--' << files.join(' ')
1274
1518
  elsif expect
1275
- raise_error(parent ? 'pathspec not present' : 'pathspec not within worktree', hint: 'invalid')
1519
+ raise_error(parent ? 'pathspec not present' : 'pathspec not within worktree')
1276
1520
  end
1277
1521
  end
1278
1522
  end
@@ -1307,9 +1551,13 @@ module Squared
1307
1551
  end
1308
1552
  end
1309
1553
 
1310
- def git_session(*cmd, worktree: true, **kwargs)
1554
+ def git_session(*cmd, opts: nil, worktree: true, **kwargs)
1311
1555
  dir = worktree ? ["--work-tree=#{shell_quote(path)}", "--git-dir=#{shell_quote(gitpath)}"] : []
1312
- session('git', *dir, *cmd, **kwargs)
1556
+ ret = session('git', *dir, **kwargs)
1557
+ return ret.merge(cmd) unless opts
1558
+
1559
+ opts = option_sanitize(opts, OPT_GIT[:common]).first
1560
+ [ret.merge(cmd), opts]
1313
1561
  end
1314
1562
 
1315
1563
  def git_output(*cmd, **kwargs)
@@ -1327,11 +1575,11 @@ module Squared
1327
1575
  end
1328
1576
 
1329
1577
  def gitpath
1330
- basepath('.git')
1578
+ basepath '.git'
1331
1579
  end
1332
1580
 
1333
1581
  def commithash(val)
1334
- val =~ /\A#\{(\h{5,40})\}\z/ ? $1 : nil
1582
+ val[/^#\{(\h{5,40})\}$/, 1]
1335
1583
  end
1336
1584
 
1337
1585
  def threadargs