squared 0.1.7 → 0.2.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.
@@ -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)
382
+ end
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)
240
417
  end
241
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)
280
495
  end
281
496
 
282
- def stash(flag = nil, files = [], commit: nil)
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)
524
+ end
525
+
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,14 +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
- cmd << '--branch' if option('branch')
299
542
  if (val = option('ignore-submodules', ignore: false))
300
- cmd << shell_option('ignore-submodules', case val
543
+ cmd << basic_option('ignore-submodules', case val
301
544
  when '0', 'none'
302
545
  'none'
303
546
  when '1', 'untracked'
@@ -306,11 +549,11 @@ module Squared
306
549
  'dirty'
307
550
  else
308
551
  'all'
309
- end, escape: false)
552
+ end)
310
553
  end
311
554
  append_pathspec
312
- out, banner = source(io: true)
313
- if invoked_sync?('status')
555
+ out, banner, from = source(io: true)
556
+ if sync
314
557
  print_item banner
315
558
  banner = nil
316
559
  end
@@ -323,43 +566,58 @@ module Squared
323
566
  styles: [nil, :green, nil, :red], index: -1 }
324
567
  ]
325
568
  end)
326
- list_result(ret, 'files', action: 'modified')
569
+ list_result(ret, 'files', from: from, action: 'modified')
327
570
  end
328
571
 
329
572
  def reset(flag, files = [], ref: nil)
330
573
  cmd = git_session 'reset'
331
- if flag == :head
332
- append_commit ref
333
- append_pathspec files
334
- else
335
- case flag
336
- when :hard, :soft, :merge, :keep
337
- cmd << "--#{flag}"
338
- when :submodules
339
- append_submodules flag
340
- else
574
+ case flag
575
+ when :mode
576
+ case mode
577
+ when 'mixed'
341
578
  cmd << '--mixed'
579
+ cmd << '-N' if option('n')
342
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
343
589
  end
344
- append_commit ref
590
+ when :patch
591
+ cmd << '--patch'
345
592
  end
593
+ append_commit ref
594
+ append_pathspec files unless files.empty?
346
595
  source
347
596
  end
348
597
 
349
- 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)
350
599
  cmd = git_session 'checkout'
351
600
  case flag
352
601
  when :branch
353
602
  cmd << '--detach' if detach == 'd' || option('detach')
354
- if create
355
- cmd << "-#{create}" << branch
356
- if (val = option('start-point'))
357
- cmd << val
358
- end
359
- else
360
- cmd << branch
603
+ if (val = option('track'))
604
+ cmd << shell_option('track', val)
361
605
  end
606
+ cmd << if create
607
+ shell_option(create, branch)
608
+ else
609
+ branch
610
+ end
362
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
363
621
  when :detach
364
622
  cmd << "--#{flag}" << commit
365
623
  else
@@ -381,7 +639,11 @@ module Squared
381
639
  out = cmd.to_s
382
640
  cmd << '--annotate' if %w[-s --sign -u --local-user].none? { |val| out.include?(" #{val}") }
383
641
  end
384
- append_message message
642
+ if !commit && message && (hash = commithash(message))
643
+ commit = hash
644
+ else
645
+ append_message message
646
+ end
385
647
  append_value(refs, escape: false)
386
648
  append_head commit
387
649
  when :delete, :list, :merged
@@ -392,44 +654,25 @@ module Squared
392
654
  source
393
655
  end
394
656
 
