squared 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,11 +2,93 @@
2
2
 
3
3
  module Squared
4
4
  module Workspace
5
+ module Git
6
+ GIT_REPO = {}
7
+ GIT_PROTO = %r{^(?:https?|ssh|git|file)://}i.freeze
8
+ private_constant :GIT_REPO, :GIT_PROTO
9
+
10
+ def git(name, uri = nil, base: nil, repo: [], options: {})
11
+ data = {}
12
+ check = ->(proj) { proj.is_a?(Project::Git) && git_clone?(proj.path) }
13
+ if uri.is_a?(Array)
14
+ base = name
15
+ uri.each { |val| repo << proj if (proj = @project[val.to_s]) && check.(proj) }
16
+ elsif uri
17
+ data[name.to_s] = uri
18
+ elsif name.is_a?(Enumerable)
19
+ data = name.to_h
20
+ elsif name.is_a?(String) && name =~ GIT_PROTO
21
+ base = name
22
+ @project.each_value { |proj| repo << proj if !proj.parent && check.(proj) }
23
+ else
24
+ warn log_message(Logger::WARN, name, subject: 'git', hint: 'invalid') if warning
25
+ return self
26
+ end
27
+ if base
28
+ base = base =~ GIT_PROTO ? "#{base.chomp('/')}/" : @root.join(base)
29
+ repo.each do |target|
30
+ if target.is_a?(Project::Git)
31
+ data[target.localname] = target.project
32
+ else
33
+ data[target.to_s] = nil
34
+ end
35
+ end
36
+ end
37
+ data.each do |key, val|
38
+ if val.is_a?(Hash)
39
+ uri = val.fetch(:uri, '')
40
+ opts = val.fetch(:options, {})
41
+ else
42
+ uri = val.is_a?(String) ? val : key.to_s
43
+ opts = options
44
+ end
45
+ unless uri.match?(GIT_PROTO) || Pathname.new(uri).absolute?
46
+ if uri.start_with?('.')
47
+ uri = @root.join(uri)
48
+ elsif base
49
+ uri = base.is_a?(Pathname) ? base.join(uri) : base + uri
50
+ else
51
+ next
52
+ end
53
+ end
54
+ key = task_name(key)
55
+ (GIT_REPO[main] ||= {})[key] = [uri.to_s, opts]
56
+ (@kind[key] ||= []) << Project::Git
57
+ end
58
+ self
59
+ end
60
+
61
+ def git_repo(name)
62
+ (ret = GIT_REPO[main]) && ret[name]
63
+ end
64
+
65
+ def git_clone?(path, name = nil)
66
+ return false if name && !git_repo(name)
67
+
68
+ !path.exist? || path.empty?
69
+ end
70
+ end
71
+ Application.include Git
72
+
5
73
  module Project
6
74
  class Git < Base
7
- OPT_FETCH = %w[all tags prune append atomic unshallow dry-run depth=i deepen=i since=d jobs=i].freeze
8
- OPT_PULL = %w[ff ff-only edit autostash squash verify <fetch].freeze
9
- private_constant :OPT_PULL, :OPT_FETCH
75
+ FOR_FETCH = %w[atomic multiple negotiate-only prefetch progress prune-tags refetch recurse-submodules=s].freeze
76
+ NO_FETCH = %w[all ipv4 ipv6 recurse-submodules show-forced-updates tags].freeze
77
+ FNO_FETCH = %w[auto-gc auto-maintenance write-commit-graph write-fetch-head].freeze
78
+ OPT_FETCH = (FOR_FETCH + NO_FETCH + FNO_FETCH +
79
+ %w[append dry-run force keep prune quiet set-upstream unshallow update-shallow verbose
80
+ deepen=i depth=i jobs=i negotiation-tip=s refmap=s server-option=s shallow-exclude=s
81
+ shallow-since=d upload-pack=s]).freeze
82
+ NO_PULL = %w[allow-unrelated-histories autostash commit edit ff signoff squash stat verify verify-signatures
83
+ gpg-sign=? log=? rebase=?].freeze
84
+ OPT_PULL = (NO_PULL + %w[ff-only signoff=s strategy=s strategy-option=s]).freeze
85
+ OPT_REBASE = %w[continue skip abort quit].freeze
86
+ VAL_REBASE = %w[true false merges interactive].freeze
87
+ NO_BRANCH = %w[abbrev=i color=? column=? track=?].freeze
88
+ OPT_BRANCH = (NO_BRANCH + %w[all create-reflog ignore-case quiet remotes verbose
89
+ contains=s format=s merged=s no-contains=s no-merged=s points-at=s sort=s]).freeze
90
+ private_constant :FOR_FETCH, :NO_FETCH, :FNO_FETCH, :OPT_FETCH, :NO_PULL, :OPT_PULL, :OPT_REBASE, :VAL_REBASE,
91
+ :NO_BRANCH, :OPT_BRANCH
10
92
 
11
93
  class << self
12
94
  include Rake::DSL
@@ -17,17 +99,17 @@ module Squared
17
99
  namespace(name = ws.task_name('git')) do
18
100
  all = ws.task_join(name, 'all')
19
101
 
20
- desc Common::Format.message(*"#{all}[git?=rebase|stash,depend?]".split(':'))
21
- task 'all', [:git, :depend] do |_, args|
22
- cmd = case args.git
23
- when 'rebase'
24
- [ws.task_sync('rebase')]
25
- when 'stash'
102
+ ws.format_desc(all, %w[stash|rebase depend])
103
+ task 'all' do |_, args|
104
+ opts = args.to_a
105
+ cmd = if opts.include?('stash')
26
106
  [ws.task_sync('stash'), ws.task_sync('pull')]
107
+ elsif opts.include?('rebase')
108
+ [ws.task_sync('rebase')]
27
109
  else
28
110
  [ws.task_sync('pull')]
29
111
  end
30
- cmd << ws.task_sync('depend') if args.depend && !ws.series[:depend].empty?
112
+ cmd << ws.task_sync('depend') if opts.include?('depend') && !ws.series[:depend].empty?
31
113
  cmd << ws.task_sync('build')
32
114
  Common::Utils.task_invoke(*cmd, **ws.invokeargs)
33
115
  end
@@ -37,11 +119,11 @@ module Squared
37
119
  end
38
120
 
39
121
  def tasks
40
- %i[pull rebase fetch stash status].freeze
122
+ %i[pull rebase fetch clone stash status].freeze
41
123
  end
42
124
 
43
125
  def batchargs
44
- [ref, { 'pull-s': %i[stash pull], 'rebase-s': %i[stash rebase] }]
126
+ [ref, { 'pull+s': %i[stash pull], 'rebase+s': %i[stash rebase] }]
45
127
  end
46
128
 
47
129
  def config?(val)
@@ -52,19 +134,21 @@ module Squared
52
134
  end
53
135
 
54
136
  @@tasks[ref] = {
55
- checkout: %i[branch detach force merge].freeze,
56
- commit: %i[add amend amend-orig all no-all].freeze,
57
- diff: %i[head cached branch files].freeze,
58
- fetch: %i[all submodules unshallow].freeze,
59
- files: %i[cached modified deleted others].freeze,
60
- pull: %i[head rebase no-rebase commit no-commit submodules].freeze,
61
- stash: %i[push pop apply list clear].freeze,
62
- refs: %i[heads tags].freeze,
63
- reset: %i[head soft mixed hard merge keep submodules].freeze,
64
- restore: %i[worktree staged overlay].freeze,
65
- rev: %i[commit branch].freeze,
66
- show: %i[oneline pretty format].freeze,
67
- tag: %i[add delete list merged].freeze
137
+ 'checkout' => %i[branch track detach force merge].freeze,
138
+ 'commit' => %i[add amend amend-orig all no-all].freeze,
139
+ 'diff' => %i[head cached branch files between contain].freeze,
140
+ 'pull' => %i[origin remote].freeze,
141
+ 'fetch' => %i[origin remote].freeze,
142
+ 'stash' => %i[push pop apply list clear].freeze,
143
+ 'rebase' => %i[onto root send].freeze,
144
+ 'branch' => %i[create set delete move copy list edit current].freeze,
145
+ 'refs' => %i[heads tags].freeze,
146
+ 'files' => %i[cached modified deleted others].freeze,
147
+ 'reset' => %i[head patch mode].freeze,
148
+ 'restore' => %i[source worktree staged overlay].freeze,
149
+ 'rev' => %i[commit branch].freeze,
150
+ 'show' => %i[format oneline].freeze,
151
+ 'tag' => %i[add delete list merged].freeze
68
152
  }.freeze
69
153
 
70
154
  def initialize(*, **)
@@ -76,114 +160,131 @@ module Squared
76
160
  Git.ref
77
161
  end
78
162
 
79
- def populate(*)
163
+ def populate(*, **)
80
164
  super
81
165
  return unless ref?(Git.ref)
82
166
 
83
167
  namespace name do
84
168
  @@tasks[Git.ref].each do |action, flags|
169
+ next if @pass.include?(action)
170
+
85
171
  namespace action do
86
172
  flags.each do |flag|
87
173
  case action
88
- when :pull, :fetch
89
- desc format_desc(action, flag, action == :pull ? OPT_PULL : OPT_FETCH)
90
- task flag, [:opts] do |_, args|
91
- __send__(action, flag, opts: args.to_a)
174
+ when 'pull', 'fetch'
175
+ if flag == :remote
176
+ if @@task_desc
177
+ format_desc(action, flag, ['refspec=s'] + (action == 'pull' ? OPT_PULL : OPT_FETCH),
178
+ before: 'remote')
179
+ end
180
+ task flag, [:remote] do |_, args|
181
+ remote = param_guard(action, flag, args: args, key: :remote)
182
+ __send__(action, flag, remote: remote, opts: args.extras)
183
+ end
184
+ else
185
+ format_desc(action, flag, action == 'pull' ? OPT_PULL : OPT_FETCH)
186
+ task flag do |_, args|
187
+ __send__(action, flag, opts: args.to_a)
188
+ end
92
189
  end
93
- when :commit, :restore
94
- if flag == :all
95
- desc format_desc(action, flag, 'message?')
190
+ when 'commit', 'restore'
191
+ case flag
192
+ when :all
193
+ format_desc action, flag, 'message?'
96
194
  task flag, [:message] do |_, args|
97
195
  commit(flag, message: args.fetch(:message, nil))
98
196
  end
197
+ when :source
198
+ format_desc action, flag, 'tree,pathspec*'
199
+ task flag, [:tree] do |_, args|
200
+ tree = param_guard(action, flag, args: args, key: :tree)
201
+ restore(flag, args.extras, tree: tree)
202
+ end
99
203
  else
100
- desc format_desc(action, flag, 'pathspec+')
101
- task flag, [:pathspec] do |_, args|
102
- files = guard_params(action, flag, args: args.to_a)
103
- __send__(action, flag, files)
204
+ format_desc action, flag, 'pathspec+'
205
+ task flag do |_, args|
206
+ files = param_guard(action, flag, args: args.to_a)
207
+ __send__ action, flag, files
104
208
  end
105
209
  end
106
- when :tag
210
+ when 'tag'
107
211
  case flag
108
212
  when :list
109
- desc format_desc(action, flag, 'pattern*')
110
- task flag, [:pattern] do |_, args|
111
- tag(flag, args.to_a)
213
+ format_desc action, flag, 'pattern*'
214
+ task flag do |_, args|
215
+ tag flag, args.to_a
112
216
  end
113
217
  when :merged
114
- desc format_desc(action, flag, 'commit,pattern*')
115
- task flag, [:commit, :pattern] do |_, args|
116
- commit = guard_params(action, flag, args: args, key: :commit)
117
- tag(flag, args.to_a.drop(1), commit: commit)
218
+ format_desc action, flag, 'commit,pattern*'
219
+ task flag, [:commit] do |_, args|
220
+ commit = param_guard(action, flag, args: args, key: :commit)
221
+ tag(flag, args.extras, commit: commit)
118
222
  end
119
223
  when :delete
120
- desc format_desc(action, flag, 'name+')
121
- task flag, [:name] do |_, args|
122
- name = guard_params(action, flag, args: args.to_a)
123
- tag(flag, name)
224
+ format_desc action, flag, 'name+'
225
+ task flag do |_, args|
226
+ name = param_guard(action, flag, args: args.to_a)
227
+ tag flag, name
124
228
  end
125
- else
126
- desc format_desc(action, flag, 'name,message?,commit?')
229
+ when :add
230
+ format_desc action, flag, 'name,message?,commit?'
127
231
  task flag, [:name, :message, :commit] do |_, args|
128
- name = guard_params(action, flag, args: args, key: :name)
232
+ name = param_guard(action, flag, args: args, key: :name)
129
233
  tag(flag, [name], message: args.message, commit: args.commit)
130
234
  end
131
235
  end
132
- when :stash
236
+ when 'stash'
133
237
  if flag == :push
134
- desc format_desc(action, flag, 'pathspec*')
135
- task flag, [:pathspec] do |_, args|
136
- stash(flag, args.to_a)
238
+ format_desc action, flag, 'pathspec*'
239
+ task flag do |_, args|
240
+ stash flag, args.to_a
137
241
  end
138
242
  else
139
- desc format_desc(action, flag, 'commit?')
243
+ format_desc action, flag, 'commit?'
140
244
  task flag, [:commit] do |_, args|
141
245
  stash(flag, commit: args.commit)
142
246
  end
143
247
  end
144
- when :rev
145
- desc format_desc(action, flag, "ref?=HEAD#{flag == :commit ? ',size?' : ''}")
146
- task flag, [:ref, :size] do |_, args|
147
- rev(flag, ref: args.ref, size: args.size)
148
- end
149
- when :refs, :files
150
- desc format_desc(action, flag, 'grep?=pattern')
151
- task flag, [:grep] do |_, args|
152
- __send__(action, flag, grep: args.fetch(:grep, nil))
153
- end
154
- when :diff
248
+ when 'diff'
155
249
  case flag
156
250
  when :head
157
- desc format_desc(action, flag, 'index?=0,pathspec*')
158
- task flag, [:pathspec] do |_, args|
251
+ format_desc action, flag, 'index?=0,pathspec*'
252
+ task flag do |_, args|
159
253
  files = args.to_a
160
254
  diff(flag, files, index: /\A\d+\z/.match?(files[0]) && !option('index') ? files.shift.to_i : 0)
161
255
  end
162
256
  when :cached
163
- desc format_desc(action, flag, 'pathspec*')
164
- task flag, [:pathspec] do |_, args|
165
- diff(flag, args.to_a)
257
+ format_desc action, flag, 'pathspec*'
258
+ task flag do |_, args|
259
+ diff flag, args.to_a
166
260
  end
167
261
  when :branch
168
- desc format_desc(action, flag, 'name,pathspec*')
262
+ format_desc action, flag, 'name,pathspec*'
169
263
  task flag, [:name] do |_, args|
170
- branch = guard_params(action, flag, args: args, key: :name)
171
- diff(flag, args.to_a.drop(1), branch: branch)
264
+ branch = param_guard(action, flag, args: args, key: :name)
265
+ diff(flag, args.extras, branch: branch)
172
266
  end
173
267
  when :files
174
- desc format_desc(action, flag, 'path1,path2')
268
+ format_desc action, flag, 'path1,path2'
175
269
  task flag, [:path1, :path2] do |_, args|
176
- path1 = guard_params(action, flag, args: args, key: :path1)
177
- path2 = guard_params(action, flag, args: args, key: :path2)
178
- diff(flag, [path1, path2])
270
+ path1 = param_guard(action, flag, args: args, key: :path1)
271
+ path2 = param_guard(action, flag, args: args, key: :path2)
272
+ diff flag, [path1, path2]
273
+ end
274
+ else
275
+ format_desc action, flag, 'commit1,commit2,pathspec*'
276
+ task flag, [:commit1, :commit2] do |_, args|
277
+ commit1 = param_guard(action, flag, args: args, key: :commit1)
278
+ commit2 = param_guard(action, flag, args: args, key: :commit2)
279
+ diff(flag, args.extras, range: [commit1, commit2])
179
280
  end
180
281
  end
181
- when :checkout
282
+ when 'checkout'
182
283
  case flag
183
284
  when :branch
184
- desc format_desc(action, flag, 'name,create?=b|B,commit?,detach?=d')
285
+ format_desc action, flag, 'name,create?=[bB],commit?,detach?=d'
185
286
  task flag, [:name, :create, :commit, :detach] do |_, args|
186
- branch = guard_params(action, flag, args: args, key: :name)
287
+ branch = param_guard(action, flag, args: args, key: :name)
187
288
  create = args.create
188
289
  if args.commit == 'd'
189
290
  detach = 'd'
@@ -200,45 +301,131 @@ module Squared
200
301
  detach = args.detach
201
302
  commit = args.commit
202
303
  end
203
- guard_params(action, flag, args: { create: create }, key: :create, pat: /\Ab\z/i) if create
304
+ param_guard(action, flag, args: { create: create }, key: :create, pat: /\Ab\z/i) if create
204
305
  checkout(flag, branch: branch, create: create, commit: commit, detach: detach)
205
306
  end
307
+ when :track
308
+ format_desc action, flag, 'origin,(^)name?'
309
+ task flag, [:origin, :name] do |_, args|
310
+ origin = param_guard(action, flag, args: args, key: :origin)
311
+ checkout(flag, branch: args.name, origin: origin)
312
+ end
206
313
  when :detach
207
- desc format_desc(action, flag, 'branch/commit?')
314
+ format_desc action, flag, 'branch/commit?'
208
315
  task flag, [:commit] do |_, args|
209
316
  checkout(flag, commit: args.commit)
210
317
  end
211
318
  else
212
- desc format_desc(action, flag, 'pathspec*')
213
- task flag, [:pathspec] do |_, args|
214
- checkout(flag, args.to_a)
319
+ format_desc action, flag, 'pathspec*'
320
+ task flag do |_, args|
321
+ checkout flag, args.to_a
215
322
  end
216
323
  end
217
- when :reset
218
- if flag == :head
219
- desc format_desc(action, flag, 'ref,pathspec+')
220
- task flag, [:ref, :pathspec] do |_, args|
221
- files = guard_params(action, flag, args: args.to_a.drop(1))
222
- reset(flag, files, ref: args.ref)
324
+ when 'branch'
325
+ case flag
326
+ when :create
327
+ format_desc action, flag, 'name,ref?=HEAD'
328
+ task flag, [:name, :ref] do |_, args|
329
+ name = param_guard(action, flag, args: args, key: :name)
330
+ branch(flag, target: name, ref: args.ref)
331
+ end
332
+ when :set
333
+ format_desc(action, flag, '(!)upstream,name?')
334
+ task flag, [:upstream, :name] do |_, args|
335
+ upstream = param_guard(action, flag, args: args, key: :upstream)
336
+ branch(flag, target: args.name, ref: upstream)
337
+ end
338
+ when :delete
339
+ format_desc action, flag, '(^~)name+'
340
+ task flag, [:name] do |_, args|
341
+ name = param_guard(action, flag, args: args.to_a)
342
+ branch flag, name
343
+ end
344
+ when :edit
345
+ format_desc action, flag, 'name?'
346
+ task flag, [:name] do |_, args|
347
+ branch(flag, target: args.name)
348
+ end
349
+ when :list
350
+ format_desc(action, flag, OPT_BRANCH, before: 'pattern*')
351
+ task flag do |_, args|
352
+ branch(flag, opts: args.to_a)
353
+ end
354
+ when :current
355
+ format_desc action, flag
356
+ task flag do
357
+ branch flag
223
358
  end
224
359
  else
225
- desc format_desc(action, flag, 'ref?=HEAD')
226
- task flag, [:ref] do |_, args|
227
- reset(flag, ref: args.ref)
360
+ format_desc action, flag, 'newbranch,oldbranch?'
361
+ task flag, [:newbranch, :oldbranch] do |_, args|
362
+ newbranch = param_guard(action, flag, args: args, key: :newbranch)
363
+ branch flag, [args.oldbranch, newbranch]
228
364
  end
229
365
  end
230
- when :show
231
- if flag == :oneline
232
- desc format_desc(action, flag, 'object*')
233
- task flag, [:object] do |_, args|
234
- show(args.to_a, pretty: 'oneline', abbrev: true)
366
+ when 'reset'
367
+ case flag
368
+ when :mode
369
+ if @@task_desc
370
+ format_desc(action, flag, %w[soft mixed hard merge keep {no-}submodules], after: 'ref?=HEAD',
371
+ arg: false)
372
+ end
373
+ task flag, [:mode, :ref] do |_, args|
374
+ mode = param_guard(action, flag, args: args, key: :mode)
375
+ reset(flag, mode: mode, ref: args.ref)
235
376
  end
236
377
  else
237
- desc format_desc(action, flag, 'format,object*')
238
- task flag, [:format, :object] do |_, args|
239
- show(args.to_a.drop(1), "#{flag}": args.format)
378
+ format_desc action, flag, 'ref,pathspec+'
379
+ task flag, [:ref] do |_, args|
380
+ files = param_guard(action, flag, args: args.extras)
381
+ reset(flag, files, ref: args.ref)
240
382
  end
241
383
  end
384
+ when 'show'
385
+ case flag
386
+ when :oneline
387
+ format_desc action, flag, 'object*'
388
+ task flag do |_, args|
389
+ show(args.to_a, format: 'oneline', opts: { 'abbrev-commit': true })
390
+ end
391
+ when :format
392
+ format_desc action, flag, 'format?,object*'
393
+ task flag, [:format] do |_, args|
394
+ show(args.extras, format: args.format)
395
+ end
396
+ end
397
+ when 'rebase'
398
+ case flag
399
+ when :onto
400
+ format_desc action, flag, 'commit,upstream,branch?=HEAD'
401
+ task flag, [:commit, :upstream, :branch] do |_, args|
402
+ commit = param_guard(action, flag, args: args, key: :commit)
403
+ upstream = param_guard(action, flag, args: args, key: :upstream)
404
+ rebase(flag, commit: commit, upstream: upstream, branch: args.branch)
405
+ end
406
+ when :root
407
+ format_desc action, flag, 'branch,onto?'
408
+ task flag, [:branch, :onto] do |_, args|
409
+ branch = param_guard(action, flag, args: args, key: :branch)
410
+ rebase(flag, commit: args.onto, branch: branch)
411
+ end
412
+ when :send
413
+ format_desc(action, flag, OPT_REBASE, arg: nil)
414
+ task flag, [:command] do |_, args|
415
+ command = param_guard(action, flag, args: args, key: :command)
416
+ rebase(flag, command: command)
417
+ end
418
+ end
419
+ when 'rev'
420
+ format_desc(action, flag, "ref?=HEAD#{flag == :commit ? ',size?' : ''}")
421
+ task flag, [:ref, :size] do |_, args|
422
+ rev_parse(flag, ref: args.ref, size: args.size)
423
+ end
424
+ when 'refs', 'files'
425
+ format_desc action, flag, 'grep?=pattern'
426
+ task flag, [:grep] do |_, args|
427
+ __send__(action == 'refs' ? :ls_remote : :ls_files, flag, grep: args.fetch(:grep, nil))
428
+ end
242
429
  end
243
430
  end
244
431
  end
@@ -246,40 +433,97 @@ module Squared
246
433
  end
247
434
  end
248
435
 
249
- def pull(flag = nil, sync: invoked_sync?('pull', flag), opts: [])
436
+ def generate(keys = [], **)
437
+ keys << :clone if clone?
438
+ super
439
+ end
440
+
441
+ def pull(flag = nil, sync: invoked_sync?('pull', flag), remote: nil, opts: [])
250
442
  cmd = git_session 'pull'
251
- if flag == :'no-rebase' || option('rebase', equals: '0')
252
- cmd << '--no-rebase'
253
- elsif flag == :rebase || option('rebase')
254
- cmd << '--rebase'
255
- end
256
- if flag == :'no-commit' || option('commit', equals: '0')
257
- cmd << '--no-commit'
258
- elsif flag == :commit || option('commit')
259
- cmd << '--commit'
260
- end
261
- append_pull opts, OPT_PULL, flag
262
- sub = if verbose
263
- [
264
- { pat: /^(.+)(\|\s+\d+\s+)([^-]*)(-+)(.*)$/, styles: :red, index: 4 },
265
- { pat: /^(.+)(\|\s+\d+\s+)(\++)(-*)(.*)$/, styles: :green, index: 3 }
266
- ]
267
- end
268
- source(sync: sync, sub: sub, **threadargs)
443
+ if (val = option('rebase', ignore: false))
444
+ cmd << case val
445
+ when '0'
446
+ '--no-rebase'
447
+ else
448
+ VAL_REBASE.include?(val) ? basic_option('rebase', val) : '--rebase'
449
+ end
450
+ end
451
+ append_pull(opts, OPT_PULL + OPT_FETCH - (FOR_FETCH + FNO_FETCH), NO_PULL + NO_FETCH, remote: remote,
452
+ flag: flag)
453
+ source(sync: sync, sub: if verbose
454
+ [
455
+ { pat: /^(.+)(\|\s+\d+\s+)([^-]*)(-+)(.*)$/, styles: :red, index: 4 },
456
+ { pat: /^(.+)(\|\s+\d+\s+)(\++)(-*)(.*)$/, styles: :green, index: 3 }
457
+ ]
458
+ end, **threadargs)
269
459
  end
270
460
 
271
- def rebase
272
- pull(:rebase, sync: invoked_sync?('rebase'))
461
+ def rebase(flag = nil, sync: invoked_sync?('rebase', flag), command: nil, commit: nil, upstream: nil,
462
+ branch: nil)
463
+ return pull(:rebase, sync: sync) unless flag
464
+
465
+ cmd = git_session 'rebase'
466
+ cmd << '--interactive' unless flag == :send || !option('interactive', 'i')
467
+ case flag
468
+ when :onto
469
+ return unless upstream
470
+
471
+ cmd << shell_option('onto', commit)
472
+ cmd << shell_escape(upstream)
473
+ append_head branch
474
+ when :root
475
+ return unless branch
476
+
477
+ cmd << shell_option('onto', commit) if commit
478
+ cmd << '--root' << shell_escape(branch)
479
+ when :send
480
+ return unless OPT_REBASE.include?(command)
481
+
482
+ cmd << "--#{command}"
483
+ else
484
+ return
485
+ end
486
+ source
273
487
  end
274
488
 
275
- def fetch(flag = nil, opts: [])
489
+ def fetch(flag = nil, sync: invoked_sync?('fetch', flag), remote: nil, opts: [])
276
490
  cmd = git_session 'fetch'
277
- cmd << '--all' if flag == :all || option('all')
278
- append_pull opts, OPT_FETCH, flag
279
- source(sync: invoked_sync?('fetch', flag), **threadargs)
491
+ cmd << '--all' if !remote && !opts.include?('multiple') && option('all')
492
+ cmd << '--verbose' if verbose && !opts.include?('quiet')
493
+ append_pull(opts, OPT_FETCH, NO_FETCH + FNO_FETCH, remote: remote, flag: flag)
494
+ source(sync: sync, **threadargs)
495
+ end
496
+
497
+ def clone(*, sync: invoked_sync?('clone'), **)
498
+ return unless clone? && (data = workspace.git_repo(name))
499
+
500
+ cmd = git_session('clone', worktree: false)
501
+ opts = data[1].dup
502
+ if (val = option('depth', ignore: false))
503
+ if (n = val.to_i) > 0
504
+ opts[:depth] = n
505
+ else
506
+ opts.delete(:depth)
507
+ end
508
+ end
509
+ opts[:origin] = val if (val = option('origin', ignore: false))
510
+ opts[:branch] = val if (val = option('branch', strict: true))
511
+ opts[:local] = val != '0' if (val = option('local', strict: true))
512
+ opts.delete(:'recurse-submodules') || opts.delete(:'no-recurse-submodules') if append_submodules(:clone)
513
+ append_hash opts
514
+ if cmd.include?('--quiet') || cmd.include?('-q')
515
+ quiet = true
516
+ elsif verbose
517
+ quiet = false
518
+ else
519
+ cmd << '--quiet'
520
+ quiet = true
521
+ end
522
+ append_value([data[0], path], delim: true, escape: false)
523
+ source(banner: sync && !quiet, multiple: !sync || quiet)
280
524
  end
281
525
 
282
- def stash(flag = nil, files = [], commit: nil)
526
+ def stash(flag = nil, files = [], sync: invoked_sync?('stash', flag), commit: nil)
283
527
  cmd = git_session 'stash', flag || 'push'
284
528
  case flag
285
529
  when :apply, :pop
@@ -290,13 +534,13 @@ module Squared
290
534
  append_message option('message', 'm', ignore: false)
291
535
  append_pathspec files
292
536
  end
293
- source(sync: invoked_sync?('stash', flag), **threadargs)
537
+ source(sync: sync, **threadargs)
294
538
  end
295
539
 
296
- def status
540
+ def status(*, sync: invoked_sync?('status'), **)
297
541
  cmd = git_session 'status', option('long') ? '--long' : '--short'
298
542
  if (val = option('ignore-submodules', ignore: false))
299
- cmd << shell_option('ignore-submodules', case val
543
+ cmd << basic_option('ignore-submodules', case val
300
544
  when '0', 'none'
301
545
  'none'
302
546
  when '1', 'untracked'
@@ -305,11 +549,11 @@ module Squared
305
549
  'dirty'
306
550
  else
307
551
  'all'
308
- end, escape: false)
552
+ end)
309
553
  end
310
554
  append_pathspec
311
- out, banner = source(io: true)
312
- if invoked_sync?('status')
555
+ out, banner, from = source(io: true)
556
+ if sync
313
557
  print_item banner
314
558
  banner = nil
315
559
  end
@@ -322,43 +566,58 @@ module Squared
322
566
  styles: [nil, :green, nil, :red], index: -1 }
