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