395
- def rev(flag, ref: nil, size: nil)
396
- git_session 'rev-parse', if flag == :branch
397
- '--abbrev-ref'
398
- else
399
- (n = size.to_i) > 0 ? shell_option('short', [n, 5].max, escape: false) : '--verify'
400
- end
401
- append_commit ref
402
- source
403
- end
404
-
405
- def refs(flag, grep: nil)
406
- git_session 'ls-remote', "--#{flag}", '--refs'
407
- out, banner = source(io: true)
408
- print_item banner
409
- ret = write_lines(out, grep: grep)
410
- list_result(ret, flag.to_s, grep: grep)
411
- end
412
-
413
- def files(flag, grep: nil)
414
- git_session 'ls-files', "--#{flag}"
415
- out, banner = source(io: true)
416
- print_item banner
417
- ret = write_lines(out, grep: grep)
418
- list_result(ret, 'files', grep: grep)
419
- end
420
-
421
- def diff(flag, files = [], branch: nil, index: 0)
657
+ def diff(flag, files = [], branch: nil, index: 0, range: [])
422
658
  cmd = git_session 'diff'
423
- unless flag == :files
424
- if /\A#\h{5,40}\#\z/.match?(sha = files.first)
425
- sha = sha[1..-2]
426
- files.shift
427
- else
428
- 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
429
672
  end
430
673
  end
431
674
  if (val = option('unified')).to_i > 0
432
- cmd << shell_option('unified', val, escape: false)
675
+ cmd << basic_option('unified', val)
433
676
  end
434
677
  append_nocolor
435
678
  case flag
@@ -440,14 +683,23 @@ module Squared
440
683
  cmd << branch
441
684
  when :files
442
685
  cmd << '--no-index'
686
+ when :between, :contain
687
+ cmd << shell_quote(range.join(flag == :between ? '..' : '...'))
443
688
  else
444
689
  if (val = option('index')) || index > 0
445
690
  cmd << "HEAD~#{val || index}"
446
- elsif sha && option('merge-base')
691
+ elsif !sha.empty? && option('merge-base')
447
692
  cmd << '--merge-base'
448
693
  end
449
694
  end
450
- 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
451
703
  append_pathspec(files, parent: flag == :files)
452
704
  source(exception: cmd.include?('--exit-code'))
453
705
  end
@@ -466,85 +718,227 @@ module Squared
466
718
  raise_error('commit', 'pathspec', hint: 'missing') if (files = projectmap(files)).empty?
467
719
  "-- #{files.join(' ')}"
468
720
  end
469
- format = '%(if)%(HEAD)%(then)%(refname:short)...%(upstream:short)...%(upstream:track)%(end)'
470
- branch = nil
471
721
  origin = nil
472
- source('git fetch --no-tags --quiet', io: true, banner: false, stdout: true)
473
- source("git for-each-ref --format=\"#{format}\" refs/heads", io: true, banner: false).first.each do |line|
474
- next if (line = line.chomp).empty?
722
+ branch = nil
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))
475
727
 
476
- branch, origin, hint = line.split('...')
477
- if hint && !hint.match?(/^\[(\D+0,\D+0)\]$/)
478
- raise_error('work tree is not usable', hint: hint[1..-2])
479
- elsif origin.empty?
480
- return nil if pass
728
+ branch = data[1]
729
+ sha = data[2]
730
+ if !data[3]
731
+ unless (origin = option('repository', prefix: 'git', ignore: false))
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))
734
+ split_escape(data[1]).each do |val|
735
+ next unless val.end_with?("/#{branch}")
481
736
 
482
- raise_error('no remote upstream', hint: branch)
737
+ origin = val[0..val.size - branch.size - 2]
738
+ break
739
+ end
740
+ end
741
+ end
742
+ upstream = true if origin
743
+ elsif (data = Regexp.new("^(.+)/#{Regexp.escape(branch)}$").match(data[3]))
744
+ origin = data[1]
483
745
  end
484
746
  break
485
747
  end
486
- i = origin.index('/')
487
- branch = "#{branch}:#{origin[i + 1..-1]}" unless origin.end_with?("/#{branch}")
488
- origin = origin[0..i - 1]
489
- cmd = git_session 'commit'
490
- cmd << '--dry-run' if option('dry-run')
491
- cmd << '--amend' if amend
748
+ raise_error('commit', 'work tree is not usable') unless origin && branch
749
+ cmd = git_session 'commit', option('dry-run') && '--dry-run'
750
+ if amend
751
+ cmd << '--amend'
752
+ else
753
+ cmd.delete('--amend')
754
+ end
492
755
  if message