323
567
  ]
324
568
  end)
325
- list_result(ret, 'files', action: 'modified')
569
+ list_result(ret, 'files', from: from, action: 'modified')
326
570
  end
327
571
 
328
572
  def reset(flag, files = [], ref: nil)
329
573
  cmd = git_session 'reset'
330
- if flag == :head
331
- append_commit ref
332
- append_pathspec files
333
- else
334
- case flag
335
- when :hard, :soft, :merge, :keep
336
- cmd << "--#{flag}"
337
- when :submodules
338
- append_submodules flag
339
- else
574
+ case flag
575
+ when :mode
576
+ case mode
577
+ when 'mixed'
340
578
  cmd << '--mixed'
579
+ cmd << '-N' if option('n')
341
580
  cmd << '--no-refresh' if option('refresh', equals: '0')
581
+ when 'soft', 'hard', 'merge', 'keep'
582
+ cmd << "--#{mode}"
583
+ when 'submodules'
584
+ cmd << '--recurse-submodules'
585
+ when 'no-submodules'
586
+ cmd << '--no-recurse-submodules'
587
+ else
588
+ return
342
589
  end
343
- append_commit ref
590
+ when :patch
591
+ cmd << '--patch'
344
592
  end
593
+ append_commit ref
594
+ append_pathspec files unless files.empty?
345
595
  source
