squared 0.3.11 → 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,29 +11,27 @@ 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)
14
20
  base = name
15
- uri.each do |val|
16
- if (proj = @project[val.to_s]) && check.(proj)
17
- repo << proj
18
- end
19
- end
21
+ uri.each { |val| repo << proj if (proj = @project[val.to_s]) && check.(proj) }
20
22
  elsif uri
21
23
  data[name.to_s] = uri
22
24
  elsif name.is_a?(Enumerable)
23
25
  data = name.to_h
24
- elsif name.is_a?(String) && name =~ GIT_PROTO
26
+ elsif name.is_a?(String) && name.match?(GIT_PROTO)
25
27
  base = name
26
28
  @project.each_value { |proj| repo << proj if !proj.parent && check.(proj) }
27
29
  else
28
- 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
29
31
  return self
30
32
  end
31
33
  if base
32
- base = base =~ GIT_PROTO ? "#{base.chomp('/')}/" : @root.join(base)
34
+ base = base.match?(GIT_PROTO) ? "#{base.chomp('/')}/" : @root.join(base)
33
35
  repo.each do |target|
34
36
  if target.is_a?(Project::Git)
35
37
  data[target.localname] = target.project
@@ -59,6 +61,23 @@ module Squared
59
61
  (GIT_REPO[main] ||= {})[key] = [uri.to_s, opts]
60
62
  (@kind[key] ||= []) << Project::Git
61
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)
62
81
  self
63
82
  end
64
83
 
@@ -66,36 +85,86 @@ module Squared
66
85
  (ret = GIT_REPO[main]) && ret[name]
67
86
  end
68
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
+
69
132
  def git_clone?(path, name = nil)
70
133
  return false if name && !git_repo(name)
71
134
 
72
135
  !path.exist? || path.empty?
73
136
  end
137
+
138
+ private
139
+
140
+ def rev_timenow
141
+ DateTime.now.strftime('%Q').to_i + time_offset
142
+ end
74
143
  end
75
144
  Application.include Git
76
145
 
77
146
  module Project
78
147
  class Git < Base
