squared 0.4.8 → 0.4.9

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.
@@ -27,7 +27,7 @@ module Squared
27
27
  return self
28
28
  end
29
29
  if base
30
- base = base.match?(GIT_PROTO) ? "#{base.chomp('/')}/" : @root.join(base)
30
+ base = base.match?(GIT_PROTO) ? "#{base.chomp('/')}/" : @root + base
31
31
  repo.each do |target|
32
32
  if target.is_a?(Project::Git)
33
33
  data[target.localname] = target.project
@@ -46,14 +46,14 @@ module Squared
46
46
  end
47
47
  unless uri.match?(GIT_PROTO) || Pathname.new(uri).absolute?
48
48
  if uri.start_with?('.')
49
- uri = @root.join(uri)
49
+ uri = @root + uri
50
50
  elsif base
51
- uri = base.is_a?(Pathname) ? base.join(uri) : base + uri
51
+ uri = base + uri
52
52
  else
53
53
  next
54
54
  end
55
55
  end
56
- key = task_name(key)
56
+ key = task_name key
57
57
  (GIT_REPO[main] ||= {})[key] = [uri.to_s, opts]
58
58
  (@kind[key] ||= []) << Project::Git
59
59
  end
@@ -145,115 +145,117 @@ module Squared
145
145
  module Project
146
146
  class Git < Base