346
596
  end
347
597
 
348
- def checkout(flag, files = [], branch: nil, create: nil, commit: nil, detach: nil)
598
+ def checkout(flag, files = [], branch: nil, origin: nil, create: nil, commit: nil, detach: nil)
349
599
  cmd = git_session 'checkout'
350
600
  case flag
351
601
  when :branch
352
602
  cmd << '--detach' if detach == 'd' || option('detach')
353
- if create
354
- cmd << "-#{create}" << branch
355
- if (val = option('start-point'))
356
- cmd << val
357
- end
358
- else
359
- cmd << branch
603
+ if (val = option('track'))
604
+ cmd << shell_option('track', val)
360
605
  end
606
+ cmd << if create
607
+ shell_option(create, branch)
608
+ else
609
+ branch
610
+ end
361
611
  cmd << commit
612
+ when :track
613
+ if branch
614
+ if branch.start_with?('^')
615
+ opt = 'B'
616
+ branch = branch[1..-1]
617
+ end
618
+ cmd << shell_option(opt || 'b', branch)
619
+ end
620
+ cmd << '--track' << origin
362
621
  when :detach
363
622
  cmd << "--#{flag}" << commit
364
623
  else
@@ -380,7 +639,11 @@ module Squared
380
639
  out = cmd.to_s