79
- include Prompt
80
-
81
148
  OPT_GIT = {
82
- branch: %w[a|all create-reflog i|ignore-case q|quiet r|remotes v|verbose vv abbrev=i color=b column=b
83
- contains=e format=q merged=e no-contains=e no-merged=e points-at=e u|set-upstream-to=e sort=q
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,
151
+ branch: %w[a|all create-reflog i|ignore-case q|quiet r|remotes v|verbose abbrev=i color=b column=b
152
+ contains=b format=q merged=b no-contains=b no-merged=b points-at=e u|set-upstream-to=e sort=q
84
153
  t|track=b].freeze,
85
154
  checkout: %w[l d|detach f|force ignore-other-worktrees ignore-skip-worktree-bits m|merge p|patch
86
- pathspec-file-nul q|quiet orphan=e ours theirs conflict=b pathspec-from-file=p
155
+ pathspec-file-nul q quiet orphan=e ours theirs conflict=b pathspec-from-file=p
87
156
  t|track=b].freeze,
157
+ clean: %w[d x X f|force i|interactive n|dry-run q|quiet e|exlcude=q].freeze,
88
158
  diff: {
89
159
  base: %w[0 1|base 2|ours 3|theirs].freeze,
90
160
  show: %w[s exit-code histogram].freeze
91
161
  }.freeze,
92
162
  fetch: {
93
- base: %w[multiple progress P|prune-tags refetch stdin u|update-head-ok
163
+ base: %w[multiple progress P|prune-tags refetch stdin u|update-head-ok recurse-submodules=b
94
164
  recurse-submodules-default=b].freeze,
95
- pull: %w[4 6 n t a|append atomic dry-run f|force k|keep negotiate-only prefetch p|prune q|quiet
165
+ pull: %w[4 6 n t a|append atomic dry-run f|force k|keep n|negotiate-only prefetch p|prune q|quiet
96
166
  set-upstream unshallow update-shallow v|verbose deepen=i depth=i j|jobs=i negotiation-tip=q
97
- recurse-submodules=v refmap=q o|server-option=e shallow-exclude=e shallow-since=v
98
- upload-pack=e].freeze
167
+ refmap=q o|server-option=q shallow-exclude=e shallow-since=b upload-pack=q].freeze
99
168
  }.freeze,
100
169
  log: {
101
170
  base: %w[all all-match alternate-refs author-date-order basic-regexp bisect boundary cherry cherry-mark
@@ -103,47 +172,54 @@ module Squared
103
172
  first-parent F|fixed-strings follow full-diff full-history ignore-missing invert-grep left-only
104
173
  merge log-size no-max-parents no-min-parents not P|perl-regexp reflog i|regexp-ignore-case
105
174
  remove-empty reverse right-only simplify-by-decoration simplify-merges single-worktree show-pulls
106
- 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
107
176
  branches=q? committer=q decorate=b decorate-refs=q decorate-refs-exclude=q exclude=q
108
177
  exclude-hidden=b? glob=q grep=q grep-reflog=q L=q n|max-count=i max-parents=i min-parents=i
109
178
  no-walk=b? remotes=q? since=q since-as-filter=q skip=i tags=q? until=q].freeze,
110
179
  format: %w[t children combined-all-paths oneline left-right no-diff-merges parents relative-date
111
- show-signature date=q diff-merges=b encoding=b expand-tabs=i format=q notes=b pretty=q?
180
+ show-signature date=q diff-merges=b encoding=b expand-tabs=i format=q notes=q pretty=q?
112
181
  show-linear-break=q?].freeze,
113
182
  diff: %w[p R u z l=i G=q O=q S=q binary check compact-summary cumulative find-copies-harder full-index
114
183
  W|function-context w|ignore-all-space ignore-blank-lines ignore-cr-at-eol ignore-space-at-eol
115
184
  b|ignore-space-change D|irreversible-delete graph ita-invisible-in-index minimal name-only
116
185
  name-status no-color-moved-ws no-prefix no-renames numstat patch-with-raw patch-with-stat patience
117
- pickaxe-all pickaxe-regex raw shortstat summary a|text abbrev=i? anchored=q B|break-rewrites=e?
118
- color=b color-moved=b color-moved-ws=b color-words=q? diff-algorithm=b diff-filter=e? X|dirstat=b?
119
- dirstat-by-file=q? dst-prefix=q C|find-copies=b? find-object=e M|find-renames=b?
186
+ pickaxe-all pickaxe-regex raw shortstat summary a|text abbrev=i? anchored=q B|break-rewrites=b?
187
+ color=b color-moved=b color-moved-ws=b color-words=q? diff-algorithm=b diff-filter=e? X|dirstat=q?
188
+ dirstat-by-file=q? dst-prefix=q C|find-copies=i? find-object=b M|find-renames=i?
120
189
  I|ignore-matching-lines=q ignore-submodules=b inter-hunk-context=i line-prefix=q output=p
121
190
  output-indicator-context=q output-indicator-new=q output-indicator-old=q relative=p rotate-to=p
122
191
  skip-to=p src-prefix=q stat=q? stat-width=i stat-name-width=i stat-count=i submodule=b? U|unified=i
123
- word-diff=b? word-diff-regex=q ws-error-highlight=b].freeze
192
+ word-diff=b? word-diff-regex=q ws-error-highligt=q].freeze
124
193
  }.freeze,
125
- ls_files: %w[f t v z debug deduplicate directory eol error-unmatch exclude-standard full-name i|ignored
126
- k|killed no-empty-directory recurse-submodules sparse s|stage u|unmerged abbrev=i x|exclude=q
194
+ ls_files: %w[z debug deduplicate directory eol error-unmatch exclude-standard full-name k|killed
195
+ no-empty-directory recurse-submodules sparse s|stage u|unmerged abbrev=i x|exclude=q
127
196
  X|exclude-from=p exclude-per-directory=p format=q with-tree=q].freeze,
128
- ls_remote: %w[exit-code get-url q|quiet o|server-option=e symref sort=q upload-pack=e].freeze,
129
- pull: %w[e n allow-unrelated-histories ff-only S|gpg-sign=b? log=i r|rebase=v? s|strategy=b
130
- 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,
131
203
  rebase: %w[n C=i allow-empty-message apply committer-date-is-author-date edit-todo f|force-rebase ignore-date
132
- ignore-whitespace i|interactive keep-base m|merge no-ff q|quiet quit r|rebase-merges=b?
133
- reset-author-date root show-current-patch signoff v|verbose empty=b S|gpg-sign=b? onto=e
134
- s|strategy=b X|strategy-option=b whitespace=e].freeze,
204
+ ignore-whitespace i|interactive keep-base m merge no-ff q|quiet quit r|rebase-merges=b?
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,
135
207
  reset: %w[N pathspec-file-nul q|quiet pathspec-from-file=p].freeze,
136
208
  restore: %w[ignore-unmerged ignore-skip-worktree-bits m|merge ours p|patch pathspec-file-nul S|staged theirs
137
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,
138
212
  rev_parse: {
139
213
  output: %w[absolute-git-dir all flags git-common-dir git-dir is-bare-repository is-inside-git-dir
140
- is-inside-work-tree is-shallow-repository local-env-vars no-flags no-revs not q|quiet revs-only
141
- shared-index-path show-cdup show-prefix show-toplevel show-superproject-working-tree sq sq-quote
142
- symbolic symbolic-full-name verify abbrev-ref=b? after=q before=q default=e disambiguate=b
143
- exclude=q exclude-hidden=b glob=q git-path=p path-format=b? prefix=q branches=q? remotes=q?
144
- resolve-git-dir=p short=i? show-object-format=b? since=q tags=q? until=q].freeze,
214
+ is-inside-work-tree is-shallow-repository local-env-vars no-flags no-revs not q|quiet sq
215
+ revs-only shared-index-path show-cdup show-prefix show-toplevel show-superproject-working-tree
216
+ sq-quote symbolic symbolic-full-name verify abbrev-ref=b? after=q before=q default=q
217
+ disambiguate=b exclude=q exclude-hidden=b glob=q git-path=p path-format=b? prefix=q branches=q?
218
+ remotes=q? resolve-git-dir=p short=i? show-object-format=b? since=q tags=q? until=q].freeze,
145
219
  parseopt: %w[keep-dashdash stop-at-non-option stuck-long].freeze
146
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,
147
223
  show: %w[t combined-all-paths no-diff-merges remerge-diff show-signature diff-merges=b encoding=b
148
224
  expand-tabs=i notes=q show-notes=q?].freeze,
149
225
  stash: {
@@ -153,7 +229,8 @@ module Squared
153
229
  pop: %w[index].freeze,
154
230
  apply: %w[index].freeze
155
231
  }.freeze,
156
- 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
157
234
  sort=q].freeze,
158
235
  no: {
159
236
  fetch: {
@@ -169,20 +246,26 @@ module Squared
169
246
  tag: %w[column].freeze,
170
247
  branch: %w[color-moved column color track].freeze,
171
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,
172
251
  rebase: %w[autosquash autostash fork-point gpg-sign keep-empty reapply-cherry-picks reschedule-failed-exec
173
252
  rerere-autoupdate stat update-refs verify].freeze,
174
253
  reset: %w[refresh].freeze,
175
254
  restore: %w[overlay progress recurse-submodules].freeze,
255
+ revert: %w[edit gpg-sign rerere-autoupdate].freeze,
176
256
  show: %w[standard-notes].freeze
177
257
  }.freeze
178
- }
258
+ }.freeze
179
259
  VAL_GIT = {
260
+ merge: {
261
+ send: %w[continue abort quit].freeze
262
+ }.freeze,
180
263
  rebase: {
181
264
  send: %w[continue skip abort quit].freeze,
182
265
  value: %w[true false merges interactive].freeze
183
266
  }.freeze,
184
267
  reset: %w[soft mixed hard merge keep recurse-submodules no-recurse-submodules].freeze
185
- }
268
+ }.freeze
186
269
  private_constant :OPT_GIT, :VAL_GIT
187
270
 
188
271
  class << self
@@ -214,7 +297,7 @@ module Squared
214
297
  end
215
298
 
216
299
  def tasks
217
- %i[pull rebase fetch clone stash status].freeze
300
+ %i[pull rebase fetch clone stash status branch revbuild].freeze
218
301
  end
219
302
 
220
303
  def batchargs
@@ -232,24 +315,25 @@ module Squared
232
315
  'branch' => %i[create set delete move copy list edit current].freeze,
233
316
  'checkout' => %i[commit branch track detach path].freeze,
234
317
  'commit' => %i[add all amend amend-orig].freeze,
235
- 'diff' => %i[head cached branch files view between contain].freeze,
318
+ 'diff' => %i[head branch files view between contain].freeze,
236
319
  'fetch' => %i[origin remote].freeze,
237
- 'files' => %i[cached modified deleted others].freeze,
320
+ 'files' => %i[cached modified deleted others ignored].freeze,
321
+ 'git' => %i[clean mv restore revert rm].freeze,
238
322
  'log' => %i[view between contain].freeze,
323
+ 'merge' => %i[commit no-commit send].freeze,
239
324
  'pull' => %i[origin remote].freeze,
240
325
  'rebase' => %i[branch onto send].freeze,
241
326
  'refs' => %i[heads tags remote].freeze,
242
327
  'reset' => %i[commit index patch mode].freeze,
243
- 'restore' => %i[source worktree staged overlay].freeze,
244
- 'rev' => %i[commit branch output parseopt].freeze,
328
+ 'rev' => %i[commit branch output parseopt build].freeze,
245
329
  'show' => %i[format oneline].freeze,
246
- 'stash' => %i[push pop apply drop clear list].freeze,
330
+ 'stash' => %i[push pop apply drop list].freeze,
247
331
  'tag' => %i[add delete list].freeze
248
332
  }.freeze
249
333
 
250
334
  def initialize(*, **)
251
335
  super
252
- initialize_ref(Git.ref) if gitpath.exist?
336
+ initialize_ref Git.ref if gitpath.exist?
253
337
  end
254
338
 
255
339
  def ref
@@ -294,19 +378,6 @@ module Squared
294
378
  commit(flag, refs: refs)
295
379
  end
296
380
  end
297
- when 'restore'
298
- if flag == :source
299
- format_desc action, flag, 'tree,opts*,pathspec*'
300
- task flag, [:tree] do |_, args|
301
- tree = param_guard(action, flag, args: args, key: :tree)
302
- restore(flag, args.extras, tree: tree)
303
- end
304
- else
305
- format_desc action, flag, 'opts*,pathspec+'
306
- task flag do |_, args|
307
- restore flag, args.to_a
308
- end
309
- end
310
381
  when 'tag'
311
382
  case flag
312
383
  when :list
@@ -340,9 +411,19 @@ module Squared
340
411
  case flag
341
412
  when :view, :between, :contain
342
413
  if flag == :view && action == 'log'
343
- format_desc action, flag, '(^)commit*,pathspec*,opts*'
414
+ format_desc action, flag, '(^)commit/H0*,pathspec*,opts*'
344
415
  task flag do |_, args|
345
- 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)
346
427
  end