147
147
  OPT_GIT = {
148
- common: %w[bare paginate no-pager glob-pathspecs icase-pathspecs literal-pathspecs no-optional-locks
149
- no-replace-objects noglob-pathspecs c=q config-env=q exec-path=p namespace=p].freeze,
150
- branch: %w[a|all create-reflog i|ignore-case q|quiet r|remotes v|verbose abbrev=i color=b column=b
151
- contains=b format=q merged=b no-contains=b no-merged=b points-at=e u|set-upstream-to=e sort=q
148
+ common: %w[c=q bare glob-pathspecs icase-pathspecs literal-pathspecs no-optional-locks no-pager
149
+ no-replace-objects noglob-pathspecs paginate config-env=q exec-path=p namespace=p].freeze,
150
+ add: %w[A|all e|edit f|force ignore-errors ignore-missing ignore-removal i|interactive no-all
151
+ no-ignore-removal n|dry-run p|patch pathspec-file-nul renormalize sparse u|update v|verbose
152
+ chmod=b pathspec-from-file=p].freeze,
153
+ branch: %w[a|all create-reflog i|ignore-case q|quiet r|remotes v|verbose vv abbrev=i color=b column=b
154
+ contains=b format=q merged=b no-contains=b no-merged=b points-at=b u|set-upstream-to=b sort=q
152
155
  t|track=b].freeze,
153
156
  checkout: %w[l d|detach f|force ignore-other-worktrees ignore-skip-worktree-bits m|merge p|patch
154
- pathspec-file-nul q|quiet orphan=e ours theirs conflict=b pathspec-from-file=p
155
- t|track=b].freeze,
156
- clean: %w[d x X f|force i|interactive n|dry-run q|quiet e|exlcude=q].freeze,
157
+ pathspec-file-nul q|quiet ours theirs conflict=b orphan=b pathspec-from-file=p t|track=b].freeze,
157
158
  diff: {
158
159
  base: %w[0 1|base 2|ours 3|theirs].freeze,
159
160
  show: %w[s exit-code histogram].freeze
160
161
  }.freeze,
161
162
  fetch: {
162
- base: %w[multiple progress P|prune-tags refetch stdin u|update-head-ok
163
- recurse-submodules-default=b].freeze,
164
- pull: %w[4 6 n t a|append atomic dry-run f|force k|keep n|negotiate-only prefetch p|prune q|quiet
163
+ base: %w[multiple progress P|prune-tags refetch stdin u|update-head-ok recurse-submodules-default=b].freeze,
164
+ pull: %w[4 6 n t a|append atomic dry-run f|force k|keep negotiate-only prefetch p|prune q|quiet
165
165
  set-upstream unshallow update-shallow v|verbose deepen=i depth=i j|jobs=i negotiation-tip=q
166
- recurse-submodules=v refmap=q o|server-option=q shallow-exclude=e shallow-since=v
166
+ recurse-submodules=v refmap=q o|server-option=q shallow-exclude=b shallow-since=v
167
167
  upload-pack=q].freeze
168
168
  }.freeze,
169
+ git: {
170
+ add: %w[N|intent-to-add refresh].freeze,
171
+ clean: %w[d x X f|force n|dry-run i|interactive q|quiet e|exclude=q].freeze,
172
+ mv: %w[k f|force n|dry-run v|verbose].freeze,
173
+ revert: %w[e S=bm? abort continue n|no-commit quit reference skip cleanup=b gpg-sign=b? m|mainline=i
174
+ s|signoff strategy=b X|strategy-option=b].freeze,
175
+ rm: %w[r cached f|force n|dry-run ignore-unmatch pathspec-file-nul q|quiet sparse v|verbose
176
+ pathspec-from-file=p].freeze
177
+ }.freeze,
169
178
  log: {
170
- base: %w[all all-match alternate-refs author-date-order basic-regexp bisect boundary cherry cherry-mark
179
+ base: %w[L=qm all all-match alternate-refs author-date-order basic-regexp bisect boundary cherry cherry-mark
171
180
  cherry-pick clear-decorations date-order dense do-walk exclude-first-parent-only E|extended-regexp
172
181
  first-parent F|fixed-strings follow full-diff full-history ignore-missing invert-grep left-only
173
- merge log-size no-max-parents no-min-parents not P|perl-regexp reflog i|regexp-ignore-case
182
+ log-size merge no-max-parents no-min-parents not P|perl-regexp reflog i|regexp-ignore-case
174
183
  remove-empty reverse right-only simplify-by-decoration simplify-merges single-worktree show-pulls
175
184
  source sparse stdin topo-order g|walk-reflogs after=q ancestry-path=b? author=q before=q
176
185
  branches=q? committer=q decorate=b decorate-refs=q decorate-refs-exclude=q exclude=q
177
- exclude-hidden=b? glob=q grep=q grep-reflog=q L=q n|max-count=i max-parents=i min-parents=i
178
- no-walk=b? remotes=q? since=q since-as-filter=q skip=i tags=q? until=q].freeze,
186
+ exclude-hidden=b glob=q grep=q grep-reflog=q n|max-count=i max-parents=i min-parents=i no-walk=b?
187
+ remotes=q? since=q since-as-filter=q skip=i tags=q? until=q].freeze,
179
188
  format: %w[t children combined-all-paths oneline left-right no-diff-merges parents relative-date
180
- show-signature date=q diff-merges=b encoding=b expand-tabs=i format=q notes=q pretty=q?
189
+ show-signature date=q diff-merges=b encoding=b expand-tabs=i format=q notes=b pretty=q?
181
190
  show-linear-break=q?].freeze,
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
183
- W|function-context w|ignore-all-space ignore-blank-lines ignore-cr-at-eol ignore-space-at-eol
184
- b|ignore-space-change D|irreversible-delete graph ita-invisible-in-index minimal name-only
185
- name-status no-color-moved-ws no-prefix no-renames numstat patch-with-raw patch-with-stat patience
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?
189
- I|ignore-matching-lines=q ignore-submodules=b inter-hunk-context=i line-prefix=q output=p
190
- output-indicator-context=q output-indicator-new=q output-indicator-old=q relative=p rotate-to=p
191
- skip-to=p src-prefix=q stat=q? stat-width=i stat-name-width=i stat-count=i submodule=b? U|unified=i
192
- word-diff=b? word-diff-regex=q ws-error-highligt=q].freeze
191
+ diff: %w[p R u z B=bm? C=bm? l=im G=qm I=qm M=bm? O=qm S=qm U=im binary check compact-summary cumulative
192
+ find-copies-harder full-index W|function-context w|ignore-all-space ignore-blank-lines
193
+ ignore-cr-at-eol ignore-space-at-eol b|ignore-space-change D|irreversible-delete graph
194
+ ita-invisible-in-index minimal name-only name-status no-color-moved-ws no-prefix no-renames numstat
195
+ patch-with-raw patch-with-stat patience pickaxe-all pickaxe-regex raw shortstat summary a|text
196
+ abbrev=i? anchored=q break-rewrites=b? color=b color-moved=b color-moved-ws=b color-words=q?
197
+ diff-algorithm=b diff-filter=e? X|dirstat=b? dirstat-by-file=b? dst-prefix=q find-copies=i?
198
+ find-object=b find-renames=b? ignore-matching-lines=q ignore-submodules=b? inter-hunk-context=i
199
+ line-prefix=q output=p output-indicator-context=q output-indicator-new=q output-indicator-old=q
200
+ relative=p rotate-to=p skip-to=p src-prefix=q stat=b? stat-count=i stat-width=i stat-name-width=i
201
+ submodule=b? unified=i word-diff=b? word-diff-regex=q ws-error-highlight=b].freeze
193
202
  }.freeze,
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
203
+ ls_files: %w[f t v z debug deduplicate directory eol error-unmatch exclude-standard full-name i|ignored
204
+ k|killed no-empty-directory recurse-submodules sparse s|stage u|unmerged abbrev=i x|exclude=q
196
205
  X|exclude-from=p exclude-per-directory=p format=q with-tree=q].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=v? s|strategy=b
206
+ ls_remote: %w[exit-code get-url q|quiet symref o|server-option=q sort=q upload-pack=q].freeze,
207
+ merge: %w[e n S=bm? allow-unrelated-histories ff-only m=q q|quiet v|verbose cleanup=b F|file=p gpg-sign=b?
208
+ into-name=b log=i s|strategy=b X|strategy-option=b].freeze,
209
+ pull: %w[e n S=bm? allow-unrelated-histories ff-only cleanup=b gpg-sign=b? log=i r|rebase=v? s|strategy=b
200
210
  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,
203
- rebase: %w[n C=i allow-empty-message apply committer-date-is-author-date edit-todo f|force-rebase ignore-date
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,
211
+ rebase: %w[n C=im S=bm? allow-empty-message apply committer-date-is-author-date edit-todo empty=b
212
+ f|force-rebase ignore-date ignore-whitespace i|interactive keep-base m|merge no-ff q|quiet quit
213
+ reset-author-date root show-current-patch signoff v|verbose empty=b x|exec=q gpg-sign=b? onto=b
214
+ r|rebase-merges=b s|strategy=b X|strategy-option=b whitespace=b].freeze,
207
215
  reset: %w[N pathspec-file-nul q|quiet pathspec-from-file=p].freeze,
208
- restore: %w[ignore-unmerged ignore-skip-worktree-bits m|merge ours p|patch pathspec-file-nul S|staged theirs
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,
212
- rev_parse: {
213
- output: %w[absolute-git-dir all flags git-common-dir git-dir is-bare-repository is-inside-git-dir
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,
219
- parseopt: %w[keep-dashdash stop-at-non-option stuck-long].freeze
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,
216
+ restore: %w[ignore-skip-worktree-bits ignore-unmerged m|merge ours p|patch pathspec-file-nul q|quiet S|staged
217
+ theirs W|worktree conflict=b pathspec-from-file=p s|source=b].freeze,
218
+ rev_parse: %w[absolute-git-dir all git-common-dir git-dir is-bare-repository is-inside-git-dir
219
+ is-inside-work-tree is-shallow-repository local-env-vars no-revs not q|quiet revs-only
220
+ shared-index-path show-cdup show-prefix show-superproject-working-tree show-toplevel sq sq-quote
221
+ symbolic symbolic-full-name verify abbrev-ref=b? after=q before=q branches=q? default=q
222
+ disambiguate=b exclude=q exclude-hidden=b git-path=p glob=q path-format=b? prefix=q remotes=q?
223
+ resolve-git-dir=p short=i? show-object-format=b? since=q tags=q? until=q].freeze,
223
224
  show: %w[t combined-all-paths no-diff-merges remerge-diff show-signature diff-merges=b encoding=b
224
225
  expand-tabs=i notes=q show-notes=q?].freeze,
225
226
  stash: {
226
227
  common: %w[q|quiet].freeze,
227
- push: %w[a|all u|include-untracked k|keep-index no-keep-index p|patch S|staged m|message=q
228
- pathspec-from-file=p].freeze,
228
+ push: %w[a|all u|include-untracked k|keep-index no-keep-index no-include-untracked pathspec-file-nul
229
+ p|patch S|staged m|message=q pathspec-from-file=p].freeze,
229
230
  pop: %w[index].freeze,
230
231
  apply: %w[index].freeze
231
232
  }.freeze,
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
234
- sort=q].freeze,
233
+ status: %w[u|ignore-submodules=bm? ignored=b? untracked-files=b?],
234
+ tag: %w[n=im cleanup=b create-reflog i|ignore-case color=b? column=b contains=b? format=q merged=b?
235
+ no-contains=b? no-merged=b? points-at=q sort=q].freeze,
235
236
  no: {
237
+ branch: %w[color color-moved column track].freeze,
238
+ checkout: %w[overwrite-ignore guess overlay progress recurse-submodules track].freeze,
236
239
  fetch: {
237
240
  base: %w[auto-gc auto-maintenance write-commit-graph write-fetch-head].freeze,
238
241
  pull: %w[all ipv4 ipv6 recurse-submodules show-forced-updates tags].freeze
239
242
  },
240
243
  log: {
241
244
  base: %w[decorate mailmap merges use-mailmap].freeze,
242
- show: %w[abbrev-commit expand-tabs notes].freeze,
243
- diff: %w[color color-moved ext-diff indent-heuristic patch relative rename-empty textconv].freeze
245
+ diff: %w[color color-moved ext-diff indent-heuristic patch relative rename-empty textconv].freeze,
246
+ show: %w[abbrev-commit expand-tabs notes].freeze
244
247
  }.freeze,
245
- pull: %w[autostash commit edit ff log signoff squash stat verify verify-signatures gpg-sign rebase].freeze,
246
- tag: %w[column].freeze,
247
- branch: %w[color-moved column color track].freeze,
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
248
+ merge: %w[autostash edit ff gpg-sign log overwrite-ignore progress rerere-autoupdate signoff squash stat
250
249
  verify verify-signatures].freeze,
251
- rebase: %w[autosquash autostash fork-point gpg-sign keep-empty reapply-cherry-picks reschedule-failed-exec
252
- rerere-autoupdate stat update-refs verify].freeze,
250
+ pull: %w[autostash commit edit gpg-sign ff log rebase signoff squash stat verify verify-signatures].freeze,
251
+ rebase: %w[autosquash autostash fork-point gpg-sign keep-empty reapply-cherry-picks rebase-merges
252
+ rerere-autoupdate reschedule-failed-exec stat update-refs verify].freeze,
253
253
  reset: %w[refresh].freeze,
254
254
  restore: %w[overlay progress recurse-submodules].freeze,
255
+ rev_parse: %w[flags].freeze,
255
256
  revert: %w[edit gpg-sign rerere-autoupdate].freeze,
256
- show: %w[standard-notes].freeze
257
+ show: %w[standard-notes].freeze,
258
+ tag: %w[column].freeze
257
259
  }.freeze
258
260
  }.freeze
259
261
  VAL_GIT = {
@@ -272,7 +274,7 @@ module Squared
272
274
  include Rake::DSL
273
275
 
274
276
  def populate(ws, **)
275
- return if ws.series[:pull].empty?
277
+ return if ws.series.exclude?(:pull, true)
276
278
 
277
279
  namespace(name = ws.task_name('git')) do
278
280
  all = ws.task_join(name, 'all')
@@ -308,23 +310,24 @@ module Squared
308
310
  end
309
311
 
310
312
  subtasks({
311
- 'branch' => %i[create set delete move copy list edit current].freeze,
313
+ 'branch' => %i[create track delete move copy list current].freeze,
312
314
  'checkout' => %i[commit branch track detach path].freeze,
313
- 'commit' => %i[add all amend amend-orig].freeze,
315
+ 'commit' => %i[add all amend amend-orig fixup].freeze,
314
316
  'diff' => %i[head branch files view between contain].freeze,
315
317
  'fetch' => %i[origin remote].freeze,
316
- 'files' => %i[cached modified deleted others ignored].freeze,
317
- 'git' => %i[clean mv revert rm].freeze,
318
+ 'files' => %i[cached modified deleted others].freeze,
319
+ 'git' => %i[add clean mv revert rm].freeze,
318
320
  'log' => %i[view between contain].freeze,
319
321
  'merge' => %i[commit no-commit send].freeze,
320
322
  'pull' => %i[origin remote].freeze,
321
323
  'rebase' => %i[branch onto send].freeze,
322
324
  'refs' => %i[heads tags remote].freeze,
323
325
  'reset' => %i[commit index patch mode].freeze,
324
- 'restore' => %i[staged worktree].freeze,
325
- 'rev' => %i[commit branch output parseopt build].freeze,
326
+ 'restore' => %i[source staged worktree].freeze,
327
+ 'rev' => %i[commit build output].freeze,
326
328
  'show' => %i[format oneline textconv].freeze,
327
329
  'stash' => %i[push pop apply drop clear list].freeze,
330
+ 'switch' => %i[create detach merge].freeze,
328
331
  'tag' => %i[add sign delete list].freeze
329
332
  })
330
333
 
@@ -350,7 +353,7 @@ module Squared
350
353
  case action
351
354
  when 'pull', 'fetch'
352
355
  if flag == :remote
353
- format_desc(action, flag, 'remote,opts*')
356
+ format_desc action, flag, 'remote,opts*'
354
357
  task flag, [:remote] do |_, args|
355
358
  remote = param_guard(action, flag, args: args, key: :remote)
356
359
  __send__(action, flag, args.extras, remote: remote)
@@ -366,13 +369,39 @@ module Squared
366
369
  when :all
367
370
  format_desc action, flag, 'message?'
368
371
  task flag, [:message] do |_, args|
369
- commit(flag, message: args.fetch(:message, nil))
372
+ commit(flag, message: args.message)
370
373
  end
371
374
  else
372
- format_desc action, flag, 'pathspec+'
375
+ format_desc(action, flag, 'pathspec+', before: flag == :add ? 'opts*' : nil)
373
376
  task flag do |_, args|
374
- refs = param_guard(action, flag, args: args.to_a)
375
- commit(flag, refs: refs)
377
+ if flag == :fixup
378
+ ref, squash, pick = choice_commit(accept: [['Auto squash?', true]], reflog: false,
379
+ values: ['Pick [amend|reword]'])
380
+ pick = case pick&.downcase
381
+ when 'a', 'amend'
382
+ 'amend'
383
+ when 'r', 'reword'
384
+ 'reword'
385
+ end
386
+ if squash
387
+ found = false
388
+ git_spawn(git_output('log --format=%h'), stdout: false).each do |val|
389
+ if found
390
+ squash = val.chomp
391
+ break
392
+ end
393
+ found = val.chomp == ref
394
+ end
395
+ end
396
+ end
397
+ refs = pick == 'reword' ? [] : param_guard(action, flag, args: args.to_a)
398
+ if flag == :add
399
+ opts = refs
400
+ refs = []
401
+ else
402
+ opts = []
403
+ end
404
+ commit(flag, opts, refs: refs, ref: ref, squash: squash, pick: pick)
376
405
  end
377
406
  end
378
407
  when 'tag'
@@ -383,65 +412,105 @@ module Squared
383
412
  tag flag, args.to_a
384
413
  end
385
414
  when :delete
386
- format_desc action, flag, 'name+?'
415
+ format_desc action, flag, 'name+'
387
416
  task flag do |_, args|
388
- if (refs = args.to_a).empty?
389
- refs = choice_refs('Choose a tag', 'tags', multiple: true, accept: 'Delete?')
417
+ refs = args.to_a
418
+ if refs.empty?
419
+ refs = choice_refs('Choose a tag', 'tags', multiple: true, accept: 'Delete?', series: true)
420
+ remote = choice_remote
390
421
  end
391
- tag(flag, refs: refs)
422
+ tag(flag, refs: refs, remote: remote)
392
423
  end
393
424
  when :add, :sign
394
- format_desc action, flag, 'name,message?,commit?'
395
- task flag, [:name, :message, :commit] do |_, args|
396
- name = param_guard(action, flag, args: args, key: :name)
397
- tag(flag, refs: [name], message: args.message, commit: args.commit)
425
+ format_desc action, flag, 'name,message?,commit?,remote?'
426
+ task flag, [:name, :message, :commit, :remote] do |_, args|
427
+ if (name = args.name)
428
+ message = args.message
429
+ commit = commithead args.commit
430
+ remote = args.remote
431
+ else
432
+ commit, name, message = choice_commit(values: [['Enter tag name', true], 'Enter message'],
433
+ series: true, reflog: false)
434
+ remote = choice_remote
435
+ end
436
+ ret = tag(flag, refs: [name], message: message, commit: commit, remote: remote)
437
+ print_success if success?(ret) && !remote
398
438
  end
399
439
  end
400
440
  when 'stash'
401
- if flag == :list
402
- format_desc action, flag
403
- else
404
- format_desc(action, flag, '*opts', after: flag == :push ? 'pathspec*' : 'commit?')
405
- end
441
+ format_desc(action, flag, 'opts*', after: case flag
442
+ when :push then 'pathspec*'
443
+ when :list then nil
444
+ else 'commit?' end)
406
445
  task flag do |_, args|
407
446
  stash flag, args.to_a
408
447
  end
409
448
  when 'log', 'diff'
410
449
  case flag
411
450
  when :view, :between, :contain
412
- if flag == :view && action == 'log'
413
- format_desc action, flag, '(^)commit/H0*,opts*,pathspec*'
451
+ view = flag == :view
452
+ if view && action == 'log'
453
+ format_desc action, flag, '(^)commit*|:,opts*,ref?,pathspec*'
414
454
  task flag do |_, args|
415
- index = []
416
- args.to_a.each do |val|
417
- if val =~ /^H(\d+)$/
418
- index << "HEAD~#{$1}"
419
- elsif (sha = commithash(val))
420
- index << sha
421
- elsif val.start_with?('^')
422
- index << shell_quote(val)
455
+ args = args.to_a
456
+ if args.first == ':'
457
+ args.shift
458
+ index = choice_commit(multiple: true)
459
+ else
460
+ index = []
461
+ args.each do |val|
462
+ if matchhead(val)
463
+ index << commithead(val)
464
+ elsif (sha = commithash(val))
465
+ index << sha
466
+ elsif val.start_with?('^')
467
+ index << shell_quote(val)
468
+ else
469
+ break
470
+ end
423
471
  end
472
+ args = args.drop(index.size)
424
473
  end
425
- log!(flag, args.to_a.drop(index.size), index: index)
474
+ log!(flag, args, index: index)
426
475
  end
427
476
  else
428
477
  format_desc action, flag, 'commit1,commit2,opts*,pathspec*'
429
478
  task flag, [:commit1, :commit2] do |_, args|
430
- commit1 = param_guard(action, flag, args: args, key: :commit1)
431
- commit2 = param_guard(action, flag, args: args, key: :commit2)
432
- __send__(action == 'log' ? :log! : :diff, flag, args.extras, range: [commit1, commit2])
479
+ commit1 = commithead args.commit1
480
+ if commit1
481
+ commit2 = commithead param_guard(action, flag, args: args, key: :commit2)
482
+ args = args.extras.to_a
483
+ range = [commit1, commit2]
484
+ else
485
+ range, opts, refs = choice_commit(multiple: view ? true : 2, values: %w[Options Pathspec])
486
+ range = range.reverse
487
+ args = OptionPartition.strip(opts)
488
+ args.concat(refs.shellsplit) if refs
489
+ end
490
+ __send__(action == 'log' ? :log! : :diff, flag, args, range: range)
433
491
  end
434
492
  end
435
493
  when :head
436
- format_desc action, flag, 'commit/H0*,opts*,pathspec*'
494
+ format_desc action, flag, 'commit*|:,opts*,pathspec*'
437
495
  task flag do |_, args|
438
- index = []
439
- args.to_a.each do |val|
440
- break unless val =~ /^H(\d+)$/ || (sha = commithash(val))
441
-
442
- index << ($1 ? "HEAD~#{$1}" : sha)
496
+ args = args.to_a
497
+ if args.first == ':'
498
+ args.shift
499
+ index = choice_commit(multiple: true)
500
+ else
501
+ index = []
502
+ args.each do |val|
503
+ if matchhead(val)
504
+ index << commithead(val)
505
+ elsif (sha = commithash(val))
506
+ index << sha
507
+ else
508
+ break
509
+ end
510
+ end
511
+ args = args.drop(index.size)
443
512
  end
444
- diff(flag, args.to_a.drop(index.size), index: index)
513
+ diff(flag, args, index: index)
445
514
  end
446
515
  when :branch
447
516
  format_desc action, flag, 'name,opts*,pathspec*'
@@ -460,7 +529,7 @@ module Squared
460
529
  when 'checkout'
461
530
  case flag
462
531
  when :branch
463
- format_desc action, flag, 'name?,create?=[bB],commit?,detach?=d'
532
+ format_desc action, flag, 'name,create?=[bB],commit?,detach?=d'
464
533
  task flag, [:name, :create, :commit, :detach] do |_, args|
465
534
  if (branch = args.name)
466
535
  branch = param_guard(action, flag, args: args, key: :name)
@@ -473,21 +542,21 @@ module Squared
473
542
  commit = nil
474
543
  detach = 'd'
475
544
  elsif create && create.size > 1
476
- commit = create
545
+ commit = commithead create
477
546
  create = nil
478
547
  detach = args.commit
479
548
  else
480
549
  detach = args.detach
481
- commit = args.commit
550
+ commit = commithead args.commit
482
551
  end
483
552
  param_guard(action, flag, args: { create: create }, key: :create, pat: /\Ab\z/i) if create
484
553
  else
485
- branch = choice_refs 'Choose a branch to switch', 'heads'
554
+ branch = choice_refs 'Choose a branch to switch'
486
555
  end
487
556
  checkout(flag, branch: branch, create: create, commit: commit, detach: detach)
488
557
  end
489
558
  when :track
490
- format_desc action, flag, 'origin?,(^)name?'
559
+ format_desc action, flag, 'origin,(^)name?'
491
560
  task flag, [:origin, :name] do |_, args|
492
561
  if (origin = args.origin)
493
562
  branch = args.name
@@ -497,15 +566,26 @@ module Squared
497
566
  checkout(flag, branch: branch, origin: origin)
498
567
  end
499
568
  when :commit
500
- format_desc action, flag, 'branch/commit,opts*'
569
+ format_desc action, flag, 'ref,opts*'
501
570
  task flag, [:commit] do |_, args|
502
- commit = param_guard(action, flag, args: args, key: :commit)
503
- checkout(flag, commit: commit)
571
+ commit = commithead args.commit
572
+ if commit
573
+ args = args.extras
574
+ else
575
+ commit, opts = choice_commit(values: ['Options'])
576
+ args = OptionPartition.strip(opts)
577
+ end
578
+ checkout(flag, args, commit: commit)
504
579
  end
505
580
  when :detach
506
- format_desc action, flag, 'branch/commit?'
581
+ format_desc action, flag, 'ref?'
507
582
  task flag, [:commit] do |_, args|
508
- checkout(flag, commit: args.commit)
583
+ commit = commithead args.commit
584
+ unless commit
585
+ commit, merge = choice_commit(values: ['Merge? [y|N]'])
586
+ merge = merge&.upcase == 'Y'
587
+ end
588
+ checkout(flag, commit: commit, merge: merge)
509
589
  end
510
590
  when :path
511
591
  format_desc action, flag, 'opts*,pathspec*'
@@ -516,33 +596,46 @@ module Squared
516
596
  when 'branch'
517
597
  case flag
518
598
  when :create
519
- format_desc action, flag, 'name,ref?=HEAD'
599
+ format_desc action, flag, 'name,ref?=HEAD|:'
520
600
  task flag, [:name, :ref] do |_, args|
521
601
  target = param_guard(action, flag, args: args, key: :name)
522
- branch(flag, target: target, ref: args.ref)
602
+ ref = commithead args.ref
603
+ ref, remote = choice_refs('Choose a remote', 'remotes', accept: [['Push?', true]]) if ref == ':'
604
+ branch(flag, target: target, ref: ref, remote: remote)
523
605
  end
524
- when :set
525
- format_desc(action, flag, '(^)upstream?,name?')
606
+ when :track
607
+ format_desc action, flag, '(^~)upstream?,name?'
526
608
  task flag, [:upstream, :name] do |_, args|
527
609
  if (ref = args.upstream)
528
610
  target = args.name
611
+ if ref.start_with?('~')
612
+ ref = ref[1..-1]
613
+ remote = true
614
+ end
529
615
  else
530
- ref, target = choice_refs('Choose a remote', 'remotes', values: ['Enter branch name'])
616
+ ref, remote, target = choice_refs('Choose a remote', 'remotes', accept: [['Push?', true]],
617
+ values: ['Enter branch name'])
531
618
  end
532
- branch(flag, target: target, ref: ref)
619
+ branch(flag, target: target, ref: ref, remote: remote)
533
620
  end
534
621
  when :delete
535
- format_desc action, flag, '(^~)name+?'
622
+ format_desc action, flag, '(^~)name*,:?'
536
623
  task flag do |_, args|
537
- if (refs = args.to_a).empty?
538
- refs = choice_refs('Choose a branch', 'heads', multiple: true, accept: 'Delete?')
624
+ refs = args.to_a
625
+ if refs.empty? || (r = refs.last == ':')
626
+ accept = ['Delete?']
627
+ accept << ['Force?', true] unless r
628
+ remote = choice_refs('Choose a branch', r ? 'remotes' : 'heads', multiple: true,
629
+ accept: accept)
630
+ if r
631
+ refs.pop
632
+ else
633
+ refs = remote.first
634
+ refs.map! { |val| "^#{val}" } if remote[1]
635
+ remote = nil
636
+ end
539
637
  end
540
- branch(flag, refs: refs)
541
- end
542
- when :edit
543
- format_desc action, flag, 'name?'
544
- task flag, [:name] do |_, args|
545
- branch(flag, target: args.name)
638
+ branch(flag, refs: refs, remote: remote)
546
639
  end
547
640
  when :list
548
641
  format_desc action, flag, 'opts*,pattern*'
@@ -557,17 +650,55 @@ module Squared
557
650
  else
558
651
  format_desc action, flag, 'branch,oldbranch?'
559
652
  task flag, [:branch, :oldbranch] do |_, args|
560
- branch = param_guard(action, flag, args: args, key: :branch)
561
- branch(flag, refs: [args.oldbranch, branch])
653
+ if (branch = args.branch)
654
+ oldbranch = args.oldbranch
655
+ else
656
+ oldbranch, branch = choice_refs("Choose a branch to #{flag}",
657
+ values: [['Enter new branch name', true]])
658
+ end
659
+ branch(flag, refs: [oldbranch, branch])
660
+ end
661
+ end
662
+ when 'switch'
663
+ case flag
664
+ when :create
665
+ format_desc action, flag, '(^)name,ref?=HEAD|:'
666
+ task flag, [:name, :commit] do |_, args|
667
+ target = param_guard(action, flag, args: args, key: :name)
668
+ commit = commithead args.commit
669
+ commit, track = choice_commit(values: ['Track? [Y|n]'], force: false) if commit == ':'
670
+ switch(flag, target: target, commit: commit, track: track)
671
+ end
672
+ when :detach
673
+ format_desc action, flag, 'ref?=HEAD'
674
+ task flag, [:commit] do |_, args|
675
+ commit = commithead(args.commit) || choice_commit(force: false)
676
+ switch(flag, commit: commit)
677
+ end
678
+ when :merge
679
+ format_desc action, flag, 'branch?'
680
+ task flag, [:branch] do |_, args|
681
+ commit = args.branch || choice_refs('Choose a branch')
682
+ switch(flag, commit: commit)
562
683
  end
563
684
  end
564
685
  when 'reset'
565
686
  case flag
566
687
  when :commit
567
- format_desc action, flag, 'branch/commit,opts*'
688
+ format_desc action, flag, 'ref|:,opts*'
568
689
  task flag, [:commit] do |_, args|
569
- commit = param_guard(action, flag, args: args, key: :commit)
570
- reset(flag, args.extras, commit: commit)
690
+ commit = commithead args.commit
691
+ if commit && commit != ':'
692
+ args = args.extras
693
+ else
694
+ commit, mode = choice_commit(values: ['Mode [mixed|soft|hard|N]'])
695
+ args = args.extras.to_a.concat(case mode&.downcase
696
+ when 'h', 'hard' then ['hard']
697
+ when 's', 'soft' then ['soft']
698
+ when 'n', 'N' then ['mixed', 'N']
699
+ else ['mixed'] end)
700
+ end
701
+ print_success if success?(reset(flag, args, commit: commit))
571
702
  end
572
703
  when :index
573
704
  format_desc action, flag, 'opts*,pathspec*'
@@ -575,15 +706,18 @@ module Squared
575
706
  reset flag, args.to_a
576
707
  end
577
708
  when :mode
578
- format_desc action, flag, 'mode,ref?=HEAD'
709
+ format_desc action, flag, 'mode,ref?=HEAD|:'
579
710
  task flag, [:mode, :ref] do |_, args|
580
711
  mode = param_guard(action, flag, args: args, key: :mode)
581
- reset(flag, mode: mode, ref: args.ref)
712
+ ref = commithead args.ref
713
+ ref = choice_commit(reflog: false) if ref == ':'
714
+ reset(flag, mode: mode, ref: ref)
582
715
  end
583
716
  when :patch
584
- format_desc action, flag, 'ref,pathspec*'
717
+ format_desc action, flag, 'ref?=HEAD|:,pathspec*'
585
718
  task flag, [:ref] do |_, args|
586
- ref = param_guard(action, flag, args: args, key: :ref)
719
+ ref = commithead args.ref
720
+ ref = choice_commit(reflog: false) unless ref && ref != ':'
587
721
  reset(flag, refs: args.extras, ref: ref)
588
722
  end
589
723
  end
@@ -592,7 +726,7 @@ module Squared
592
726
  when :oneline
593
727
  format_desc action, flag, 'opts*,object*'
594
728
  task flag do |_, args|
595
- show flag, args.to_a.push('abbrev-commit')
729
+ show flag, args.to_a
596
730
  end
597
731
  when :format
598
732
  format_desc action, flag, 'format?,opts*,object*'
@@ -609,23 +743,42 @@ module Squared
609
743
  when 'rebase', 'merge'
610
744
  case flag
611
745
  when :branch
612
- format_desc action, flag, 'opts*,upstream?,branch?'
613
- task flag do |_, args|
614
- args = param_guard(action, flag, args: args.to_a)
615
- rebase flag, args
746
+ format_desc action, flag, 'upstream,branch?=HEAD,opts*'
747
+ task flag, [:upstream] do |_, args|
748
+ if (upstream = args.upstream)
749
+ args = args.extras
750
+ else
751
+ upstream, opts = choice_refs('Choose upstream branch', values: ['Options'])
752
+ args = OptionPartition.strip(opts)
753
+ end
754
+ rebase(flag, args, upstream: upstream)
616
755
  end
617
756
  when :onto
618
- format_desc action, flag, 'branch/commit,upstream,branch?=HEAD'
757
+ format_desc action, flag, 'ref,upstream,branch?=HEAD'
619
758
  task flag, [:commit, :upstream, :branch] do |_, args|
620
- commit = param_guard(action, flag, args: args, key: :commit)
621
- upstream = param_guard(action, flag, args: args, key: :upstream)
622
- rebase(flag, commit: commit, upstream: upstream, branch: args.branch)
759
+ commit = commithead args.commit
760
+ if commit
761
+ upstream = param_guard(action, flag, args: args, key: :upstream)
762
+ branch = args.branch
763
+ args = []
764
+ else
765
+ commit = choice_refs 'Choose "onto" branch'
766
+ target, opts = choice_commit(multiple: 2, values: ['Options'], reflog: false)
767
+ branch, upstream = target
768
+ args = OptionPartition.strip(opts)
769
+ end
770
+ rebase(flag, args, commit: commit, upstream: upstream, branch: branch)
623
771
  end
624
772
  when :commit, :'no-commit'
625
- format_desc action, flag, 'branch/commit+,opts*'
773
+ format_desc action, flag, 'refs+,opts*'
626
774
  task flag do |_, args|
627
- args = param_guard(action, flag, args: args.to_a)
628
- merge flag, args
775
+ args = args.to_a
776
+ if args.empty?
777
+ accept = "Merge with #{`#{git_output('branch --show-current')}`.chomp}?"
778
+ branch, opts = choice_refs('Choose a branch', values: ['Options'], accept: accept)
779
+ args = OptionPartition.strip(opts)
780
+ end
781
+ merge(flag, args, branch: branch)
629
782
  end
630
783
  when :send
631
784
  format_desc(action, flag, VAL_GIT[action.to_sym][:send], arg: nil)
@@ -640,25 +793,20 @@ module Squared
640
793
  when :commit
641
794
  format_desc action, flag, 'ref?=HEAD,size?'
642
795
  task flag, [:ref, :size] do |_, args|
643
- ref = args.ref
796
+ ref = commithead args.ref
644
797
  size = args.size
645
- if !size && ref.to_i > 0
798
+ if !size && ref.to_i.between?(1, 40)
646
799
  size = ref
647
800
  ref = nil
648
801
  end
649
802
  rev_parse(flag, ref: ref, size: size)
650
803
  end
651
- when :branch
652
- format_desc action, flag, 'ref?=HEAD'
653
- task flag, [:ref] do |_, args|
654
- rev_parse(flag, ref: args.ref)
655
- end
656
804
  when :build
657
805
  format_desc action, flag, OPT_GIT[:status]
658
806
  task flag do |_, args|
659
807
  revbuild flag, args.to_a
660
808
  end
661
- else
809
+ when :output
662
810
  format_desc action, flag, 'opts*,args*'
663
811
  task flag do |_, args|
664
812
  rev_parse flag, args.to_a
@@ -666,10 +814,9 @@ module Squared
666
814
  end
667
815
  when 'refs', 'files'
668
816
  if flag == :remote
669
- format_desc action, flag, 'remote,opts*,pattern*'
817
+ format_desc action, flag, 'remote?,opts*,pattern*'
670
818
  task flag, [:remote] do |_, args|
671
- remote = param_guard(action, flag, args: args, key: :remote)
672
- ls_remote(flag, args.extras, remote: remote)
819
+ ls_remote(flag, args.extras, remote: args.remote)
673
820
  end
674
821
  else
675
822
  format_desc action, flag, 'opts*,pattern*'
@@ -677,17 +824,57 @@ module Squared
677
824
  __send__(action == 'refs' ? :ls_remote : :ls_files, flag, args.to_a)
678
825
  end
679
826
  end
680
- when 'restore', 'git'
681
- format_desc(action, flag, 'opts*', before: case flag
682
- when :rm
683
- 'source+,destination'
684
- when :revert
685
- 'commit+'
686
- else
687
- 'pathspec*'
688
- end)
827
+ when 'restore'
828
+ case flag
829
+ when :source
830
+ format_desc action, flag, 'ref,opts*,pathspec*'
831
+ task flag, [:commit] do |_, args|
832
+ commit = commithead args.commit
833
+ if commit
834
+ args = args.extras
835
+ else
836
+ commit, opts, files = choice_commit(values: ['Options', ['Pathspec', true]])
837
+ args = OptionPartition.strip(opts)
838
+ files = files&.shellsplit
839
+ end
840
+ restore(flag, args, commit: commit, files: files)
841
+ end
842
+ when :staged, :worktree
843
+ format_desc action, flag, 'opts*,pathspec*,:?'
844
+ task flag do |_, args|
845
+ args = args.to_a
846
+ if args.empty? || args.last == ':'
847
+ files = []
848
+ git_spawn('status -s --porcelain', stdout: false).each do |line|
849
+ next unless line =~ /^(.)(.)\s+(.+)$/
850
+
851
+ case (flag == :staged ? $1 : $2)
852
+ when 'A', 'M'
853
+ files << $3
854
+ end
855
+ end
856
+ unless files.empty?
857
+ files = choice_index('Select a file', files, multiple: true, force: false,
858
+ accept: 'Restore?')
859
+ end
860
+ args.pop
861
+ args, glob = args.partition { |val| val.match?(/^(?:[a-z-]+=|[^*]+$)/) }
862
+ (files ||= []).concat(glob)
863
+ next if args.empty? && files.empty?
864
+ end
865
+ restore(flag, args, files: files)
866
+ end
867
+ end
868
+ when 'git'
869
+ before = case flag
870
+ when :rm then 'source+,destination'
871
+ when :revert then 'commit+' end
872
+ format_desc(action, flag, 'opts*', before: before, after: case flag
873
+ when :add, :clean, :mv
874
+ 'pathspec*'
875
+ end)
689
876
  task flag do |_, args|
690
- __send__(action, flag, args.to_a)
877
+ git flag, args.to_a
691
878
  end
692
879
  end
693
880
  end
@@ -744,16 +931,12 @@ module Squared
744
931
  cmd, opts = git_session('rebase', opts: opts)
745
932
  case flag
746
933
  when :branch
934
+ return unless upstream
935
+
747
936
  op = OptionPartition.new(opts, OPT_GIT[:rebase], cmd, project: self, no: OPT_GIT[:no][:rebase])
748
- case op.size
749
- when 0
750
- append_head
751
- when 1, 2
752
- op.append(delim: true)
753
- else
754
- op.append(op.pop(2), delim: true)
755
- .clear(pass: false)
756
- end
937
+ cmd << shell_escape(upstream)
938
+ append_head op.shift
939
+ op.clear(pass: false)
757
940
  when :onto
758
941
  return unless upstream
759
942
 
@@ -775,8 +958,8 @@ module Squared
775
958
 
776
959
  def fetch(flag = nil, opts = [], sync: invoked_sync?('fetch', flag), remote: nil)
777
960
  cmd, opts = git_session('fetch', opts: opts)
778
- append_pull(opts, collect_hash(OPT_GIT[:fetch]),
779
- no: collect_hash(OPT_GIT[:no][:fetch]), remote: remote, flag: flag)
961
+ append_pull(opts, collect_hash(OPT_GIT[:fetch]), no: collect_hash(OPT_GIT[:no][:fetch]), remote: remote,
962
+ flag: flag)
780
963
  cmd << '--all' if !remote && !session_arg?('multiple') && option('all')
781
964
  cmd << '--verbose' if verbose && !session_arg?('quiet')
782
965
  source(sync: sync, **threadargs)
@@ -807,8 +990,12 @@ module Squared
807
990
  def stash(flag = nil, opts = [], sync: invoked_sync?('stash', flag))
808
991
  if flag
809
992
  cmd, opts = git_session('stash', flag, opts: opts)
810
- op = OptionPartition.new(opts, OPT_GIT[:stash][:common] + OPT_GIT[:stash].fetch(flag, []), cmd,
811
- project: self)
993
+ list = OPT_GIT[:stash][:common] + OPT_GIT[:stash].fetch(flag, [])
994
+ if flag == :list
995
+ list += collect_hash(OPT_GIT[:log])
996
+ no = collect_hash(OPT_GIT[:no][:log])
997
+ end
998
+ op = OptionPartition.new(opts, list, cmd, project: self, no: no, first: flag == :push ? matchpathspec : nil)
812
999
  case flag
813
1000
  when :push
814
1001
  append_pathspec op.extras
@@ -823,6 +1010,7 @@ module Squared
823
1010
  end
824
1011
  return
825
1012
  when :list
1013
+ op.clear
826
1014
  out, banner, from = source(io: true)
827
1015
  print_item banner
828
1016
  list_result(write_lines(out), 'objects', from: from)
@@ -830,8 +1018,8 @@ module Squared
830
1018
  end
831
1019
  else
832
1020
  git_session('stash', 'push', opts: opts)
833
- append_option(%w[all keep-index include-untracked staged].freeze, no: true, ignore: false)
834
- append_message option('message', 'm', ignore: false)
1021
+ append_option(OPT_GIT[:stash][:push].grep_v(/[=|]/), no: true, ignore: false)
1022
+ append_message
835
1023
  end
836
1024
  source(banner: !quiet?, sync: sync, **threadargs)
837
1025
  end
@@ -839,6 +1027,7 @@ module Squared
839
1027
  def status(*)
840
1028
  cmd = git_session 'status'
841
1029
  cmd << (option('long') ? '--long' : '--short')
1030
+ cmd << '--branch' if option('branch')
842
1031
  if (val = option('ignore-submodules', ignore: false))
843
1032
  cmd << basic_option('ignore-submodules', case val
844
1033
  when '0', 'none'
@@ -852,18 +1041,22 @@ module Squared
852
1041
  end)
853
1042
  end
854
1043
  append_pathspec
1044
+ if verbose
1045
+ r = color(:red)
1046
+ g = color(:green)
1047
+ sub = if session_arg?('short')
1048
+ [
1049
+ { pat: /^(.)([A-Z?!])(.+)$/, styles: r, index: 2 },
1050
+ { pat: /^([A-Z?!])(.+)$/, styles: g },
1051
+ { pat: /^(\?\?)(.+)$/, styles: r },
1052
+ { pat: /^(## )(.+?)(\.{3})(.+)$/, styles: [nil, g, nil, r], index: -1 }
1053
+ ]
1054
+ else
1055
+ [{ pat: /^(\t+)([a-z]+: +.+)$/, styles: r, index: 2 }]
1056
+ end
1057
+ end
855
1058
  out, banner, from = source(io: true)
856
- ret = write_lines(out, banner: banner, sub: if verbose
857
- r = color(:red)
858
- g = color(:green)
859
- [
860
- { pat: /^(.)([A-Z?!])(.+)$/, styles: r, index: 2 },
861
- { pat: /^([A-Z?!])(.+)$/, styles: g },
862
- { pat: /^(\?\?)(.+)$/, styles: r },
863
- { pat: /^(## )(.+)(\.{3})(.+)$/,
864
- styles: [nil, g, nil, r], index: -1 }
865
- ]
866
- end)
1059
+ ret = write_lines(out, banner: banner, sub: sub)
867
1060
  list_result(ret, 'files', from: from, action: 'modified')
868
1061
  end
869
1062
 
@@ -927,13 +1120,14 @@ module Squared
927
1120
  case flag
928
1121
  when :commit, :index
929
1122
  op = OptionPartition.new(opts, OPT_GIT[:reset] + VAL_GIT[:reset], cmd,
930
- project: self, no: OPT_GIT[:no][:reset])
1123
+ project: self, no: OPT_GIT[:no][:reset],
1124
+ first: flag == :index ? matchpathspec : nil)
931
1125
  if flag == :commit
932
- op.append(commit, delim: true)
1126
+ op.append(commit)
933
1127
  .clear(pass: false)
934
1128
  ref = false
935
1129
  else
936
- (refs ||= []).concat(op.extras)
1130
+ refs = op.extras
937
1131
  end
938
1132
  when :mode
939
1133
  return unless VAL_GIT[:reset].include?(mode)
@@ -945,6 +1139,7 @@ module Squared
945
1139
  end
946
1140
  when :patch
947
1141
  cmd << '--patch'
1142
+ append_pathspec(refs, pass: false)
948
1143
  end
949
1144
  unless ref == false
950
1145
  append_commit(ref, head: true)
@@ -953,7 +1148,7 @@ module Squared
953
1148
  source
954
1149
  end
955
1150
 
956
- def checkout(flag, opts = [], branch: nil, origin: nil, create: nil, commit: nil, detach: nil)
1151
+ def checkout(flag, opts = [], branch: nil, origin: nil, create: nil, commit: nil, detach: nil, merge: false)
957
1152
  cmd, opts = git_session('checkout', opts: opts)
958
1153
  append_option 'force', 'merge'
959
1154
  case flag
@@ -971,21 +1166,23 @@ module Squared
971
1166
  end
972
1167
  cmd << '--track' << shell_quote(origin)
973
1168
  when :detach
1169
+ cmd << '-m' if merge
974
1170
  cmd << '--detach' << commit
975
1171
  else
976
- op = OptionPartition.new(opts, OPT_GIT[:checkout], cmd, project: self, no: OPT_GIT[:no][:checkout])
1172
+ op = OptionPartition.new(opts, OPT_GIT[:checkout], cmd, project: self, no: OPT_GIT[:no][:checkout],
1173
+ first: flag == :path ? matchpathspec : nil)
977
1174
  if flag == :commit
978
- op.append(commit, delim: true)
1175
+ op.append(commit)
979
1176
  .clear(pass: false)
980
1177
  else
981
1178
  append_head
982
- append_pathspec op.extras
1179
+ append_pathspec(op.extras, pass: false)
983
1180
  end
984
1181
  end
985
1182
  source
986
1183
  end
987
1184
 
988
- def tag(flag, opts = [], refs: [], message: nil, commit: nil)
1185
+ def tag(flag, opts = [], refs: [], message: nil, commit: nil, remote: nil)
989
1186
  cmd, opts = git_session('tag', opts: opts)
990
1187
  case flag
991
1188
  when :add, :sign
@@ -997,31 +1194,32 @@ module Squared
997
1194
  cmd << '--force' if option('force')
998
1195
  if !commit && message && (sha = commithash(message))
999
1196
  commit = sha
1000
- else
1001
- append_message message
1197
+ message = nil
1002
1198
  end
1199
+ append_message message
1003
1200
  append_value refs
1004
1201
  append_head commit
1005
- when :list
1202
+ when :delete
1203
+ cmd << '--delete'
1204
+ append_value refs
1205
+ else
1006
1206
  op = OptionPartition.new(opts, OPT_GIT[:tag], cmd << '--list', project: self, no: OPT_GIT[:no][:tag])
1007
1207
  out, banner, from = source(io: true)
1008
1208
  print_item banner
1009
1209
  ret = write_lines(out, grep: op.extras)
1010
1210
  list_result(ret, 'tags', from: from, grep: op.extras)
1011
1211
  return
1012
- when :delete
1013
- cmd << '--delete'
1014
- append_value refs
1015
- else
1016
- cmd << shell_option(flag, commit)
1017
1212
  end
1213
+ remote ||= option('remote')
1018
1214
  source
1215
+ git_spawn('push', flag == :delete ? '-d' : nil, remote, *refs.map { |val| shell_quote(val) }) if remote
1019
1216
  end
1020
1217
 
1021
1218
  def log!(flag, opts = [], range: [], index: [])
1022
1219
  cmd, opts = git_session('log', opts: opts)
1023
1220
  op = OptionPartition.new(opts, collect_hash(OPT_GIT[:log]), cmd,
1024
- project: self, no: collect_hash(OPT_GIT[:no][:log]))
1221
+ project: self, no: collect_hash(OPT_GIT[:no][:log]),
1222
+ first: matchpathspec)
1025
1223
  case flag
1026
1224
  when :between, :contain
1027
1225
  op << shell_quote(range.join(flag == :between ? '..' : '...'))
@@ -1036,7 +1234,8 @@ module Squared
1036
1234
  def diff(flag, opts = [], refs: [], branch: nil, range: [], index: [])
1037
1235
  cmd, opts = git_session('diff', opts: opts)
1038
1236
  op = OptionPartition.new(opts, collect_hash(OPT_GIT[:diff]) + OPT_GIT[:log][:diff], cmd,
1039
- project: self, no: OPT_GIT[:no][:log][:diff])
1237
+ project: self, no: OPT_GIT[:no][:log][:diff],
1238
+ first: flag == :files ? nil : matchpathspec)
1040
1239
  case flag
1041
1240
  when :files, :view, :between, :contain
1042
1241
  op.delete('--cached')
@@ -1049,7 +1248,7 @@ module Squared
1049
1248
  case flag
1050
1249
  when :view
1051
1250
  op << '--merge-base' if option('merge-base')
1052
- op << shell_quote(range.first, quote: true) << shell_quote(range.last, quote: true)
1251
+ op.merge(range)
1053
1252
  when :between, :contain
1054
1253
  op.delete('--merge-base')
1055
1254
  op << shell_quote(range.join(flag == :between ? '..' : '...'))
@@ -1072,82 +1271,99 @@ module Squared
1072
1271
  source(exception: op.arg?('exit-code'))
1073
1272
  end
1074
1273
 
1075
- def commit(flag, *, refs: [], message: nil, pass: false)
1076
- message ||= option('message', 'm', prefix: 'git', ignore: false)
1077
- amend = flag.to_s.start_with?('amend')
1274
+ def commit(flag, opts = [], refs: [], ref: nil, squash: nil, pick: nil, message: nil, pass: false)
1275
+ fixup = flag == :fixup
1276
+ amend = !fixup && flag.to_s.start_with?('amend')
1277
+ unless flag == :add || pick == 'reword'
1278
+ pathspec = if flag == :all || ((fixup || amend) && refs.size == 1 && refs.first == '*')
1279
+ '--all'
1280
+ elsif (refs = projectmap(refs)).empty?
1281
+ raise_error 'no qualified pathspec'
1282
+ else
1283
+ "-- #{refs.join(' ')}"
1284
+ end
1285
+ end
1286
+ if fixup
1287
+ source git_session('commit', basic_option('fixup', "#{pick ? "#{pick}:" : ''}#{ref}"), pathspec)
1288
+ source git_output('rebase --autosquash', squash) if squash.is_a?(String)
1289
+ return
1290
+ end
1291
+ message ||= messageopt
1078
1292
  if !message && !amend
1079
1293
  return if pass
1080
1294
 
1081
- raise_error('missing message', hint: 'GIT_MESSAGE="description"')
1295
+ message = readline('Enter message', force: true)
1082
1296
  end
1083
- pathspec = if flag == :all || (amend && refs.size == 1 && refs.first == '*')
1084
- '--all'
1085
- elsif (refs = projectmap(refs)).empty?
1086
- raise_error 'no qualified pathspec'
1087
- else
1088
- "-- #{refs.join(' ')}"
1089
- end
1090
- origin = nil
1091
1297
  branch = nil
1298
+ origin = nil
1092
1299
  upstream = nil
1300
+ cmd, opts = git_session('add', opts: opts)
1301
+ op = OptionPartition.new(opts, OPT_GIT[:add], cmd, project: self, first: matchpathspec)
1302
+ op << '--verbose' if verbose
1303
+ dryrun = dryrun?
1304
+ format = '%(if)%(HEAD)%(then)%(refname:short)...%(upstream:short)...%(upstream:track)%(end)'
1093
1305
  git_spawn 'fetch --no-tags --quiet'
1094
- git_spawn('branch -vv --list', stdout: false).each do |val|
1095
- next unless (r = /^\*\s(\S+)\s+(\h+)(?:\s\[(.+?)(?=\]\s)\])?\s/.match(val))
1096
-
1097
- branch = r[1]
1098
- if r[3]
1099
- origin = r[3][%r{^(.+)/#{Regexp.escape(branch)}$}, 1]
1100
- else
1101
- unless (origin = option('repository', prefix: 'git', ignore: false))
1102
- out = git_spawn 'log -n1 --format=%h%d'
1103
- if out =~ /^#{r[2]} \(HEAD -> #{Regexp.escape(branch)}, (.+?)\)$/
1104
- split_escape($1).each do |s|
1105
- next unless s.end_with?("/#{branch}")
1106
-
1107
- origin = s[0, s.size - branch.size - 1]
1108
- break
1109
- end
1306
+ foreachref('heads', format: format).each do |line|
1307
+ next if (line = line.chomp).empty?
1308
+
1309
+ branch, origin, hint = line.split('...')
1310
+ if hint && !hint.match?(/^\[(\D+0,\D+0)\]$/)
1311
+ raise_error('work tree is not usable', hint: hint[1..-2])
1312
+ elsif origin.empty?
1313
+ return nil if pass
1314
+ break if dryrun
1315
+
1316
+ unless (origin = option('upstream', prefix: 'git', ignore: false))
1317
+ if (origin = choice_refs('Choose an upstream', 'remotes', attempts: 1, force: false))
1318
+ git_spawn 'branch', quote_option('set-upstream-to', origin)
1319
+ else
1320
+ origin = readline('Enter an upstream', force: true)
1110
1321
  end
1111
1322
  end
1112
- upstream = true if origin
1323
+ raise_error('missing remote name', hint: origin) unless origin.include?('/')
1324
+ upstream = true
1113
1325
  end
1114
1326
  break
1115
1327
  end
1116
- raise_error 'work tree is not usable' unless origin && branch
1117
- cmd = git_session('commit', option('dry-run') && '--dry-run', options: false)
1118
- if amend
1119
- cmd << '--amend'
1328
+ if pathspec
1329
+ op << pathspec
1120
1330
  else
1121
- cmd.delete('--amend')
1331
+ append_pathspec op.extras
1122
1332
  end
1333
+ co = git_session('commit', options: false)
1334
+ pu = git_output 'push', upstream && '--set-upstream'
1335
+ if dryrun
1336
+ op.delete('--dry-run')
1337
+ co << '--dry-run'
1338
+ pu << '--dry-run'
1339
+ end
1340
+ co << '--amend' if amend
1123
1341
  if message
1124
1342
  append_message message
1125
1343
  elsif flag == :'amend-orig' || option('edit', equals: '0')
1126
- cmd << '--no-edit'
1127
- end
1128
- a = git_output 'add', '--verbose'
1129
- b = git_output 'push', upstream && '--set-upstream'
1130
- if dryrun?
1131
- a << '--dry-run'
1132
- b << '--dry-run'
1344
+ co << '--no-edit'
1133
1345
  end
1134
- a << pathspec
1135
- b << '--force-with-lease' if amend
1136
- b << origin << branch
1346
+ pu << '--force-with-lease' if amend
1347
+ pu.merge(repotrack(origin, branch))
1137
1348
  puts if pass
1138
- source a
1139
- source cmd
1140
- source b
1349
+ source op
1350
+ source co
1351
+ source pu
1141
1352
  end
1142
1353
 
1143
- def merge(flag, opts = [], command: nil)
1354
+ def merge(flag, opts = [], command: nil, branch: nil)
1144
1355
  cmd, opts = git_session('merge', opts: opts)
1145
1356
  case flag
1146
1357
  when :commit, :'no-commit'
1147
1358
  op = OptionPartition.new(opts, OPT_GIT[:merge], cmd, project: self, no: OPT_GIT[:no][:merge])
1148
1359
  raise_error 'no branch/commit' if op.empty?
1149
1360
  op << "--#{flag}" << '--'
1150
- append_commit(*op.extras)
1361
+ if branch
1362
+ op << branch
1363
+ op.clear(pass: false)
1364
+ else
1365
+ append_commit(*op.extras)
1366
+ end
1151
1367
  else
1152
1368
  return unless VAL_GIT[:merge][:send].include?(command)
1153
1369
 
@@ -1156,7 +1372,7 @@ module Squared
1156
1372
  source
1157
1373
  end
1158
1374
 
1159
- def branch(flag = nil, opts = [], refs: [], ref: nil, target: nil)
1375
+ def branch(flag = nil, opts = [], refs: [], ref: nil, target: nil, remote: nil)
1160
1376
  cmd, opts = git_session('branch', opts: opts)
1161
1377
  stdout = false
1162
1378
  case flag
@@ -1172,43 +1388,46 @@ module Squared
1172
1388
  end
1173
1389
  end
1174
1390
  cmd << '--force' if option('force')
1175
- when :set
1176
- return unless ref
1177
-
1391
+ cmd << shell_quote(target)
1392
+ cmd << shell_quote(ref) if ref
1393
+ when :track
1394
+ raise_error('invalid upstream', hint: ref) unless ref.include?('/')
1178
1395
  if ref.start_with?('^')
1179
- cmd << '--unset-upstream' << shell_escape(ref[1..-1])
1180
- target = nil
1396
+ cmd << '--unset-upstream' << shell_quote(ref[1..-1])
1397
+ remote = false
1181
1398
  stdout = true
1182
1399
  else
1183
1400
  cmd << quote_option('set-upstream-to', ref)
1401
+ cmd << shell_quote(target) if target
1184
1402
  end
1185
- ref = nil
1186
1403
  when :delete
1404
+ remote&.each do |val|
1405
+ source git_output('push', '--delete', *val.split('/', 2).map { |s| shell_quote(s) })
1406
+ end
1187
1407
  force, list = refs.partition { |val| val.match?(/^[~^]/) }
1188
1408
  force.each do |val|
1189
- dr = val[0, 3]
1409
+ dr = val[0, 2]
1190
1410
  d = dr.include?('^') ? '-D' : '-d'
1191
1411
  r = '-r' if dr.include?('~')
1192
- source git_output('branch', d, r, shell_quote(val.sub(/^[\^~]+/, '')))
1412
+ source git_output('branch', d, r, shell_quote(val.sub(/^[~^]+/, '')))
1193
1413
  end
1194
1414
  return if list.empty?
1195
1415
 
1196
1416
  cmd << '-d'
1197
1417
  append_value list
1418
+ remote = nil
1198
1419
  when :move, :copy
1199
1420
  flag = "-#{flag.to_s[0]}"
1200
1421
  cmd << (option('force') ? flag.upcase : flag)
1201
1422
  refs.compact.each { |val| cmd << shell_quote(val) }
1202
1423
  stdout = true
1203
- when :edit
1204
- cmd << '--edit-description'
1205
1424
  when :current
1206
1425
  cmd << '--show-current'
1207
1426
  source(banner: verbosetype > 1, stdout: true)
1208
1427
  return
1209
1428
  when :list
1210
1429
  op = OptionPartition.new(opts, OPT_GIT[:branch], cmd << '--list',
1211
- project: self, no: OPT_GIT[:no][:branch], single: /^v+$/)
1430
+ project: self, no: OPT_GIT[:no][:branch], single: /\Av+\z/)
1212
1431
  op.each { |val| op << shell_quote(val) }
1213
1432
  out, banner, from = source(io: true)
1214
1433
  print_item banner
@@ -1250,15 +1469,46 @@ module Squared
1250
1469
  end
1251
1470
  return
1252
1471
  end
1253
- cmd << shell_escape(target) if target
1254
- cmd << shell_escape(ref) if ref
1255
- source(stdout: stdout)
1472
+ return unless success?(source(stdout: stdout))
1473
+
1474
+ if !ref
1475
+ print_success if flag == :create
1476
+ elsif remote && target
1477
+ source git_output('push', '-u', shell_quote(ref.split('/', 2).first), shell_quote(target))
1478
+ end
1256
1479
  end
1257
1480
 
1258
- def restore(flag, opts = [])
1259
- cmd, opts = git_session('restore', "--#{flag}", opts: opts)
1260
- op = OptionPartition.new(opts, OPT_GIT[:restore], cmd, project: self, no: OPT_GIT[:no][:restore])
1261
- append_pathspec(op.extras, pass: false)
1481
+ def switch(flag, opts = [], target: nil, commit: nil, track: nil)
1482
+ cmd = git_session('switch', opts: opts).first
1483
+ case flag
1484
+ when :create
1485
+ c = 'c'
1486
+ if target.start_with?('^')
1487
+ target = target[1..-1]
1488
+ c = c.upcase
1489
+ end
1490
+ cmd << quote_option(c, target)
1491
+ cmd << case (track ||= option('track', ignore: false))
1492
+ when 'n', 'N', '0', 'false'
1493
+ '--no-track'
1494
+ when 'y', 'Y', '1', 'true'
1495
+ '--track'
1496
+ when 'direct', 'inherit'
1497
+ basic_option('track', track)
1498
+ end
1499
+ when :detach, :merge
1500
+ cmd << "--#{flag}"
1501
+ end
1502
+ cmd << '--force' if option('force')
1503
+ append_head commit
1504
+ source
1505
+ end
1506
+
1507
+ def restore(flag, opts = [], commit: nil, files: nil)
1508
+ cmd, opts = git_session('restore', shell_option(flag, commit, escape: false, force: false), opts: opts)
1509
+ op = OptionPartition.new(opts, OPT_GIT[:restore], cmd, project: self, no: OPT_GIT[:no][:restore],
1510
+ first: matchpathspec)
1511
+ append_pathspec(op.extras + (files || []), pass: false)
1262
1512
  source(sync: false, stderr: true)
1263
1513
  end
1264
1514
 
@@ -1267,14 +1517,13 @@ module Squared
1267
1517
  case flag
1268
1518
  when :textconv
1269
1519
  cmd << '--textconv'
1270
- append_value(files.map { |val| Dir[val] }
1271
- .flatten
1520
+ append_value(files.flat_map { |val| Dir[val] }
1272
1521
  .select { |val| projectpath?(val) }
1273
1522
  .map! { |val| shell_quote("HEAD:#{val}") })
1274
1523
  source(banner: false)
1275
1524
  return
1276
1525
  when :oneline
1277
- format = flag.to_s
1526
+ format = 'oneline'
1278
1527
  end
1279
1528
  if format
1280
1529
  case (val = format.downcase)
@@ -1291,21 +1540,12 @@ module Squared
1291
1540
  op = OptionPartition.new(opts, OPT_GIT[:show] + OPT_GIT[:diff][:show] + OPT_GIT[:log][:diff], cmd,
1292
1541
  project: self,
1293
1542
  no: OPT_GIT[:no][:show] + collect_hash(OPT_GIT[:no][:log], pass: [:base]))
1294
- unless val == 'oneline' && op.arg?('abbrev-commit')
1295
- op << basic_option('abbrev', val) if (val = option('abbrev')) && val.to_i > 0
1296
- banner = true
1297
- end
1298
1543
  op.append(delim: true)
1299
- source(exception: false, banner: banner)
1544
+ source(exception: false, banner: flag != :oneline)
1300
1545
  end
1301
1546
 
1302
1547
  def rev_parse(flag, opts = [], ref: nil, size: nil)
1303
1548
  cmd, opts = git_session('rev-parse', opts: opts)
1304
- cmd << if flag == :parseopt
1305
- '--parseopt'
1306
- elsif opts.delete('sq-quote')
1307
- '--sq-quote'
1308
- end
1309
1549
  case flag
1310
1550
  when :commit
1311
1551
  cmd << ((n = size.to_i) > 0 ? basic_option('short', [n, 5].max) : '--verify')
@@ -1313,9 +1553,14 @@ module Squared
1313
1553
  when :branch
1314
1554
  cmd << '--abbrev-ref'
1315
1555
  append_commit(ref, head: true)
1316
- else
1317
- op = OptionPartition.new(opts, OPT_GIT[:rev_parse][flag], cmd, project: self)
1318
- op.append(escape: op.arg?('sq-quote'))
1556
+ when :output
1557
+ if opts.delete('sq-quote')
1558
+ cmd << '--sq-quote'
1559
+ args = true
1560
+ end
1561
+ op = OptionPartition.new(opts, OPT_GIT[:rev_parse], cmd, project: self, no: OPT_GIT[:no][:rev_parse],
1562
+ args: args)
1563
+ op.append(escape: args)
1319
1564
  end
1320
1565
  source(banner: verbosetype > 1)
1321
1566
  end
@@ -1342,18 +1587,10 @@ module Squared
1342
1587
 
1343
1588
  def git(flag, opts = [])
1344
1589
  cmd, opts = git_session(flag, opts: opts)
1345
- op = OptionPartition.new(opts, OPT_GIT[flag], cmd, project: self, no: OPT_GIT[:no][flag])
1346
- sync = false
1347
- stderr = true
1590
+ list = OPT_GIT[:git].fetch(flag, []) + OPT_GIT.fetch(flag, [])
1591
+ op = OptionPartition.new(opts, list, cmd, project: self, no: OPT_GIT[:no][flag],
1592
+ first: flag == :revert ? nil : matchpathspec)
1348
1593
  case flag
1349
- when :clean
1350
- refs = projectmap(op.extras)
1351
- unless refs.empty?
1352
- op << '--'
1353
- op.merge(refs)
1354
- end
1355
- sync = true
1356
- stderr = false
1357
1594
  when :revert
1358
1595
  if VAL_GIT[:rebase][:send].any? { |val| op.arg?(val) }
1359
1596
  op.clear
@@ -1362,14 +1599,29 @@ module Squared
1362
1599
  else
1363
1600
  append_commit(*op.extras)
1364
1601
  end
1602
+ when :add, :clean
1603
+ if flag == :add && op.empty? && !op.arg?('pathspec-from-file')
1604
+ files = []
1605
+ red = color(:red)
1606
+ git_spawn('status -s --porcelain -uall', stdout: false).each do |line|
1607
+ next unless line =~ /^.(.) (.+)$/
1608
+
1609
+ files << "#{sub_style($1, styles: red)} #{$2}"
1610
+ end
1611
+ files = choice_index('Select files', files, multiple: true, force: true, trim: /^\S+\s/, accept: 'Add?')
1612
+ end
1613
+ append_pathspec(files || op.extras)
1614
+ verbose = flag == :add && !op.arg?('verbose')
1615
+ print_success if success?(source) && verbose
1616
+ return
1365
1617
  when :mv
1366
- refs = projectmap(op.extras)
1618
+ refs = projectmap op.extras
1367
1619
  raise_error 'no source/destination' unless refs.size > 1
1368
1620
  op.merge(refs)
1369
1621
  when :rm
1370
- append_pathspec(op.extras, expect: true)
1622
+ append_pathspec(op.extras, expect: !op.arg?('pathspec-from-file'))
1371
1623
  end
1372
- source(sync: sync, stderr: stderr)
1624
+ source(sync: false, stderr: true)
1373
1625
  end
1374
1626
 
1375
1627
  def clone?
@@ -1388,16 +1640,17 @@ module Squared
1388
1640
 
1389
1641
  def source(cmd = @session, exception: true, io: false, sync: true, stdout: false, stderr: false, banner: true,
1390
1642
  multiple: false, sub: nil)
1643
+ cmd = cmd.target if cmd.is_a?(OptionPartition)
1391
1644
  banner = nil if multiple && banner
1392
1645
  if cmd.respond_to?(:done)
1393
1646
  if io && banner == false
1394
1647
  from = nil
1395
- elsif !from && (from = cmd.drop(1).find { |val| val.match?(/^[a-z][a-z\-]{2,}$/) })
1648
+ elsif !from && (from = cmd.drop(1).find { |val| val.match?(/\A[a-z][a-z\-]{2,}\z/) })
1396
1649
  from = :"git:#{from}"
1397
1650
  end
1398
1651
  banner &&= cmd.temp { |val| val.start_with?('--work-tree') || val.start_with?('--git-dir') }
1399
1652
  end
1400
- cmd = session_done(cmd)
1653
+ cmd = session_done cmd
1401
1654
  log&.info cmd
1402
1655
  on :first, from
1403
1656
  banner = if banner
@@ -1414,20 +1667,20 @@ module Squared
1414
1667
  ret = `#{cmd}`
1415
1668
  if !ret.empty?
1416
1669
  puts ret
1417
- elsif banner && stdout && !stdin?
1418
- puts 'Success'
1670
+ elsif success?(!banner.nil?)
1671
+ print_success
1419
1672
  end
1420
1673
  elsif sync || (!exception && !stderr)
1421
1674
  print_item banner unless multiple
1422
- shell(cmd, exception: exception)
1675
+ ret = shell(cmd, exception: exception)
1423
1676
  else
1424
1677
  require 'open3'
1425
1678
  if stderr
1426
1679
  Open3.popen3(cmd) do |_, out, err|
1427
- ret = write_lines(out, banner: banner, sub: sub, pass: true)
1428
- if ret == 0
1429
- ret = write_lines(err, banner: banner)
1430
- puts 'Success' if ret == 0 && banner && !stdin?
1680
+ n = write_lines(out, banner: banner, sub: sub, pass: true)
1681
+ if n == 0
1682
+ n = write_lines(err, banner: banner)
1683
+ print_success if success?(n == 0 && !banner.nil?)
1431
1684
  else
1432
1685
  write_lines(err, loglevel: Logger::DEBUG)
1433
1686
  end
@@ -1442,8 +1695,10 @@ module Squared
1442
1695
  raise if exception && ret != true
1443
1696
 
1444
1697
  warn log_message(Logger::WARN, e, pass: true) if warning?
1698
+ nil
1445
1699
  else
1446
1700
  on :last, from
1701
+ ret
1447
1702
  end
1448
1703
  end
1449
1704
 
@@ -1484,7 +1739,7 @@ module Squared
1484
1739
  styles = theme.fetch(:banner, []).reject { |s| s.to_s.end_with?('!') }
1485
1740
  styles << :bold if styles.size <= 1
1486
1741
  puts print_footer("#{size} #{size == 1 ? type.sub(/(?:(?<!l)e)?s\z/, '') : type}",
1487
- sub: { pat: /\A(\d+)(.+)\z/, styles: styles })
1742
+ sub: { pat: /\A(\d+)(.+)\z/m, styles: styles })
1488
1743
  else
1489
1744
  puts empty_status("No #{type} were #{action}", 'grep', grep.is_a?(Array) ? case grep.size
1490
1745
  when 0
@@ -1497,20 +1752,32 @@ module Squared
1497
1752
  on :last, from
1498
1753
  end
1499
1754
 
1500
- def choice_refs(msg, type, format: nil, sort: '-creatordate', count: ARG[:CHOICE], short: true, **kwargs)
1755
+ def choice_refs(msg, *type, format: nil, sort: '-creatordate', count: true, short: true, **kwargs)
1756
+ type << 'heads' if type.empty?
1501
1757
  unless format
1502
1758
  format = +"%(refname#{short ? ':short' : ''})"
1503
- case type
1504
- when 'heads', 'tags'
1759
+ if type.include?('heads') || type.include?('tags')
1505
1760
  format += '%(if)%(HEAD)%(then) *%(end)'
1506
1761
  trim = /\s+\*\z/
1507
1762
  end
1508
1763
  end
1509
- cmd = git_output 'for-each-ref', quote_option('format', format)
1510
- cmd << quote_option('sort', sort) if sort
1511
- cmd << shell_option('count', count) if count
1512
- cmd << "refs/#{type}"
1513
- choice_index(msg, source(cmd, io: true, banner: false), trim: trim, **kwargs)
1764
+ args = []
1765
+ args << quote_option('sort', sort) if sort
1766
+ args << shell_option('count', env('GIT_COUNT', ARG[:CHOICE])) if count
1767
+ choice_index(msg, foreachref(type, *args, format: format), trim: trim, **kwargs)
1768
+ end
1769
+
1770
+ def choice_commit(count: true, reflog: true, force: true, **kwargs)
1771
+ kwargs[:attempts] ||= 1 unless force
1772
+ cmd = git_output(reflog && env('GIT_REFLOG') ? 'reflog' : 'log')
1773
+ cmd << quote_option('format', '(%h) %s')
1774
+ cmd << basic_option('max-count', env('GIT_COUNT', ARG[:CHOICE])) if count
1775
+ choice_index('Choose a commit', git_spawn(cmd, stdout: false), column: /\((\w+)\)/, force: force, **kwargs)
1776
+ end
1777
+
1778
+ def choice_remote(force: false, **kwargs)
1779
+ kwargs[:attempts] ||= 1 unless force
1780
+ choice_index('Select a remote', git_spawn('remote', stdout: false), force: force, **kwargs)
1514
1781
  end
1515
1782
 
1516
1783
  def status_digest(*args, algorithm: nil, **kwargs)
@@ -1524,15 +1791,11 @@ module Squared
1524
1791
  next if !glob.empty? && glob.none? { |val| File.fnmatch?(val, file, File::FNM_DOTMATCH) }
1525
1792
  next if !pass.empty? && pass.any? { |val| File.fnmatch?(val, file, File::FNM_DOTMATCH) }
1526
1793
 
1527
- ret[file] = algorithm.hexdigest(File.read(basepath(file)))
1794
+ ret[file] = algorithm.hexdigest(File.read(path + file))
1528
1795
  end
1529
1796
  ret
1530
1797
  end
1531
1798
 
1532
- def git_spawn(*cmd, stdout: true)
1533
- source(git_output(*cmd), io: true, banner: false, stdout: stdout)
1534
- end
1535
-
1536
1799
  def append_pull(opts, list, target: @session, flag: nil, no: nil, remote: nil)
1537
1800
  target << '--force' if option('force', target: target)
1538
1801
  append_submodules(target: target)
@@ -1589,7 +1852,7 @@ module Squared
1589
1852
  option_clear files
1590
1853
  else
1591
1854
  if files.empty? && (val = option('pathspec', target: target))
1592
- files = split_escape(val)
1855
+ files = split_escape val
1593
1856
  end
1594
1857
  files = projectmap(files, parent: parent, pass: pass)
1595
1858
  if !files.empty?
@@ -1600,12 +1863,13 @@ module Squared
1600
1863
  end
1601
1864
  end
1602
1865
 
1603
- def append_message(val, target: @session)
1604
- target << quote_option('message', val) unless val.to_s.empty?
1866
+ def append_message(val = nil, target: @session)
1867
+ val = messageopt if val.to_s.empty?
1868
+ target << quote_option('message', val) if val
1605
1869
  end
1606
1870
 
1607
1871
  def append_head(val = nil, target: @session)
1608
- return target << val if val
1872
+ return target << shell_quote(val) if val
1609
1873
 
1610
1874
  append_first('head', 'tree-ish', 'object', target: target, flag: false, ignore: false)
1611
1875
  end
@@ -1614,22 +1878,33 @@ module Squared
1614
1878
  return unless (val = option('recurse-submodules', target: target, ignore: false))
1615
1879
 
1616
1880
  if from == :clone
1617
- projectmap(split_escape(val)).each do |path|
1618
- target << basic_option('recurse-submodules', path)
1881
+ case val
1882
+ when '0', 'false'
1883
+ target << '--no-recurse-submodules'
1884
+ when '1', 'true'
1885
+ target << '--recurse-submodules'
1886
+ else
1887
+ projectmap(split_escape(val)).each do |path|
1888
+ target << basic_option('recurse-submodules', path)
1889
+ end
1619
1890
  end
1620
- target
1621
1891
  else
1622
1892
  target << case val
1623
1893
  when 'no', '0', 'false'
1624
1894
  '--no-recurse-submodules'
1625
1895
  when 'yes', 'on-demand'
1626
- "--recurse-submodules#{from == :reset ? '' : "=#{val}"}"
1896
+ "--recurse-submodules=#{val}"
1627
1897
  else
1628
1898
  '--recurse-submodules'
1629
1899
  end
1630
1900
  end
1631
1901
  end
1632
1902
 
1903
+ def foreachref(path, *args, format: nil)
1904
+ path = as_a(path).map! { |val| "refs/#{val}" }
1905
+ git_spawn('for-each-ref', format && quote_option('format', format), *args, *path, stdout: false)
1906
+ end
1907
+
1633
1908
  def git_session(*cmd, opts: nil, worktree: true, **kwargs)
1634
1909
  dir = worktree ? ["--work-tree=#{shell_quote(path)}", "--git-dir=#{shell_quote(gitpath)}"] : []
1635
1910
  return session('git', *dir, *cmd, **kwargs) unless opts
@@ -1643,10 +1918,14 @@ module Squared
1643
1918
  git_session(*cmd, main: false, options: false, **kwargs)
1644
1919
  end
1645
1920
 
1921
+ def git_spawn(*cmd, stdout: true)
1922
+ source(cmd.first.is_a?(Set) ? cmd.first : git_output(*cmd), io: true, banner: false, stdout: stdout)
1923
+ end
1924
+
1646
1925
  def dryrun?(*, target: @session, **)
1647
1926
  return false unless target
1648
1927
 
1649
- target.include?('--dry-run')
1928
+ target.include?('--dry-run') || !option('dry-run', target: target).nil?
1650
1929
  end
1651
1930
 
1652
1931
  def quiet?(target: @session)
@@ -1656,11 +1935,36 @@ module Squared
1656
1935
  end
1657
1936
 
1658
1937
  def gitpath
1659
- basepath '.git'
1938
+ path + '.git'
1939
+ end
1940
+
1941
+ def repotrack(origin, branch, quote: true)
1942
+ i = origin.index('/')
1943
+ branch = "#{branch}:#{origin[i + 1..-1]}" unless origin.end_with?("/#{branch}")
1944
+ ret = [origin[0..i - 1], branch]
1945
+ quote ? ret.map! { |val| shell_quote(val) } : ret
1660
1946
  end
1661
1947
 
1662
1948
  def commithash(val)
1663
- val[/^#\{(\h{5,40})\}$/, 1]
1949
+ val[/:(\h{5,40})\z/, 1] || val[/\A#\{(\h{5,40})\}\z/, 1]
1950
+ end
1951
+
1952
+ def commithead(val)
1953
+ return val unless (s = matchhead(val))
1954
+
1955
+ s.match?(/^\d/) ? "@~#{s}" : "@#{s}"
1956
+ end
1957
+
1958
+ def matchhead(val)
1959
+ val && val =~ /^(?:(?:HEAD|@)([~^]\d*)?|H(\d+))$/ ? $2 || $1 || '' : nil
1960
+ end
1961
+
1962
+ def matchpathspec
1963
+ [/\A[^a-z\d-]+/i, %r{\A[^=\\/*]*[\\/*]}]
1964
+ end
1965
+
1966
+ def messageopt
1967
+ option('message', 'm', prefix: 'git', ignore: false)
1664
1968
  end
1665
1969
 
1666
1970
  def threadargs