381
640
  cmd << '--annotate' if %w[-s --sign -u --local-user].none? { |val| out.include?(" #{val}") }
382
641
  end
383
- append_message message
642
+ if !commit && message && (hash = commithash(message))
643
+ commit = hash
644
+ else
645
+ append_message message
646
+ end
384
647
  append_value(refs, escape: false)
385
648
  append_head commit
386
649
  when :delete, :list, :merged
@@ -391,44 +654,25 @@ module Squared
391
654
  source
392
655
  end
393
656
 
394
- def rev(flag, ref: nil, size: nil)
395
- git_session 'rev-parse', if flag == :branch
396
- '--abbrev-ref'
397
- else
398
- (n = size.to_i) > 0 ? shell_option('short', [n, 5].max, escape: false) : '--verify'
399
- end
400
- append_commit ref
401
- source
402
- end
403
-
404
- def refs(flag, grep: nil)
405
- git_session 'ls-remote', " --#{flag}"
406
- out, banner = source(io: true)
407
- print_item banner
408
- ret = write_lines(out, grep: grep)
409
- list_result(ret, flag.to_s, grep: grep)
410
- end
411
-
412
- def files(flag, grep: nil)
413
- git_session 'ls-files', " --#{flag}"
414
- out, banner = source(io: true)
415
- print_item banner
416
- ret = write_lines(out, grep: grep)
417
- list_result(ret, 'files', grep: grep)
418
- end
419
-
420
- def diff(flag, files = [], branch: nil, index: 0)
657
+ def diff(flag, files = [], branch: nil, index: 0, range: [])
421
658
  cmd = git_session 'diff'