347
428
  else
348
429
  format_desc action, flag, 'commit1,commit2,pathspec*,opts*'
@@ -352,10 +433,16 @@ module Squared
352
433
  __send__(action == 'log' ? :logx : :diff, flag, args.extras, range: [commit1, commit2])
353
434
  end
354
435
  end
355
- when :head, :cached
356
- format_desc action, flag, 'opts*,pathspec*'
436
+ when :head
437
+ format_desc action, flag, 'commit/H0*,opts*,pathspec*'
357
438
  task flag do |_, args|
358
- 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)
359
446
  end
360
447
  when :branch
361
448
  format_desc action, flag, 'name,opts*,pathspec*'
@@ -406,7 +493,7 @@ module Squared
406
493
  format_desc action, flag, 'branch/commit,opts*'
407
494
  task flag, [:commit] do |_, args|
408
495
  commit = param_guard(action, flag, args: args, key: :commit)
409
- checkout(flag, args.extras, commit: commit)
496
+ checkout(flag, commit: commit)
410
497
  end
411
498
  when :detach
412
499
  format_desc action, flag, 'branch/commit?'
@@ -500,7 +587,7 @@ module Squared
500
587
  show args.format, args.extras
501
588
  end
502
589
  end
503
- when 'rebase'
590
+ when 'rebase', 'merge'
504
591
  case flag
505
592
  when :branch
506
593
  format_desc action, flag, 'opts*,upstream?,branch?'
@@ -515,11 +602,18 @@ module Squared
515
602
  upstream = param_guard(action, flag, args: args, key: :upstream)
516
603
  rebase(flag, commit: commit, upstream: upstream, branch: args.branch)
517
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
518
611
  when :send
519
- format_desc(action, flag, VAL_GIT[:rebase][:send], arg: nil)
612
+ format_desc(action, flag, VAL_GIT[action.to_sym][:send], arg: nil)
520
613
  task flag, [:command] do |_, args|
521
- command = param_guard(action, flag, args: args, key: :command)
522
- 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)
523
617
  end
524
618
  end
525
619
  when 'rev'
@@ -540,6 +634,11 @@ module Squared
540
634
  task flag, [:ref] do |_, args|
541
635
  rev_parse(flag, ref: args.ref)
542
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
543
642
  else
544
643
  format_desc action, flag, 'opts*,args*'
545
644
  task flag do |_, args|