493
756
  append_message message
494
757
  elsif flag == :'amend-orig' || option('no-edit')
495
758
  cmd << '--no-edit'
496
759
  end
497
- a = ['git add --verbose']
498
- b = ['git push']
499
- b << '--dry-run' if dry_run?
760
+ a = git_output 'add', '--verbose'
761
+ b = git_output 'push', upstream && '--set-upstream'
762
+ if dryrun?
763
+ a << '--dry-run'
764
+ b << '--dry-run'
765
+ end
500
766
  a << pathspec
501
- b << '--force-with-lease' if amend
767
+ b << '--force' if amend
502
768
  b << origin << branch
503
769
  puts if pass
504
- source a.join(' ')
770
+ source a
505
771
  source cmd
506
- 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)
507
854
  end
508
855
 
509
- def restore(flag, files)
510
- 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]
511
860
  append_first %w[ours theirs]
512
- append_pathspec(files, expect: true)
513
- source(stdout: true)
861
+ append_pathspec(files, expect: !cmd.include?('--patch'))
862
+ source(stdout: !source)
514
863
  end
515
864
 
516
- def show(objs, pretty: nil, format: nil, abbrev: nil)
865
+ def show(objs, format: nil, opts: nil)
517
866
  cmd = git_session 'show'
518
- if (flag = pretty || format)
519
- cmd << case (val = flag.downcase)
867
+ if format
868
+ cmd << case (val = format.downcase)
520
869
  when 'oneline', 'short', 'medium', 'full', 'fuller', 'reference', 'email', 'raw'
521
- shell_option('format', val, escape: false)
870
+ basic_option('format', val)
522
871
  else
523
- shell_option('pretty', flag, escape: false, quote: true)
872
+ quote_option('pretty', format)
524
873
  end
525
874
  end
526
- if (abbrev ||= option('abbrev')) == true
527
- cmd << '--abbrev-commit'
528
- elsif abbrev.to_i > 0
529
- 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
530
881
  end
531
882
  append_value objs
532
- 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?)
533
918
  end
534
919
 
535
920
  private
536
921
 
537
922
  def source(cmd = @session, exception: true, io: false, sync: true, stdout: false, stderr: false, banner: true,
538
- 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
539
931
  cmd = session_done(cmd)
540
- log&.info cmd
541
- banner = format_banner(cmd.gsub(File.join(path, ''), ''), banner: banner)
542
- cmd = cmd.sub(/\Agit\b/, "git --work-tree=#{shell_quote(path)} --git-dir=#{shell_quote(gitpath)}")
932
+ log.info cmd
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
543
937
  begin
544
- if io
545
- [stdout ? `#{cmd}` : IO.popen(cmd), banner]
546
- elsif stdin? ? sync : stdout
547
- 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
548
942
  ret = `#{cmd}`
549
943
  if !ret.empty?
550
944
  puts ret
@@ -552,7 +946,7 @@ module Squared
552
946
  puts 'Success'
553
947
  end
554
948
  elsif sync || (!exception && !stderr)
555
- print_item banner
949
+ print_item banner unless multiple
556
950
  shell(cmd, exception: exception)
557
951
  else
558
952
  require 'open3'
@@ -571,10 +965,13 @@ module Squared
571
965
  end
572
966
  end
573
967
  rescue StandardError => e
574
- log&.error e
575
- raise if exception
968
+ log.error e
969
+ ret = on(:error, from, e) if from
970
+ raise if exception && ret != true
576
971
 
577
972
  warn log_message(Logger::WARN, e) if warning?
973
+ else
974
+ on :last, from if from
578
975
  end
579
976
  end
580
977
 
@@ -587,7 +984,7 @@ module Squared
587
984
  next if grep && !line.match?(grep)
588
985
 
589
986
  if loglevel
590
- log&.add loglevel, line
987
+ log.add loglevel, line
591
988
  else
592
989
  sub&.each { |h| line = sub_style(line, **h) }
593
990
  if banner
@@ -602,80 +999,133 @@ module Squared
602
999
  ret
603
1000
  end
604
1001
 
605
- def list_result(size, type, action: 'found', grep: nil)
606
- return unless verbose
607
-
608
- if size > 0
609
- styles = theme.fetch(:banner, []).reject { |s| s.to_s.end_with?('!') }
610
- styles << :bold if styles.size <= 1
611
- puts print_footer("#{size} #{size == 1 ? type.sub(/s\z/, '') : type}",
612
- sub: { pat: /\A(\d+)(.+)\z/, styles: styles })
613
- else
614
- 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
615
1012
  end
1013
+ on :last, from if from
616
1014
  end
617
1015
 
618
- def append_pull(opts, list, flag = nil)
619
- append_submodules flag
620
- 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)
621
1022
  opts.each do |opt|