422
- unless flag == :files
423
- if /\A#\h{5,40}\#\z/.match?(sha = files.first)
424
- sha = sha[1..-2]
425
- files.shift
426
- else
427
- sha = nil
659
+ sha = []
660
+ case flag
661
+ when :files, :between, :contain
662
+ cmd.delete('--cached')
663
+ else
664
+ items = files.dup
665
+ files.clear
666
+ items.each do |val|
667
+ if (hash = commithash(val))
668
+ sha << hash
669
+ else
670
+ files << val
671
+ end
428
672
  end
429
673
  end
430
674
  if (val = option('unified')).to_i > 0
431
- cmd << shell_option('unified', val, escape: false)
675
+ cmd << basic_option('unified', val)
432
676
  end
433
677
  append_nocolor
434
678
  case flag
@@ -439,14 +683,23 @@ module Squared
439
683
  cmd << branch
440
684
  when :files
441
685
  cmd << '--no-index'
686
+ when :between, :contain
687
+ cmd << shell_quote(range.join(flag == :between ? '..' : '...'))
442
688
  else
443
689
  if (val = option('index')) || index > 0
444
690
  cmd << "HEAD~#{val || index}"
445
- elsif sha && option('merge-base')
691
+ elsif !sha.empty? && option('merge-base')
446
692
  cmd << '--merge-base'