@@ -551,7 +650,7 @@ module Squared
551
650
  format_desc action, flag, 'remote,opts*,pattern*'
552
651
  task flag, [:remote] do |_, args|
553
652
  remote = param_guard(action, flag, args: args, key: :remote)
554
- ls_remote(flag, args.extras, remote: remote)
653
+ ls_remote(flag, args.to_a.drop(1), remote: remote)
555
654
  end
556
655
  else
557
656
  format_desc action, flag, 'opts*,pattern*'
@@ -559,6 +658,18 @@ module Squared
559
658
  __send__(action == 'refs' ? :ls_remote : :ls_files, flag, args.to_a)
560
659
  end
561
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
562
673
  end
563
674
  end
564
675
  end
@@ -571,12 +682,19 @@ module Squared
571
682
  super
572
683
  end
573
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
+
574
695
  def pull(flag = nil, opts = [], sync: invoked_sync?('pull', flag), remote: nil)
575
- cmd = git_session 'pull'
576
- if flag == :rebase
577
- cmd << '--rebase'
578
- cmd << '--autostash' if option('autostash')
579
- elsif (val = option('rebase', ignore: false))
696
+ cmd, opts = git_session('pull', flag && "--#{flag}", opts: opts)
697
+ if (val = option('rebase', ignore: false))
580
698
  cmd << case val
581
699
  when '0'
582
700
  '--no-rebase'
@@ -588,8 +706,8 @@ module Squared
588
706
  no: OPT_GIT[:no][:pull] + OPT_GIT[:no][:fetch][:pull], remote: remote, flag: flag)
589
707
  source(sync: sync, sub: if verbose
590
708
  [
591
- { pat: /^(.+)(\|\s+\d+\s+)([^-]*)(-+)(.*)$/, styles: :red, index: 4 },
592
- { 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 }
593
711
  ]
594
712
  end, **threadargs)
595
713
  end
@@ -598,7 +716,7 @@ module Squared
598
716
  command: nil)
599
717
  return pull(:rebase, sync: sync) unless flag
600
718
 
601
- cmd = git_session 'rebase'
719
+ cmd, opts = git_session('rebase', opts: opts)
602
720
  case flag
603
721
  when :branch
604
722
  branch = option_sanitize(opts, OPT_GIT[:rebase], no: OPT_GIT[:no][:rebase]).first
@@ -627,7 +745,7 @@ module Squared
627
745
  end
628
746
 
629
747
  def fetch(flag = nil, opts = [], sync: invoked_sync?('fetch', flag), remote: nil)
630
- cmd = git_session 'fetch'
748
+ cmd, opts = git_session('fetch', opts: opts)
631
749
  cmd << '--all' if !remote && !opts.include?('multiple') && option('all')
632
750
  cmd << '--verbose' if verbose && !opts.include?('quiet')