622
- if list.include?(opt) || opt.match(/^(?:de(?:pth|epen)|jobs)=\d+$/)
623
- @session << "--#{opt}"
624
- elsif opt.match(/^(?:shallow-)?since=(.+)$/) && (val = Date.parse($1))
625
- @session << shell_option('shallow-since', val.strftime('%F %T'), escape: false, quote: true)
626
- elsif opt.end_with?('!') && list.include?(opt = opt[0..-2])
627
- @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
628
1044
  end
629
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
630
1059
  end
631
1060
 
632
- def append_commit(val)
1061
+ def append_commit(val, target: @session)
633
1062
  val = val.to_s.strip
634
- @session << (val.empty? ? 'HEAD' : val)
1063
+ target << (val.empty? ? 'HEAD' : val)
635
1064
  end
636
1065
 
637
- def append_pathspec(files = [], expect: false, parent: false)
1066
+ def append_pathspec(files = [], target: @session, expect: false, parent: false)
638
1067
  if files.empty? && (val = option('pathspec'))
639
1068
  files = split_escape(val)
640
1069
  end
641
1070
  files = projectmap(files, parent: parent)
642
1071
  if !files.empty?
643
- @session << "-- #{files.join(' ')}"
1072
+ target << "-- #{files.join(' ')}"
644
1073
  elsif expect
645
1074
  raise_error(parent ? 'pathspec not present' : 'pathspec not within worktree', hint: 'invalid')
646
1075
  end
647
1076
  end
648
1077
 
649
- def append_message(val)
650
- @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?
651
1080
  end
652
1081
 
653
- def append_head(val = nil)
654
- return @session << val if val
1082
+ def append_head(val = nil, target: @session)
1083
+ return target << val if val
655
1084
 
656
- 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)
657
1086
  end
658
1087
 
659
- def append_submodules(flag = nil)
660
- if option('recurse-submodules', equals: '0')
661
- @session << '--no-recurse-submodules'
662
- elsif flag == :submodules || option('recurse-submodules')
663
- @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
664
1105
  end
665
1106
  end
666
1107
 
667
- def git_session(*cmd)
668
- 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)
669
1115
  end
670
1116
 
671
- def dry_run?
672
- @session.include?('--dry-run')
1117
+ def dryrun?(*)
1118
+ !!@session&.include?('--dry-run')
673
1119
  end
674
1120
 
675
1121
  def gitpath
676
1122
  basepath('.git')
677
1123
  end
678
1124
 
1125
+ def commithash(val)
1126
+ (data = /\A#\{(\h{5,40})\}\z/.match(val)) ? data[1] : nil
1127
+ end
1128
+
679
1129
  def threadargs
680
1130
  { stderr: true, exception: exception || !workspace.series.multiple? }
681
1131
  end