447
693
  end
448
694
  end
449
- cmd << sha
695
+ unless sha.empty?
696
+ if cmd.include?('--cached')
697
+ raise_error('diff', sha.join(', '), hint: 'one commit') if sha.size > 1
698
+ cmd << sha.first
699
+ else
700
+ cmd.merge(sha)
701
+ end
702
+ end
450
703
  append_pathspec(files, parent: flag == :files)
451
704
  source(exception: cmd.include?('--exit-code'))
452
705
  end
@@ -467,17 +720,17 @@ module Squared
467
720
  end
468
721
  origin = nil
469
722
  branch = nil
470
- upstream = false
471
- source('git fetch --no-tags --quiet', io: true, banner: false)
472
- source('git branch -vv --list', io: true, banner: false).first.each do |val|
473
- next unless (data = /\A\*\s(\S+)\s+(\h+)(?:\s\[(.+?)(?=\]\s)\])?\s/.match(val))
723
+ upstream = nil
724
+ source(git_output('fetch', '--no-tags', '--quiet'), io: true, banner: false)
725
+ source(git_output('branch', '-vv', '--list'), io: true, banner: false).first.each do |val|
726
+ next unless (data = /^\*\s(\S+)\s+(\h+)(?:\s\[(.+?)(?=\]\s)\])?\s/.match(val))
474
727
 
475
728
  branch = data[1]
476
729
  sha = data[2]
477
730
  if !data[3]
478
731
  unless (origin = option('repository', prefix: 'git', ignore: false))