633
751
  append_pull(opts, collect_hash(OPT_GIT[:fetch]), no: collect_hash(OPT_GIT[:no][:fetch]),
@@ -659,22 +777,14 @@ module Squared
659
777
 
660
778
  def stash(flag = nil, opts = [], sync: invoked_sync?('stash', flag))
661
779
  if flag
662
- cmd = git_session 'stash', flag
663
- list = OPT_GIT[:stash][:common] + OPT_GIT[:stash].fetch(flag, [])
664
- 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
665
782
  case flag
666
783
  when :push
667
784
  append_pathspec refs
668
785
  when :pop, :apply, :drop
669
- unless refs.empty?
670
- cmd << shell_escape(refs.pop)
671
- option_clear refs
672
- end
673
- when :clear
674
- if confirm("Remove #{sub_style('all', styles: theme[:active])} the stash entries? [y/N] ", 'N')
675
- source(stdout: true)
676
- end
677
- return
786
+ cmd << shell_quote(refs.pop)
787
+ option_clear refs
678
788
  when :list
679
789
  out, banner, from = source(io: true)
680
790
  print_item banner
@@ -683,17 +793,16 @@ module Squared
683
793
  return
684
794
  end
685
795
  else
686
- git_session 'stash', 'push'
796
+ git_session('stash', 'push', opts: opts)
687
797
  append_option(%w[all keep-index include-untracked staged].freeze, no: true, ignore: false)
688
798
  append_message option('message', 'm', ignore: false)
689
799
  end
690
800
  source(banner: !quiet?, sync: sync, **threadargs)
691
801
  end
692
802
 
693
- def status(*, sync: invoked_sync?('status'), **)
803
+ def status(*)
694
804
  cmd = git_session 'status'
695
805
  cmd << (option('long') ? '--long' : '--short')
696
- cmd << '--branch' if option('branch')
697
806
  if (val = option('ignore-submodules', ignore: false))
698
807
  cmd << basic_option('ignore-submodules', case val
699
808
  when '0', 'none'
@@ -708,31 +817,85 @@ module Squared
708
817
  end
709
818
  append_pathspec
710
819
  out, banner, from = source(io: true)
711
- if sync
712
- print_item banner
713
- banner = nil
714
- end
715
820
  ret = write_lines(out, banner: banner, sub: if verbose
821
+ r = color(:red)
822
+ g = color(:green)
716
823
  [
717
- { pat: /^(.)([A-Z?!])(.+)$/, styles: :red, index: 2 },
718
- { pat: /^([A-Z?!])(.+)$/, styles: :green },
719
- { pat: /^(\?\?)(.+)$/, styles: :red },
824
+ { pat: /^(.)([A-Z?!])(.+)$/, styles: r, index: 2 },
825
+ { pat: /^([A-Z?!])(.+)$/, styles: g },
826
+ { pat: /^(\?\?)(.+)$/, styles: r },
720
827
  { pat: /^(## )(.+)(\.{3})(.+)$/,
721
- styles: [nil, :green, nil, :red], index: -1 }
828
+ styles: [nil, g, nil, r], index: -1 }
722
829
  ]
723
830
  end)
724
831
  list_result(ret, 'files', from: from, action: 'modified')
725
832
  end
726
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
+
727
888
  def reset(flag, opts = [], refs: nil, ref: nil, mode: nil, commit: nil)
728
- cmd = git_session 'reset'
889
+ cmd, opts = git_session('reset', opts: opts)
729
890
  case flag
730
891
  when :commit, :index
731
- refs = option_sanitize(opts, OPT_GIT[:reset] + VAL_GIT[:reset], no: OPT_GIT[:no][:reset]).first
892
+ out = option_sanitize(opts, OPT_GIT[:reset] + VAL_GIT[:reset], no: OPT_GIT[:no][:reset]).first
732
893
  if flag == :commit
733
- append_value commit
734
- option_clear refs
894
+ append_value(commit, delim: true)
895
+ option_clear out
735
896
  ref = false
897
+ else
898
+ (refs ||= []).concat(out)
736
899
  end
737
900
  when :mode
738
901
  return unless VAL_GIT[:reset].include?(mode)
@@ -748,21 +911,19 @@ module Squared
748
911
  return
749
912
  end
750
913
  unless ref == false
751
- append_commit ref
914
+ append_commit(ref, head: true)
752
915
  append_pathspec refs if refs
753
916
  end
754
917
  source
755
918
  end
756
919
 
757
920
  def checkout(flag, opts = [], branch: nil, origin: nil, create: nil, commit: nil, detach: nil)
758
- cmd = git_session 'checkout'
921
+ cmd, opts = git_session('checkout', opts: opts)
759
922
  append_option 'force', 'merge'
760
923
  case flag
761
924
  when :branch
762
925
  cmd << '--detach' if detach == 'd' || option('detach')
763
- if (val = option('track'))
764
- cmd << shell_option('track', val)
765
- end
926
+ append_option('track', equals: true)
766
927
  cmd << if create
767
928
  shell_option(create, branch)
768
929
  else
@@ -783,7 +944,7 @@ module Squared
783
944
  else
784
945
  out = option_sanitize(opts, OPT_GIT[:checkout], no: OPT_GIT[:no][:checkout]).first
785
946
  if flag == :commit
786
- append_value commit
947
+ append_value(commit, delim: true)
787
948
  option_clear out
788
949
  else
789
950
  append_head
@@ -794,7 +955,7 @@ module Squared
794
955
  end
795
956
 
796
957
  def tag(flag, opts = [], refs: [], message: nil, commit: nil)
797
- cmd = git_session 'tag'
958
+ cmd, opts = git_session('tag', opts: opts)
798
959
  case flag
799
960
  when :add
800
961
  if option('sign')
@@ -802,8 +963,8 @@ module Squared
802
963
  elsif !session_arg?('s', 'sign', 'u', 'local-user')
803
964
  cmd << '--annotate'
804
965
  end
805
- if !commit && message && (hash = commithash(message))
806
- commit = hash
966
+ if !commit && message && (sha = commithash(message))
967
+ commit = sha
807
968
  else
808
969
  append_message message
809
970
  end
@@ -826,41 +987,27 @@ module Squared
826
987
  source
827
988
  end
828
989
 
829
- def logx(flag, opts = [], range: [])
830
- cmd = git_session 'log'
831
- 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
832
993
  case flag
833
994
  when :between, :contain
834
995
  cmd << shell_quote(range.join(flag == :between ? '..' : '...'))
835
996
  else
836
- commit, files = files.partition do |val|
837
- val.start_with?('^') || (!%r{^.(?:[\\/]|$)}.match?(val) && !%r{[\\/]$}.match?(val)) || commithash(val)
838
- end
839
- cmd.merge(commit.map { |val| commithash(val) || shell_quote(val) }) unless commit.empty?
997
+ cmd.merge(index)
840
998
  end
841
999
  append_nocolor
842
- append_pathspec files
1000
+ append_pathspec refs
843
1001
  source(exception: false)
844
1002
  end
845
1003
 
846
- def diff(flag, opts = [], refs: [], branch: nil, range: [])
847
- cmd = git_session 'diff'
1004
+ def diff(flag, opts = [], refs: [], branch: nil, range: [], index: [])
1005
+ cmd, opts = git_session('diff', opts: opts)
848
1006
  files = option_sanitize(opts, collect_hash(OPT_GIT[:diff]) + OPT_GIT[:log][:diff],
849
1007
  no: OPT_GIT[:no][:log][:diff]).first
850
1008
  case flag
851
1009
  when :files, :view, :between, :contain
852
1010
  cmd.delete('--cached')
853
- else
854
- items = files.dup
855
- sha = nil
856
- files.clear
857
- items.each do |val|
858
- if (s = commithash(val))
859
- (sha ||= []).push(s)
860
- else
861
- files << val
862
- end
863
- end
864
1011
  end
865
1012
  append_nocolor
866
1013
  if flag == :files
@@ -875,15 +1022,14 @@ module Squared
875
1022
  cmd.delete('--merge-base')
876
1023
  cmd << shell_quote(range.join(flag == :between ? '..' : '...'))
877
1024
  else
878
- cmd << '--cached' if flag == :cached
879
1025
  cmd << '--merge-base' if option('merge-base')
880
1026
  cmd << shell_quote(branch) if branch
881
- if sha
1027
+ if !index.empty?
882
1028
  if session_arg?('cached')
883
- raise_error('diff', sha.join(', '), hint: 'one commit') if sha.size > 1
884
- cmd << sha.first
1029
+ raise_error("one commit only: #{index.join(', ')}", hint: '--cached') if index.size > 1
1030
+ cmd << index.first
885
1031
  else
886
- cmd.merge(sha)
1032
+ cmd.merge(index)
887
1033
  end
888
1034
  elsif (n = option('index'))
889
1035
  cmd << "HEAD~#{n}"
@@ -900,48 +1046,61 @@ module Squared
900
1046
  if !message && !amend
901
1047
  return if pass
902
1048
 
903
- raise_error('commit', 'GIT_MESSAGE="description"', hint: 'missing')
1049
+ raise_error('missing message', hint: 'GIT_MESSAGE="description"')
904
1050
  end
905
1051
  pathspec = if flag == :all || (amend && refs.size == 1 && refs.first == '*')
906
1052
  '--all'
907
1053
  elsif (refs = projectmap(refs)).empty?
908
- raise_error('commit', 'pathspec', hint: 'missing')
1054
+ raise_error 'no qualified pathspec'
909
1055
  else
910
1056
  "-- #{refs.join(' ')}"
911
1057
  end
912
- format = '%(if)%(HEAD)%(then)%(refname:short)...%(upstream:short)...%(upstream:track)%(end)'
913
- branch = nil
914
1058
  origin = nil
915
- source(git_output('fetch --no-tags --quiet'), io: true, banner: false, stdout: true)
916
- cmd = git_output("for-each-ref --format=\"#{format}\" refs/heads")
917
- source(cmd, io: true, banner: false).first.each do |line|
918
- next if (line = line.chomp).empty?
919
-
920
- branch, origin, hint = line.split('...')
921
- if hint && !hint.match?(/^\[(\D+0,\D+0)\]$/)
922
- raise_error('work tree is not usable', hint: hint[1..-2])
923
- elsif origin.empty?
924
- return nil if pass
925
-
926
- raise_error('no remote upstream', hint: branch)
1059
+ branch = nil
1060
+ upstream = nil
1061
+ source(git_output('fetch --no-tags --quiet'), io: true, banner: false)
1062
+ source(git_output('branch -vv --list'), io: true, banner: false).first.each do |val|
1063
+ next unless (r = /^\*\s(\S+)\s+(\h+)(?:\s\[(.+?)(?=\]\s)\])?\s/.match(val))
1064
+
1065
+ branch = r[1]
1066
+ if r[3]
1067
+ origin = r[3][%r{^(.+)/#{Regexp.escape(branch)}$}, 1]
1068
+ else
1069
+ unless (origin = option('repository', prefix: 'git', ignore: false))
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)}, (.+?)\)$/
1072
+ split_escape($1).each do |val|
1073
+ next unless val.end_with?("/#{branch}")
1074
+
1075
+ origin = val[0, val.size - branch.size - 1]
1076
+ break
1077
+ end
1078
+ end
1079
+ end
1080
+ upstream = true if origin
927
1081
  end