479
- out = source('git log -n1 --format=%h%d', io: true, stdout: true, banner: false).first
480
- if (data = /\A#{sha} \(HEAD -> #{Regexp.escape(branch)}, (.+?)\)\z/.match(out))
732
+ out = source(git_output('log', '-n1', '--format=%h%d'), io: true, stdout: true, banner: false).first
733
+ if (data = /^#{sha} \(HEAD -> #{Regexp.escape(branch)}, (.+?)\)$/.match(out))
481
734
  split_escape(data[1]).each do |val|
482
735
  next unless val.end_with?("/#{branch}")
483
736
 
@@ -486,15 +739,14 @@ module Squared
486
739
  end
487
740
  end
488
741
  end
489
- upstream = !origin.nil?
490
- elsif (data = Regexp.new("\\A(.+)/#{Regexp.escape(branch)}\\z").match(data[3]))
742
+ upstream = true if origin
743
+ elsif (data = Regexp.new("^(.+)/#{Regexp.escape(branch)}$").match(data[3]))
491
744
  origin = data[1]
492
745
  end
493
746
  break
494
747
  end
495
748
  raise_error('commit', 'work tree is not usable') unless origin && branch
496
- cmd = git_session 'commit'
497
- cmd << '--dry-run' if option('dry-run')
749
+ cmd = git_session 'commit', option('dry-run') && '--dry-run'
498
750
  if amend
499
751
  cmd << '--amend'
500
752
  else
@@ -505,10 +757,9 @@ module Squared
505
757
  elsif flag == :'amend-orig' || option('no-edit')
506
758
  cmd << '--no-edit'
507
759
  end
508
- a = ['git add --verbose']
509
- b = ['git push']
510
- b << '--set-upstream' if upstream
511
- if dry_run?
760
+ a = git_output 'add', '--verbose'
761
+ b = git_output 'push', upstream && '--set-upstream'
762
+ if dryrun?
512
763
  a << '--dry-run'
513
764
  b << '--dry-run'
514
765
  end
@@ -516,50 +767,178 @@ module Squared
516
767
  b << '--force' if amend
517
768
  b << origin << branch
518
769
  puts if pass
519
- source a.join(' ')
770
+ source a
520
771
  source cmd
521
- source b.join(' ')
772
+ source b
773
+ end
774
+
775
+ def branch(flag, names = [], target: nil, ref: nil, opts: [])
776
+ cmd = git_session 'branch'
777
+ stdout = false
778
+ case flag
779
+ when :create
780
+ if (arg = option('track', ignore: false))
781
+ cmd << case arg
782
+ when '0'
783
+ '--no-track'
784
+ when 'direct', 'inherit'
785
+ basic_option('track', arg)
786
+ else
787
+ '--track'
788
+ end
789
+ end
790
+ cmd << '--force' if option('force')
791
+ when :set
792
+ if ref.start_with?('!')
793
+ cmd << '--unset-upstream' << shell_escape(arg[1..-1])
794
+ target = nil
795
+ stdout = true
796
+ else
797
+ cmd << quote_option('set-upstream-to', ref)
798
+ end
799
+ ref = nil
800
+ when :delete
801
+ force, list = names.partition { |val| val =~ /^[\^~]/ }
802
+ force.each do |val|
803
+ dr = val[0..2]
804
+ d = dr.include?('^') ? '-D' : '-d'
805
+ r = dr.include?('~') ? '-r' : nil
806
+ source git_output('branch', d, r, shell_quote(val.sub(/^[\^~]+/, '')))
807
+ end
808
+ return if list.empty?
809
+
810
+ cmd << '-d'
811
+ list.each { |val| cmd << shell_quote(val) }
812
+ when :move, :copy
813
+ flag = "-#{flag.to_s[0]}"
814
+ cmd << (option('force') ? flag.upcase : flag)
815
+ names.compact.each { |val| cmd << shell_quote(val) }
816
+ stdout = true
817
+ when :edit
818
+ cmd << '--edit-description'
819
+ when :current
820
+ cmd << '--show-current'
821
+ else
822
+ opts, pat = option_partition(opts, OPT_BRANCH, no: NO_BRANCH)
823
+ grep = []
824
+ opts.each do |opt|
825
+ if opt =~ /^(v+)$/
826
+ cmd << "-#{$1}"
827
+ elsif opt =~ pat
828
+ case $1
829
+ when 'column', 'format'
830
+ cmd << quote_option($1, $2)
831
+ when 'abbrev'
832
+ cmd << basic_option($1, $2) if $2.to_i > 0
833
+ else
834
+ cmd << shell_option($1, $2)
835
+ end
836
+ else
837
+ grep << opt
838
+ end
839
+ end
840
+ cmd << '--list'
841
+ grep.each { |val| cmd << shell_quote(val) }
842
+ out, banner, from = source(io: true)
843
+ print_item banner
844
+ ret = write_lines(out, sub: [
845
+ { pat: /^(\*\s+)(\S+)(\s*)$/, styles: :green, index: 2 },
846
+ { pat: %r{^(\s*)(remotes/\S+)(.*)$}, styles: :red, index: 2 }
847
+ ])
848
+ list_result(ret, 'branches', from: from)
849
+ return
850
+ end
851
+ cmd << shell_escape(target) if target
852
+ cmd << shell_escape(ref) if ref
853
+ source(stdout: stdout)
522
854
  end
523
855
 
524
- def restore(flag, files)
525
- git_session 'restore', "--#{flag}"
856
+ def restore(flag, files, tree: nil)
857
+ source = flag == :source
858
+ cmd = git_session 'restore', source && files.empty? ? '--patch' : nil, shell_option(flag, tree)
859
+ append_option %w[staged worktree]
526
860
  append_first %w[ours theirs]
527
- append_pathspec(files, expect: true)
528
- source(stdout: true)
861
+ append_pathspec(files, expect: !cmd.include?('--patch'))
862
+ source(stdout: !source)
529
863
  end
530
864
 
531
- def show(objs, pretty: nil, format: nil, abbrev: nil)
865
+ def show(objs, format: nil, opts: nil)
532
866
  cmd = git_session 'show'
533
- if (flag = pretty || format)
534
- cmd << case (val = flag.downcase)
867
+ if format
868
+ cmd << case (val = format.downcase)
535
869
  when 'oneline', 'short', 'medium', 'full', 'fuller', 'reference', 'email', 'raw'
536
- shell_option('format', val, escape: false)
870
+ basic_option('format', val)
537
871
  else
538
- shell_option('pretty', flag, escape: false, quote: true)
872
+ quote_option('pretty', format)
539
873
  end
540
874
  end
541
- if (abbrev ||= option('abbrev')) == true
542
- cmd << '--abbrev-commit'
543
- elsif abbrev.to_i > 0
544
- cmd << shell_option('abbrev', abbrev, escape: false)
875
+ if opts
876
+ append_hash opts
877
+ banner = format != 'oneline'
878
+ else
879
+ cmd << basic_option('abbrev', val) if (val = option('abbrev')) && val.to_i > 0
880
+ banner = true
545
881
  end
546
882
  append_value objs
547
- source(exception: false)
883
+ source(exception: false, banner: banner)
884
+ end
885
+
886
+ def rev_parse(flag, ref: nil, size: nil)
887
+ git_session 'rev-parse', if flag == :branch
888
+ '--abbrev-ref'
889
+ else
890
+ (n = size.to_i) > 0 ? basic_option('short', [n, 5].max) : '--verify'
891
+ end
892
+ append_commit ref
893
+ source
894
+ end
895
+
896
+ def ls_remote(flag, grep: nil)
897
+ git_session 'ls-remote', " --#{flag}"
898
+ out, banner, from = source(io: true)
899
+ print_item banner
900
+ ret = write_lines(out, grep: grep)
901
+ list_result(ret, flag.to_s, from: from, grep: grep)
902
+ end
903
+
904
+ def ls_files(flag, grep: nil)
905
+ git_session 'ls-files', " --#{flag}"
906
+ out, banner, from = source(io: true)
907
+ print_item banner
908
+ ret = write_lines(out, grep: grep)
909
+ list_result(ret, 'files', from: from, grep: grep)
910
+ end
911
+
912
+ def clone?
913
+ ref?(workspace.baseref) && workspace.git_clone?(path, name) ? 1 : false
914
+ end
915
+
916
+ def enabled?(*, **kwargs)
917
+ super || (kwargs[:base] == false && !!clone?)
548
918
  end
549
919
 
550
920
  private
551
921
 
552
922
  def source(cmd = @session, exception: true, io: false, sync: true, stdout: false, stderr: false, banner: true,
553
- sub: nil)
923
+ multiple: false, sub: nil)
924
+ banner = nil if multiple && banner
925
+ if cmd.respond_to?(:done)
926
+ if !(io && banner == false) && (from = cmd.drop(1).find { |val| val =~ /^[a-z][a-z\-]{2,}$/ })
927
+ from = :"git:#{from}"
928
+ end
929
+ banner &&= cmd.temp { |val| val.start_with?('--work-tree') || val.start_with?('--git-dir') }
930
+ end
554
931
  cmd = session_done(cmd)
555
932
  log.info cmd
556
- banner = format_banner(cmd.gsub(File.join(path, ''), ''), banner: banner)
557
- cmd = cmd.sub(/\Agit\b/, "git --work-tree=#{shell_quote(path)} --git-dir=#{shell_quote(gitpath)}")
933
+ on :first, from if from
934
+ banner = if banner
935
+ format_banner((banner.is_a?(String) ? banner : cmd).gsub(File.join(path, ''), ''), banner: true)
936
+ end
558
937
  begin
559
- if io
560
- [stdout ? `#{cmd}` : IO.popen(cmd), banner]
561
- elsif stdin? ? sync : stdout
562
- print_item banner
938
+ return [stdout ? `#{cmd}` : IO.popen(cmd), banner, from] if io
939
+
940
+ if stdin? ? sync : stdout
941
+ print_item banner unless multiple
563
942
  ret = `#{cmd}`
564
943
  if !ret.empty?
565
944
  puts ret
@@ -567,7 +946,7 @@ module Squared
567
946
  puts 'Success'
568
947
  end
569
948
  elsif sync || (!exception && !stderr)
570
- print_item banner
949
+ print_item banner unless multiple
571
950
  shell(cmd, exception: exception)
572
951
  else
573
952
  require 'open3'
@@ -587,9 +966,12 @@ module Squared
587
966
  end
588
967
  rescue StandardError => e
589
968
  log.error e
590
- raise if exception
969
+ ret = on(:error, from, e) if from
970
+ raise if exception && ret != true
591
971
 
592
972
  warn log_message(Logger::WARN, e) if warning?
973
+ else
974
+ on :last, from if from
593
975
  end
594
976
  end
595
977
 
@@ -617,80 +999,133 @@ module Squared
617
999
  ret
618
1000
  end
619
1001
 
620
- def list_result(size, type, action: 'found', grep: nil)
621
- return unless verbose
622
-
623
- if size > 0
624
- styles = theme.fetch(:banner, []).reject { |s| s.to_s.end_with?('!') }
625
- styles << :bold if styles.size <= 1
626
- puts print_footer("#{size} #{size == 1 ? type.sub(/s\z/, '') : type}",
627
- sub: { pat: /\A(\d+)(.+)\z/, styles: styles })
628
- else
629
- puts empty_status("No #{type} were #{action}", 'grep', grep)
1002
+ def list_result(size, type, from: nil, action: 'found', grep: nil)
1003
+ if verbose
1004
+ if size > 0
1005
+ styles = theme.fetch(:banner, []).reject { |s| s.to_s.end_with?('!') }
1006
+ styles << :bold if styles.size <= 1
1007
+ puts print_footer("#{size} #{size == 1 ? type.sub(/s\z/, '') : type}",
1008
+ sub: { pat: /\A(\d+)(.+)\z/, styles: styles })
1009
+ else
1010
+ puts empty_status("No #{type} were #{action}", 'grep', grep)
1011
+ end
630
1012
  end
1013
+ on :last, from if from
631
1014
  end
632
1015
 
633
- def append_pull(opts, list, flag = nil)
634
- append_submodules flag
635
- list = list.grep_v(/[^a-z\-]/)
1016
+ def append_pull(opts, list, no = [], target: @session, flag: nil, remote: nil)
1017
+ cmd << '--force' if option('force')
1018
+ modules = append_submodules(target: target)
1019
+ out = []
1020
+ refspec = []
1021
+ opts, pat = option_partition(opts, remote ? list + ['refspec=s'] : list, target: target, no: no)
636
1022
  opts.each do |opt|
637
- if list.include?(opt) || opt.match(/^(?:de(?:pth|epen)|jobs)=\d+$/)
638
- @session << "--#{opt}"
639
- elsif opt.match(/^(?:shallow-)?since=(.+)$/) && (val = Date.parse($1))
640
- @session << shell_option('shallow-since', val.strftime('%F %T'), escape: false, quote: true)
641
- elsif opt.end_with?('!') && list.include?(opt = opt[0..-2])
642
- @session << "--no-#{opt}"
1023
+ if opt =~ pat
1024
+ case $1
1025
+ when 'rebase'
1026
+ target << basic_option($1, $2) if VAL_REBASE.include?($2)
1027
+ when 'shallow-since'
1028
+ next unless (val = Date.parse($2))
1029
+
1030
+ target << quote_option($1, val.strftime('%F %T'))
1031
+ when 'recurse-submodules'
1032
+ target << shell_option($1, $2, quote: false) unless modules
1033
+ when 'shallow-exclude', 'gpg-sign', 'signoff', 'strategy', 'strategy-option'
1034
+ target << shell_option($1, $2)
1035
+ when 'refmap', 'negotiation-tip'
1036
+ target << quote_option($1, $2)
1037
+ when 'depth', 'deepen', 'log', 'jobs'
1038
+ target << basic_option($1, $2) if $2.to_i > 0
1039
+ when 'refspec'
1040
+ refspec << shell_escape($2, quote: true)
1041
+ end
1042
+ else
1043
+ out << opt
643
1044
  end
644
1045
  end
1046
+ if remote
1047
+ target << shell_escape(remote, quote: true)
1048
+ if (val = option('refspec', strict: true))
1049
+ append_value(split_escape(val), escape: false)
1050
+ else
1051
+ target.merge(refspec)
1052
+ end
1053
+ target.delete('--all')
1054
+ elsif target.include?('--multiple')
1055
+ target.merge(out.map { |opt| shell_escape(opt, quote: true) })
1056
+ out.clear
1057
+ end
1058
+ option_clear(out, target: target, subject: flag.to_s) if flag
645
1059
  end
646
1060
 
647
- def append_commit(val)
1061
+ def append_commit(val, target: @session)
648
1062
  val = val.to_s.strip
649
- @session << (val.empty? ? 'HEAD' : val)
1063
+ target << (val.empty? ? 'HEAD' : val)
650
1064
  end
651
1065
 
652
- def append_pathspec(files = [], expect: false, parent: false)
1066
+ def append_pathspec(files = [], target: @session, expect: false, parent: false)
653
1067
  if files.empty? && (val = option('pathspec'))
654
1068
  files = split_escape(val)
655
1069
  end
656
1070
  files = projectmap(files, parent: parent)
657
1071
  if !files.empty?
658
- @session << "-- #{files.join(' ')}"
1072
+ target << "-- #{files.join(' ')}"
659
1073
  elsif expect
660
1074
  raise_error(parent ? 'pathspec not present' : 'pathspec not within worktree', hint: 'invalid')
661
1075
  end
662
1076
  end
663
1077
 
664
- def append_message(val)
665
- @session << "--message=\"#{double_quote(val)}\"" unless val.to_s.empty?
1078
+ def append_message(val, target: @session)
1079
+ target << "--message=\"#{double_quote(val)}\"" unless val.to_s.empty?
666
1080
  end
667
1081
 
668
- def append_head(val = nil)
669
- return @session << val if val
1082
+ def append_head(val = nil, target: @session)
1083
+ return target << val if val
670
1084
 
671
- append_first(%w[head tree-ish object], flag: false, ignore: false)
1085
+ append_first(%w[head tree-ish object], target: target, flag: false, ignore: false)
672
1086
  end
673
1087
 
674
- def append_submodules(flag = nil)
675
- if option('recurse-submodules', equals: '0')
676
- @session << '--no-recurse-submodules'
677
- elsif flag == :submodules || option('recurse-submodules')
678
- @session << '--recurse-submodules'
1088
+ def append_submodules(from = nil, target: @session)
1089
+ return unless (val = option('recurse-submodules', ignore: false))
1090
+
1091
+ if from == :clone
1092
+ projectmap(split_escape(val)).each do |path|
1093
+ target << basic_option('recurse-submodules', path)
1094
+ end
1095
+ target
1096
+ else
1097
+ target << case val
1098
+ when 'no', '0'
1099
+ '--no-recurse-submodules'
1100
+ when 'yes', 'on-demand'
1101
+ "--recurse-submodules#{from == :reset ? '' : "=#{val}"}"
1102
+ else
1103
+ '--recurse-submodules'
1104
+ end
679
1105
  end
680
1106
  end
681
1107
 
682
- def git_session(*cmd)
683
- session('git', *cmd)
1108
+ def git_session(*cmd, worktree: true, **kwargs)
1109
+ dir = worktree ? ["--work-tree=#{shell_quote(path)}", "--git-dir=#{shell_quote(gitpath)}"] : []
1110
+ session('git', *dir, *cmd, **kwargs)
1111
+ end
1112
+
1113
+ def git_output(*cmd, **kwargs)
1114
+ git_session(*cmd, main: false, options: false, **kwargs)
684
1115
  end
685
1116
 
686
- def dry_run?
687
- @session.include?('--dry-run')
1117
+ def dryrun?(*)
1118
+ !!@session&.include?('--dry-run')
688
1119
  end
689
1120
 
690
1121
  def gitpath
691
1122
  basepath('.git')
692
1123
  end
693
1124
 
1125
+ def commithash(val)
1126
+ (data = /\A#\{(\h{5,40})\}\z/.match(val)) ? data[1] : nil
1127
+ end
1128
+
694
1129
  def threadargs
695
1130
  { stderr: true, exception: exception || !workspace.series.multiple? }
696
1131
  end