928
1082
  break
929
1083
  end
930
- i = origin.index('/')
931
- branch = "#{branch}:#{origin[i + 1..-1]}" unless origin.end_with?("/#{branch}")
932
- origin = origin[0..i - 1]
1084
+ raise_error 'work tree is not usable' unless origin && branch
933
1085
  cmd = git_session('commit', option('dry-run') && '--dry-run', options: false)
934
- cmd << '--amend' if amend
1086
+ if amend
1087
+ cmd << '--amend'
1088
+ else
1089
+ cmd.delete('--amend')
1090
+ end
935
1091
  if message
936
1092
  append_message message
937
1093
  elsif flag == :'amend-orig' || option('no-edit')
938
1094
  cmd << '--no-edit'
939
1095
  end
940
1096
  a = git_output 'add', '--verbose'
941
- b = git_output 'push'
942
- b << '--dry-run' if dryrun?
1097
+ b = git_output 'push', upstream && '--set-upstream'
1098
+ if dryrun?
1099
+ a << '--dry-run'
1100
+ b << '--dry-run'
1101
+ end
943
1102
  a << pathspec
944
- b << '--force-with-lease' if amend
1103
+ b << '--force' if amend
945
1104
  b << origin << branch
946
1105
  puts if pass
947
1106
  source a
@@ -949,8 +1108,24 @@ module Squared
949
1108
  source b
950
1109
  end
951
1110
 
952
- def branch(flag, opts = [], refs: [], ref: nil, target: nil)
953
- 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)
954
1129
  stdout = false
955
1130
  case flag
956
1131
  when :create
@@ -969,7 +1144,7 @@ module Squared
969
1144
  return unless ref
970
1145
 
971
1146
  if ref.start_with?('^')
972
- cmd << '--unset-upstream' << shell_escape(ref[1..-1])
1147
+ cmd << '--unset-upstream' << shell_escape(arg[1..-1])
973
1148
  target = nil
974
1149
  stdout = true
975
1150
  else
@@ -977,7 +1152,7 @@ module Squared
977
1152
  end
978
1153
  ref = nil
979
1154
  when :delete
980
- force, list = refs.partition { |val| val =~ /^[\^~]/ }
1155
+ force, list = refs.partition { |val| val.match?(/^[\^~]/) }
981
1156
  force.each do |val|
982
1157
  dr = val[0, 3]
983
1158
  d = dr.include?('^') ? '-D' : '-d'
@@ -997,10 +1172,9 @@ module Squared
997
1172
  cmd << '--edit-description'
998
1173
  when :current
999
1174
  cmd << '--show-current'
1000
- else
1001
- opts = option_sanitize(opts, OPT_GIT[:branch], no: OPT_GIT[:no][:branch]).first
1175
+ when :list
1002
1176
  grep = []
1003
- opts.each do |opt|
1177
+ option_sanitize(opts, OPT_GIT[:branch], no: OPT_GIT[:no][:branch]).first.each do |opt|
1004
1178
  if opt =~ /^(v+)$/
1005
1179
  cmd << "-#{$1}"
1006
1180
  else
@@ -1012,42 +1186,46 @@ module Squared
1012
1186
  out, banner, from = source(io: true)
1013
1187
  print_item banner
1014
1188
  ret = write_lines(out, sub: [
1015
- { pat: /^(\*\s+)(\S+)(\s*)$/, styles: :green, index: 2 },
1016
- { 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 }
1017
1191
  ])
1018
1192
  list_result(ret, 'branches', from: from)
1019
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
1020
1215
  end
1021
1216
  cmd << shell_escape(target) if target
1022
1217
  cmd << shell_escape(ref) if ref
1023
1218
  source(stdout: stdout)
1024
1219
  end
1025
1220
 
1026
- def restore(flag, opts = [], tree: nil)
1027
- cmd = git_session 'restore'
1028
- refs = option_sanitize(opts, OPT_GIT[:restore], no: OPT_GIT[:no][:restore]).first
1029
- if flag == :source
1030
- cmd << '--patch' if refs.empty?
1031
- cmd << shell_option('source', tree)
1032
- else
1033
- cmd << "--#{flag}"
1034
- end
1035
- if session_arg?('p', 'patch')
1036
- option_clear refs
1037
- else
1038
- append_pathspec(refs, expect: true)
1039
- end
1040
- source(sync: false, stderr: true)
1041
- end
1042
-
1043
1221
  def show(format, opts = [])
1044
- cmd = git_session 'show'
1222
+ cmd, opts = git_session('show', opts: opts)
1045
1223
  if format
1046
1224
  case (val = format.downcase)
1047
1225
  when 'oneline', 'short', 'medium', 'full', 'fuller', 'reference', 'email', 'raw'
1048
1226
  cmd << basic_option('format', val)
1049
1227
  else
1050
- if format =~ /^t?format:/ || format.include?('%')
1228
+ if format.match?(/^t?format:/) || format.include?('%')
1051
1229
  cmd << quote_option('pretty', format)
1052
1230
  else
1053
1231
  opts << format
@@ -1065,18 +1243,19 @@ module Squared
1065
1243
  end
1066
1244
 
1067
1245
  def rev_parse(flag, opts = [], ref: nil, size: nil)
1068
- cmd = git_session 'rev-parse', if flag == :parseopt
1069
- '--parseopt'
1070
- elsif opts.delete('sq-quote')
1071
- '--sq-quote'
1072
- 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
1073
1252
  case flag
1074
1253
  when :commit
1075
1254
  cmd << ((n = size.to_i) > 0 ? basic_option('short', [n, 5].max) : '--verify')
1076
- append_commit ref
1255
+ append_commit(ref, head: true)
1077
1256
  when :branch
1078
1257
  cmd << '--abbrev-ref'
1079
- append_commit ref
1258
+ append_commit(ref, head: true)
1080
1259
  else
1081
1260
  args = option_sanitize(opts, OPT_GIT[:rev_parse][flag]).first
1082
1261
  append_value(args, escape: session_arg?('sq-quote'))
@@ -1085,7 +1264,7 @@ module Squared
1085
1264
  end
1086
1265
 
1087
1266
  def ls_remote(flag, opts = [], remote: nil)
1088
- cmd = git_session 'ls-remote', '--refs'
1267
+ cmd, opts = git_session('ls-remote', '--refs', opts: opts)
1089
1268
  cmd << "--#{flag}" unless flag == :remote
1090
1269
  grep = option_sanitize(opts, OPT_GIT[:ls_remote]).first
1091
1270
  cmd << shell_quote(remote) if remote
@@ -1096,7 +1275,7 @@ module Squared
1096
1275
  end
1097
1276
 
1098
1277
  def ls_files(flag, opts = [])
1099
- git_session 'ls-files', "--#{flag}"
1278
+ opts = git_session('ls-files', "--#{flag}", opts: opts).last
1100
1279
  grep = option_sanitize(opts, OPT_GIT[:ls_files]).first
1101
1280
  out, banner, from = source(io: true)
1102
1281
  print_item banner
@@ -1104,10 +1283,51 @@ module Squared
1104
1283
  list_result(ret, 'files', from: from, grep: grep)
1105
1284
  end
1106
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
+
1107
1323
  def clone?
1108
1324
  ref?(workspace.baseref) && workspace.git_clone?(path, name) ? 1 : false
1109
1325
  end
1110
1326
 
1327
+ def revbuild?
1328
+ build? && !!workspace.revfile
1329
+ end
1330
+
1111
1331
  def enabled?(*, **kwargs)
1112
1332
  super || (kwargs[:base] == false && !!clone?)
1113
1333
  end
@@ -1120,7 +1340,7 @@ module Squared
1120
1340
  if cmd.respond_to?(:done)
1121
1341
  if io && banner == false
1122
1342
  from = nil
1123
- 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,}$/) })
1124
1344
  from = :"git:#{from}"
1125
1345
  end
1126
1346
  banner &&= cmd.temp { |val| val.start_with?('--work-tree') || val.start_with?('--git-dir') }
@@ -1166,13 +1386,13 @@ module Squared
1166
1386
  ret = on(:error, from, e)
1167
1387
  raise if exception && ret != true
1168
1388
 
1169
- warn log_message(Logger::WARN, e) if warning?
1389
+ warn log_message(Logger::WARN, e, pass: true) if warning?
1170
1390
  else
1171
1391
  on :last, from
1172
1392
  end
1173
1393
  end
1174
1394
 
1175
- 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)
1176
1396
  grep = as_a(grep).map do |val|
1177
1397
  next val if val.is_a?(Regexp)
1178
1398
 
@@ -1185,6 +1405,7 @@ module Squared
1185
1405
  data.each do |line|
1186
1406
  next if grep&.none? { |pat| pat.match?(line) }
1187
1407
 
1408
+ line = yield line if block_given?
1188
1409
  if loglevel
1189
1410
  log&.add loglevel, line
1190
1411
  else
@@ -1196,8 +1417,9 @@ module Squared
1196
1417
  end
1197
1418
  end
1198
1419
  ret += 1
1420
+ break if first
1199
1421
  end
1200
- print_item banner, out if banner && (ret > 0 || !pass)
1422
+ print_item banner, out if banner && (ret > 0 || (!pass && !first))
1201
1423
  ret
1202
1424
  end
1203
1425
 
@@ -1206,7 +1428,7 @@ module Squared
1206
1428
  if size > 0
1207
1429
  styles = theme.fetch(:banner, []).reject { |s| s.to_s.end_with?('!') }
1208
1430
  styles << :bold if styles.size <= 1
1209
- puts print_footer("#{size} #{size == 1 ? type.sub(/(?:(?<!l)e)?s\z/, '') : type}",
1431
+ puts print_footer("#{size} #{size == 1 ? type.sub(/e?s\z/, '') : type}",
1210
1432
  sub: { pat: /\A(\d+)(.+)\z/, styles: styles })
1211
1433
  else
1212
1434
  puts empty_status("No #{type} were #{action}", 'grep', grep.is_a?(Array) ? case grep.size
@@ -1220,12 +1442,27 @@ module Squared
1220
1442
  on :last, from
1221
1443
  end
1222
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
+
1223
1460
  def append_pull(opts, list, target: @session, no: nil, flag: nil, remote: nil)
1224
- target << '--force' if option('force', target: target)
1461
+ cmd << '--force' if option('force')
1225
1462
  rsm = append_submodules(target: target)
1226
1463
  out = []
1227
1464
  refspec = []
1228
- opts, pat = option_sanitize(opts, remote ? list + ['refspec=v'] : list, target: target, no: no)
1465
+ opts, pat = option_sanitize(opts, remote ? list + ['refspec=b'] : list, target: target, no: no)
1229
1466
  opts.each do |opt|
1230
1467
  if opt =~ pat
1231
1468
  case $1
@@ -1246,7 +1483,7 @@ module Squared
1246
1483
  end
1247
1484
  if remote
1248
1485
  append_value(remote, target: target, delim: true)
1249
- if (val = option('refspec', target: target, strict: true))
1486
+ if (val = option('refspec', strict: true))
1250
1487
  append_value(split_escape(val), target: target)
1251
1488
  else
1252
1489
  target.merge(refspec)
@@ -1259,23 +1496,27 @@ module Squared
1259
1496
  option_clear(out, target: target, subject: flag.to_s) if flag
1260
1497
  end
1261
1498
 
1262
- def append_commit(val, target: @session)
1263
- val = val.to_s.strip
1264
- 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
1265
1506
  end
1266
1507
 
1267
1508
  def append_pathspec(files = [], target: @session, expect: false, parent: false)
1268
- if session_arg?('pathspec-from-file', target: target)
1509
+ if session_arg?('pathspec-from-file')
1269
1510
  option_clear files
1270
1511
  else
1271
- if files.empty? && (val = option('pathspec', target: target))
1512
+ if files.empty? && (val = option('pathspec'))
1272
1513
  files = split_escape(val)
1273
1514
  end
1274
1515
  files = projectmap(files, parent: parent)
1275
1516
  if !files.empty?
1276
- target << "-- #{files.join(' ')}"
1517
+ target << '--' << files.join(' ')
1277
1518
  elsif expect
1278
- raise_error(parent ? 'pathspec not present' : 'pathspec not within worktree', hint: 'invalid')
1519
+ raise_error(parent ? 'pathspec not present' : 'pathspec not within worktree')
1279
1520
  end
1280
1521
  end
1281
1522
  end
@@ -1291,34 +1532,32 @@ module Squared
1291
1532
  end
1292
1533
 
1293
1534
  def append_submodules(from = nil, target: @session)
1294
- return unless (val = option('recurse-submodules', target: target, ignore: false))
1535
+ return unless (val = option('recurse-submodules', ignore: false))
1295
1536
 
1296
1537
  if from == :clone
1297
- case val
1298
- when '0', 'false'
1299
- target << '--no-recurse-submodules'
1300
- when '1', 'true'
1301
- target << '--recurse-submodules'
1302
- else
1303
- projectmap(split_escape(val)).each do |path|
1304
- target << basic_option('recurse-submodules', path)
1305
- end
1538
+ projectmap(split_escape(val)).each do |path|
1539
+ target << basic_option('recurse-submodules', path)
1306
1540
  end
1541
+ target
1307
1542
  else
1308
1543
  target << case val
1309
1544
  when 'no', '0'
1310
1545
  '--no-recurse-submodules'
1311
1546
  when 'yes', 'on-demand'
1312
- "--recurse-submodules=#{val}"
1547
+ "--recurse-submodules#{from == :reset ? '' : "=#{val}"}"
1313
1548
  else
1314
1549
  '--recurse-submodules'
1315
1550
  end
1316
1551
  end
1317
1552
  end
1318
1553
 
1319
- def git_session(*cmd, worktree: true, **kwargs)
1554
+ def git_session(*cmd, opts: nil, worktree: true, **kwargs)
1320
1555
  dir = worktree ? ["--work-tree=#{shell_quote(path)}", "--git-dir=#{shell_quote(gitpath)}"] : []
1321
- 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]
1322
1561
  end
1323
1562
 
1324
1563
  def git_output(*cmd, **kwargs)
@@ -1326,9 +1565,7 @@ module Squared
1326
1565
  end
1327
1566
 
1328
1567
  def dryrun?(*, target: @session, **)
1329
- return false unless target
1330
-
1331
- target.include?('--dry-run')
1568
+ !!target&.include?('--dry-run')
1332
1569
  end
1333
1570
 
1334
1571
  def quiet?(target: @session)
@@ -1338,11 +1575,11 @@ module Squared
1338
1575
  end
1339
1576
 
1340
1577
  def gitpath
1341
- basepath('.git')
1578
+ basepath '.git'
1342
1579
  end
1343
1580
 
1344
1581
  def commithash(val)
1345
- val =~ /\A#\{(\h{5,40})\}\z/ ? $1 : nil
1582
+ val[/^#\{(\h{5,40})\}$/, 1]
1346
1583
  end
1347
1584
 
1348
1585
  def threadargs