squared 0.3.4 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +67 -2
- data/README.md +13 -2
- data/README.ruby.md +168 -88
- data/lib/squared/app.rb +1 -0
- data/lib/squared/common/base.rb +6 -1
- data/lib/squared/common/class.rb +1 -1
- data/lib/squared/common/format.rb +24 -12
- data/lib/squared/common/prompt.rb +2 -2
- data/lib/squared/common/shell.rb +52 -39
- data/lib/squared/common/utils.rb +54 -1
- data/lib/squared/config.rb +1 -1
- data/lib/squared/version.rb +1 -1
- data/lib/squared/workspace/application.rb +16 -13
- data/lib/squared/workspace/project/base.rb +429 -123
- data/lib/squared/workspace/project/docker.rb +572 -0
- data/lib/squared/workspace/project/git.rb +405 -157
- data/lib/squared/workspace/project/node.rb +51 -51
- data/lib/squared/workspace/project/python.rb +115 -24
- data/lib/squared/workspace/project/ruby.rb +33 -34
- data/lib/squared/workspace/project.rb +7 -1
- data/lib/squared/workspace/repo.rb +9 -4
- data/lib/squared/workspace/series.rb +1 -1
- metadata +2 -1
| @@ -1,5 +1,9 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            +
            require 'date'
         | 
| 4 | 
            +
            require 'time'
         | 
| 5 | 
            +
            require 'digest'
         | 
| 6 | 
            +
             | 
| 3 7 | 
             
            module Squared
         | 
| 4 8 | 
             
              module Workspace
         | 
| 5 9 | 
             
                module Git
         | 
| @@ -7,7 +11,9 @@ module Squared | |
| 7 11 | 
             
                  GIT_PROTO = %r{^(?:https?|ssh|git|file)://}i.freeze
         | 
| 8 12 | 
             
                  private_constant :GIT_REPO, :GIT_PROTO
         | 
| 9 13 |  | 
| 10 | 
            -
                   | 
| 14 | 
            +
                  attr_reader :revfile
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  def git(name, uri = nil, base: nil, repo: [], options: {}, cache: nil)
         | 
| 11 17 | 
             
                    data = {}
         | 
| 12 18 | 
             
                    check = ->(proj) { proj.is_a?(Project::Git) && !proj.exclude?(Project::Git.ref) && git_clone?(proj.path) }
         | 
| 13 19 | 
             
                    if uri.is_a?(Array)
         | 
| @@ -17,15 +23,15 @@ module Squared | |
| 17 23 | 
             
                      data[name.to_s] = uri
         | 
| 18 24 | 
             
                    elsif name.is_a?(Enumerable)
         | 
| 19 25 | 
             
                      data = name.to_h
         | 
| 20 | 
            -
                    elsif name.is_a?(String) && name | 
| 26 | 
            +
                    elsif name.is_a?(String) && name.match?(GIT_PROTO)
         | 
| 21 27 | 
             
                      base = name
         | 
| 22 28 | 
             
                      @project.each_value { |proj| repo << proj if !proj.parent && check.(proj) }
         | 
| 23 29 | 
             
                    else
         | 
| 24 | 
            -
                      warn log_message(Logger::WARN, name, subject: 'git', hint: 'invalid') if warning
         | 
| 30 | 
            +
                      warn log_message(Logger::WARN, name, subject: 'git', hint: 'invalid', pass: true) if warning
         | 
| 25 31 | 
             
                      return self
         | 
| 26 32 | 
             
                    end
         | 
| 27 33 | 
             
                    if base
         | 
| 28 | 
            -
                      base = base | 
| 34 | 
            +
                      base = base.match?(GIT_PROTO) ? "#{base.chomp('/')}/" : @root.join(base)
         | 
| 29 35 | 
             
                      repo.each do |target|
         | 
| 30 36 | 
             
                        if target.is_a?(Project::Git)
         | 
| 31 37 | 
             
                          data[target.localname] = target.project
         | 
| @@ -55,6 +61,23 @@ module Squared | |
| 55 61 | 
             
                      (GIT_REPO[main] ||= {})[key] = [uri.to_s, opts]
         | 
| 56 62 | 
             
                      (@kind[key] ||= []) << Project::Git
         | 
| 57 63 | 
             
                    end
         | 
| 64 | 
            +
                    if cache == true
         | 
| 65 | 
            +
                      revbuild
         | 
| 66 | 
            +
                    elsif cache
         | 
| 67 | 
            +
                      revbuild(file: cache)
         | 
| 68 | 
            +
                    end
         | 
| 69 | 
            +
                    self
         | 
| 70 | 
            +
                  end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                  def revbuild(file: nil)
         | 
| 73 | 
            +
                    @revfile = @home.join(file || "#{@main}.revb")
         | 
| 74 | 
            +
                    @revdoc = JSON.parse(@revfile.read) if @revfile.exist?
         | 
| 75 | 
            +
                  rescue StandardError => e
         | 
| 76 | 
            +
                    @revfile = nil
         | 
| 77 | 
            +
                    warn log_message(Logger::WARN, e, pass: true) if @warning
         | 
| 78 | 
            +
                    self
         | 
| 79 | 
            +
                  else
         | 
| 80 | 
            +
                    @revdoc = {} unless @revdoc.is_a?(Hash)
         | 
| 58 81 | 
             
                    self
         | 
| 59 82 | 
             
                  end
         | 
| 60 83 |  | 
| @@ -62,23 +85,76 @@ module Squared | |
| 62 85 | 
             
                    (ret = GIT_REPO[main]) && ret[name]
         | 
| 63 86 | 
             
                  end
         | 
| 64 87 |  | 
| 88 | 
            +
                  def rev_entry(*keys, val: nil, create: true)
         | 
| 89 | 
            +
                    return unless @revdoc
         | 
| 90 | 
            +
                    return @revdoc.dig(*keys) unless val
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                    data = @revdoc
         | 
| 93 | 
            +
                    last = keys.pop
         | 
| 94 | 
            +
                    for key in keys
         | 
| 95 | 
            +
                      if data[key].is_a?(Hash)
         | 
| 96 | 
            +
                        data = data[key]
         | 
| 97 | 
            +
                      elsif create
         | 
| 98 | 
            +
                        data = data[key] = {}
         | 
| 99 | 
            +
                      else
         | 
| 100 | 
            +
                        return
         | 
| 101 | 
            +
                      end
         | 
| 102 | 
            +
                    end
         | 
| 103 | 
            +
                    data[last] = val
         | 
| 104 | 
            +
                  end
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                  def rev_timeutc(*keys)
         | 
| 107 | 
            +
                    rev_entry(*keys, val: rev_timenow)
         | 
| 108 | 
            +
                  end
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                  def rev_timesince(*keys, clock: false)
         | 
| 111 | 
            +
                    epoch = rev_timenow - rev_entry(*keys).to_i
         | 
| 112 | 
            +
                  rescue StandardError
         | 
| 113 | 
            +
                    nil
         | 
| 114 | 
            +
                  else
         | 
| 115 | 
            +
                    time_format(epoch, clock: clock)
         | 
| 116 | 
            +
                  end
         | 
| 117 | 
            +
             | 
| 118 | 
            +
                  def rev_clear(name)
         | 
| 119 | 
            +
                    rev_write if rev_entry(name, 'revision', val: '', create: false)
         | 
| 120 | 
            +
                  end
         | 
| 121 | 
            +
             | 
| 122 | 
            +
                  def rev_write(name = nil, data = nil, utc: nil)
         | 
| 123 | 
            +
                    return unless @revfile
         | 
| 124 | 
            +
             | 
| 125 | 
            +
                    if name
         | 
| 126 | 
            +
                      data&.each { |key, val| rev_entry(name, key, val: val) }
         | 
| 127 | 
            +
                      rev_timeutc(name, utc) if utc
         | 
| 128 | 
            +
                    end
         | 
| 129 | 
            +
                    File.write(@revfile, JSON.pretty_generate(@revdoc))
         | 
| 130 | 
            +
                  end
         | 
| 131 | 
            +
             | 
| 65 132 | 
             
                  def git_clone?(path, name = nil)
         | 
| 66 133 | 
             
                    return false if name && !git_repo(name)
         | 
| 67 134 |  | 
| 68 135 | 
             
                    !path.exist? || path.empty?
         | 
| 69 136 | 
             
                  end
         | 
| 137 | 
            +
             | 
| 138 | 
            +
                  private
         | 
| 139 | 
            +
             | 
| 140 | 
            +
                  def rev_timenow
         | 
| 141 | 
            +
                    DateTime.now.strftime('%Q').to_i + time_offset
         | 
| 142 | 
            +
                  end
         | 
| 70 143 | 
             
                end
         | 
| 71 144 | 
             
                Application.include Git
         | 
| 72 145 |  | 
| 73 146 | 
             
                module Project
         | 
| 74 147 | 
             
                  class Git < Base
         | 
| 75 148 | 
             
                    OPT_GIT = {
         | 
| 149 | 
            +
                      common: %w[bare p|paginate P|no-pager glob-pathspecs icase-pathspecs literal-pathspecs no-optional-locks
         | 
| 150 | 
            +
                                 no-replace-objects noglob-pathspecs c=q config-env=q exec-path=p namespace=p].freeze,
         | 
| 76 151 | 
             
                      branch: %w[a|all create-reflog i|ignore-case q|quiet r|remotes v|verbose abbrev=i color=b column=b
         | 
| 77 | 
            -
                                 contains= | 
| 152 | 
            +
                                 contains=b format=q merged=b no-contains=b no-merged=b points-at=e u|set-upstream-to=e sort=q
         | 
| 78 153 | 
             
                                 t|track=b].freeze,
         | 
| 79 154 | 
             
                      checkout: %w[l d|detach f|force ignore-other-worktrees ignore-skip-worktree-bits m|merge p|patch
         | 
| 80 155 | 
             
                                   pathspec-file-nul q quiet orphan=e ours theirs conflict=b pathspec-from-file=p
         | 
| 81 156 | 
             
                                   t|track=b].freeze,
         | 
| 157 | 
            +
                      clean: %w[d x X f|force i|interactive n|dry-run q|quiet e|exlcude=q].freeze,
         | 
| 82 158 | 
             
                      diff: {
         | 
| 83 159 | 
             
                        base: %w[0 1|base 2|ours 3|theirs].freeze,
         | 
| 84 160 | 
             
                        show: %w[s exit-code histogram].freeze
         | 
| @@ -88,7 +164,7 @@ module Squared | |
| 88 164 | 
             
                                 recurse-submodules-default=b].freeze,
         | 
| 89 165 | 
             
                        pull: %w[4 6 n t a|append atomic dry-run f|force k|keep n|negotiate-only prefetch p|prune q|quiet
         | 
| 90 166 | 
             
                                 set-upstream unshallow update-shallow v|verbose deepen=i depth=i j|jobs=i negotiation-tip=q
         | 
| 91 | 
            -
                                 refmap=q o|server-option= | 
| 167 | 
            +
                                 refmap=q o|server-option=q shallow-exclude=e shallow-since=b upload-pack=q].freeze
         | 
| 92 168 | 
             
                      }.freeze,
         | 
| 93 169 | 
             
                      log: {
         | 
| 94 170 | 
             
                        base: %w[all all-match alternate-refs author-date-order basic-regexp bisect boundary cherry cherry-mark
         | 
| @@ -96,7 +172,7 @@ module Squared | |
| 96 172 | 
             
                                 first-parent F|fixed-strings follow full-diff full-history ignore-missing invert-grep left-only
         | 
| 97 173 | 
             
                                 merge log-size no-max-parents no-min-parents not P|perl-regexp reflog i|regexp-ignore-case
         | 
| 98 174 | 
             
                                 remove-empty reverse right-only simplify-by-decoration simplify-merges single-worktree show-pulls
         | 
| 99 | 
            -
                                 source sparse stdin topo-order g|walk-reflogs after=q ancestry-path= | 
| 175 | 
            +
                                 source sparse stdin topo-order g|walk-reflogs after=q ancestry-path=b? author=q before=q
         | 
| 100 176 | 
             
                                 branches=q? committer=q decorate=b decorate-refs=q decorate-refs-exclude=q exclude=q
         | 
| 101 177 | 
             
                                 exclude-hidden=b? glob=q grep=q grep-reflog=q L=q n|max-count=i max-parents=i min-parents=i
         | 
| 102 178 | 
             
                                 no-walk=b? remotes=q? since=q since-as-filter=q skip=i tags=q? until=q].freeze,
         | 
| @@ -107,9 +183,9 @@ module Squared | |
| 107 183 | 
             
                                 W|function-context w|ignore-all-space ignore-blank-lines ignore-cr-at-eol ignore-space-at-eol
         | 
| 108 184 | 
             
                                 b|ignore-space-change D|irreversible-delete graph ita-invisible-in-index minimal name-only
         | 
| 109 185 | 
             
                                 name-status no-color-moved-ws no-prefix no-renames numstat patch-with-raw patch-with-stat patience
         | 
| 110 | 
            -
                                 pickaxe-all pickaxe-regex raw shortstat summary a|text abbrev=i? anchored=q B|break-rewrites= | 
| 186 | 
            +
                                 pickaxe-all pickaxe-regex raw shortstat summary a|text abbrev=i? anchored=q B|break-rewrites=b?
         | 
| 111 187 | 
             
                                 color=b color-moved=b color-moved-ws=b color-words=q? diff-algorithm=b diff-filter=e? X|dirstat=q?
         | 
| 112 | 
            -
                                 dirstat-by-file=q? dst-prefix=q C|find-copies=i? find-object= | 
| 188 | 
            +
                                 dirstat-by-file=q? dst-prefix=q C|find-copies=i? find-object=b M|find-renames=i?
         | 
| 113 189 | 
             
                                 I|ignore-matching-lines=q ignore-submodules=b inter-hunk-context=i line-prefix=q output=p
         | 
| 114 190 | 
             
                                 output-indicator-context=q output-indicator-new=q output-indicator-old=q relative=p rotate-to=p
         | 
| 115 191 | 
             
                                 skip-to=p src-prefix=q stat=q? stat-width=i stat-name-width=i stat-count=i submodule=b? U|unified=i
         | 
| @@ -118,25 +194,32 @@ module Squared | |
| 118 194 | 
             
                      ls_files: %w[z debug deduplicate directory eol error-unmatch exclude-standard full-name k|killed
         | 
| 119 195 | 
             
                                   no-empty-directory recurse-submodules sparse s|stage u|unmerged abbrev=i x|exclude=q
         | 
| 120 196 | 
             
                                   X|exclude-from=p exclude-per-directory=p format=q with-tree=q].freeze,
         | 
| 121 | 
            -
                      ls_remote: %w[exit-code get-url q|quiet o|server-option= | 
| 122 | 
            -
                       | 
| 123 | 
            -
             | 
| 197 | 
            +
                      ls_remote: %w[exit-code get-url q|quiet o|server-option=q symref sort=q upload-pack=q].freeze,
         | 
| 198 | 
            +
                      mv: %w[k f|force n|dry-run v|verbose].freeze,
         | 
| 199 | 
            +
                      pull: %w[e n allow-unrelated-histories ff-only S|gpg-sign=qq log=i r|rebase=b? s|strategy=b
         | 
| 200 | 
            +
                               X|strategy-option=b].freeze,
         | 
| 201 | 
            +
                      merge: %w[e n allow-unrelated-histories ff-only m=q q|quiet v|verbose cleanup=b F|file=p S|gpg-sign=qq
         | 
| 202 | 
            +
                                into-name=e log=i s|strategy=b X|strategy-option=b].freeze,
         | 
| 124 203 | 
             
                      rebase: %w[n C=i allow-empty-message apply committer-date-is-author-date edit-todo f|force-rebase ignore-date
         | 
| 125 204 | 
             
                                 ignore-whitespace i|interactive keep-base m merge no-ff q|quiet quit r|rebase-merges=b?
         | 
| 126 | 
            -
                                 reset-author-date root show-current-patch signoff v|verbose empty=b S|gpg-sign= | 
| 127 | 
            -
                                 s|strategy=b X|strategy-option=b whitespace= | 
| 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,
         | 
| 128 207 | 
             
                      reset: %w[N pathspec-file-nul q|quiet pathspec-from-file=p].freeze,
         | 
| 129 208 | 
             
                      restore: %w[ignore-unmerged ignore-skip-worktree-bits m|merge ours p|patch pathspec-file-nul S|staged theirs
         | 
| 130 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,
         | 
| 131 212 | 
             
                      rev_parse: {
         | 
| 132 213 | 
             
                        output: %w[absolute-git-dir all flags git-common-dir git-dir is-bare-repository is-inside-git-dir
         | 
| 133 214 | 
             
                                   is-inside-work-tree is-shallow-repository local-env-vars no-flags no-revs not q|quiet sq
         | 
| 134 215 | 
             
                                   revs-only shared-index-path show-cdup show-prefix show-toplevel show-superproject-working-tree
         | 
| 135 | 
            -
                                   sq-quote symbolic symbolic-full-name verify abbrev-ref=b? after=q before=q default= | 
| 216 | 
            +
                                   sq-quote symbolic symbolic-full-name verify abbrev-ref=b? after=q before=q default=q
         | 
| 136 217 | 
             
                                   disambiguate=b exclude=q exclude-hidden=b glob=q git-path=p path-format=b? prefix=q branches=q?
         | 
| 137 218 | 
             
                                   remotes=q? resolve-git-dir=p short=i? show-object-format=b? since=q tags=q? until=q].freeze,
         | 
| 138 219 | 
             
                        parseopt: %w[keep-dashdash stop-at-non-option stuck-long].freeze
         | 
| 139 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,
         | 
| 140 223 | 
             
                      show: %w[t combined-all-paths no-diff-merges remerge-diff show-signature diff-merges=b encoding=b
         | 
| 141 224 | 
             
                               expand-tabs=i notes=q show-notes=q?].freeze,
         | 
| 142 225 | 
             
                      stash: {
         | 
| @@ -146,7 +229,8 @@ module Squared | |
| 146 229 | 
             
                        pop: %w[index].freeze,
         | 
| 147 230 | 
             
                        apply: %w[index].freeze
         | 
| 148 231 | 
             
                      }.freeze,
         | 
| 149 | 
            -
                       | 
| 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
         | 
| 150 234 | 
             
                              sort=q].freeze,
         | 
| 151 235 | 
             
                      no: {
         | 
| 152 236 | 
             
                        fetch: {
         | 
| @@ -162,20 +246,26 @@ module Squared | |
| 162 246 | 
             
                        tag: %w[column].freeze,
         | 
| 163 247 | 
             
                        branch: %w[color-moved column color track].freeze,
         | 
| 164 248 | 
             
                        checkout: %w[overwrite-ignore guess overlay progress recurse-submodules track].freeze,
         | 
| 249 | 
            +
                        merge: %w[autostash edit ff gpg-sign log progress overwrite-ignore rerere-autoupdate signoff squash stat
         | 
| 250 | 
            +
                                  verify verify-signatures].freeze,
         | 
| 165 251 | 
             
                        rebase: %w[autosquash autostash fork-point gpg-sign keep-empty reapply-cherry-picks reschedule-failed-exec
         | 
| 166 252 | 
             
                                   rerere-autoupdate stat update-refs verify].freeze,
         | 
| 167 253 | 
             
                        reset: %w[refresh].freeze,
         | 
| 168 254 | 
             
                        restore: %w[overlay progress recurse-submodules].freeze,
         | 
| 255 | 
            +
                        revert: %w[edit gpg-sign rerere-autoupdate].freeze,
         | 
| 169 256 | 
             
                        show: %w[standard-notes].freeze
         | 
| 170 257 | 
             
                      }.freeze
         | 
| 171 | 
            -
                    }
         | 
| 258 | 
            +
                    }.freeze
         | 
| 172 259 | 
             
                    VAL_GIT = {
         | 
| 260 | 
            +
                      merge: {
         | 
| 261 | 
            +
                        send: %w[continue abort quit].freeze
         | 
| 262 | 
            +
                      }.freeze,
         | 
| 173 263 | 
             
                      rebase: {
         | 
| 174 264 | 
             
                        send: %w[continue skip abort quit].freeze,
         | 
| 175 265 | 
             
                        value: %w[true false merges interactive].freeze
         | 
| 176 266 | 
             
                      }.freeze,
         | 
| 177 267 | 
             
                      reset: %w[soft mixed hard merge keep recurse-submodules no-recurse-submodules].freeze
         | 
| 178 | 
            -
                    }
         | 
| 268 | 
            +
                    }.freeze
         | 
| 179 269 | 
             
                    private_constant :OPT_GIT, :VAL_GIT
         | 
| 180 270 |  | 
| 181 271 | 
             
                    class << self
         | 
| @@ -207,7 +297,7 @@ module Squared | |
| 207 297 | 
             
                      end
         | 
| 208 298 |  | 
| 209 299 | 
             
                      def tasks
         | 
| 210 | 
            -
                        %i[pull rebase fetch clone stash status].freeze
         | 
| 300 | 
            +
                        %i[pull rebase fetch clone stash status branch revbuild].freeze
         | 
| 211 301 | 
             
                      end
         | 
| 212 302 |  | 
| 213 303 | 
             
                      def batchargs
         | 
| @@ -225,16 +315,17 @@ module Squared | |
| 225 315 | 
             
                      'branch' => %i[create set delete move copy list edit current].freeze,
         | 
| 226 316 | 
             
                      'checkout' => %i[commit branch track detach path].freeze,
         | 
| 227 317 | 
             
                      'commit' => %i[add all amend amend-orig].freeze,
         | 
| 228 | 
            -
                      'diff' => %i[head  | 
| 318 | 
            +
                      'diff' => %i[head branch files view between contain].freeze,
         | 
| 229 319 | 
             
                      'fetch' => %i[origin remote].freeze,
         | 
| 230 320 | 
             
                      'files' => %i[cached modified deleted others ignored].freeze,
         | 
| 321 | 
            +
                      'git' => %i[clean mv restore revert rm].freeze,
         | 
| 231 322 | 
             
                      'log' => %i[view between contain].freeze,
         | 
| 323 | 
            +
                      'merge' => %i[commit no-commit send].freeze,
         | 
| 232 324 | 
             
                      'pull' => %i[origin remote].freeze,
         | 
| 233 325 | 
             
                      'rebase' => %i[branch onto send].freeze,
         | 
| 234 326 | 
             
                      'refs' => %i[heads tags remote].freeze,
         | 
| 235 327 | 
             
                      'reset' => %i[commit index patch mode].freeze,
         | 
| 236 | 
            -
                      ' | 
| 237 | 
            -
                      'rev' => %i[commit branch output parseopt].freeze,
         | 
| 328 | 
            +
                      'rev' => %i[commit branch output parseopt build].freeze,
         | 
| 238 329 | 
             
                      'show' => %i[format oneline].freeze,
         | 
| 239 330 | 
             
                      'stash' => %i[push pop apply drop list].freeze,
         | 
| 240 331 | 
             
                      'tag' => %i[add delete list].freeze
         | 
| @@ -242,7 +333,7 @@ module Squared | |
| 242 333 |  | 
| 243 334 | 
             
                    def initialize(*, **)
         | 
| 244 335 | 
             
                      super
         | 
| 245 | 
            -
                      initialize_ref | 
| 336 | 
            +
                      initialize_ref Git.ref if gitpath.exist?
         | 
| 246 337 | 
             
                    end
         | 
| 247 338 |  | 
| 248 339 | 
             
                    def ref
         | 
| @@ -287,19 +378,6 @@ module Squared | |
| 287 378 | 
             
                                    commit(flag, refs: refs)
         | 
| 288 379 | 
             
                                  end
         | 
| 289 380 | 
             
                                end
         | 
| 290 | 
            -
                              when 'restore'
         | 
| 291 | 
            -
                                if flag == :source
         | 
| 292 | 
            -
                                  format_desc action, flag, 'tree,opts*,pathspec*'
         | 
| 293 | 
            -
                                  task flag, [:tree] do |_, args|
         | 
| 294 | 
            -
                                    tree = param_guard(action, flag, args: args, key: :tree)
         | 
| 295 | 
            -
                                    restore(flag, args.extras, tree: tree)
         | 
| 296 | 
            -
                                  end
         | 
| 297 | 
            -
                                else
         | 
| 298 | 
            -
                                  format_desc action, flag, 'opts*,pathspec+'
         | 
| 299 | 
            -
                                  task flag do |_, args|
         | 
| 300 | 
            -
                                    restore flag, args.to_a
         | 
| 301 | 
            -
                                  end
         | 
| 302 | 
            -
                                end
         | 
| 303 381 | 
             
                              when 'tag'
         | 
| 304 382 | 
             
                                case flag
         | 
| 305 383 | 
             
                                when :list
         | 
| @@ -333,9 +411,19 @@ module Squared | |
| 333 411 | 
             
                                case flag
         | 
| 334 412 | 
             
                                when :view, :between, :contain
         | 
| 335 413 | 
             
                                  if flag == :view && action == 'log'
         | 
| 336 | 
            -
                                    format_desc action, flag, '(^)commit*,pathspec*,opts*'
         | 
| 414 | 
            +
                                    format_desc action, flag, '(^)commit/H0*,pathspec*,opts*'
         | 
| 337 415 | 
             
                                    task flag do |_, args|
         | 
| 338 | 
            -
                                       | 
| 416 | 
            +
                                      index = []
         | 
| 417 | 
            +
                                      args.to_a.each do |val|
         | 
| 418 | 
            +
                                        if val =~ /^H(\d+)$/
         | 
| 419 | 
            +
                                          index << "HEAD~#{$1}"
         | 
| 420 | 
            +
                                        elsif (sha = commithash(val))
         | 
| 421 | 
            +
                                          index << sha
         | 
| 422 | 
            +
                                        elsif val.start_with?('^') || (!%r{^[.\\/]}.match?(val) && !%r{[\\/]$}.match?(val))
         | 
| 423 | 
            +
                                          index << shell_quote(val)
         | 
| 424 | 
            +
                                        end
         | 
| 425 | 
            +
                                      end
         | 
| 426 | 
            +
                                      logx(flag, args.to_a.drop(index.size), index: index)
         | 
| 339 427 | 
             
                                    end
         | 
| 340 428 | 
             
                                  else
         | 
| 341 429 | 
             
                                    format_desc action, flag, 'commit1,commit2,pathspec*,opts*'
         | 
| @@ -345,10 +433,16 @@ module Squared | |
| 345 433 | 
             
                                      __send__(action == 'log' ? :logx : :diff, flag, args.extras, range: [commit1, commit2])
         | 
| 346 434 | 
             
                                    end
         | 
| 347 435 | 
             
                                  end
         | 
| 348 | 
            -
                                when :head | 
| 349 | 
            -
                                  format_desc action, flag, 'opts*,pathspec*'
         | 
| 436 | 
            +
                                when :head
         | 
| 437 | 
            +
                                  format_desc action, flag, 'commit/H0*,opts*,pathspec*'
         | 
| 350 438 | 
             
                                  task flag do |_, args|
         | 
| 351 | 
            -
                                     | 
| 439 | 
            +
                                    index = []
         | 
| 440 | 
            +
                                    args.to_a.each do |val|
         | 
| 441 | 
            +
                                      break unless val =~ /^H(\d+)$/ || (sha = commithash(val))
         | 
| 442 | 
            +
             | 
| 443 | 
            +
                                      index << ($1 ? "HEAD~#{$1}" : sha)
         | 
| 444 | 
            +
                                    end
         | 
| 445 | 
            +
                                    diff(flag, args.to_a.drop(index.size), index: index)
         | 
| 352 446 | 
             
                                  end
         | 
| 353 447 | 
             
                                when :branch
         | 
| 354 448 | 
             
                                  format_desc action, flag, 'name,opts*,pathspec*'
         | 
| @@ -493,7 +587,7 @@ module Squared | |
| 493 587 | 
             
                                    show args.format, args.extras
         | 
| 494 588 | 
             
                                  end
         | 
| 495 589 | 
             
                                end
         | 
| 496 | 
            -
                              when 'rebase'
         | 
| 590 | 
            +
                              when 'rebase', 'merge'
         | 
| 497 591 | 
             
                                case flag
         | 
| 498 592 | 
             
                                when :branch
         | 
| 499 593 | 
             
                                  format_desc action, flag, 'opts*,upstream?,branch?'
         | 
| @@ -508,11 +602,18 @@ module Squared | |
| 508 602 | 
             
                                    upstream = param_guard(action, flag, args: args, key: :upstream)
         | 
| 509 603 | 
             
                                    rebase(flag, commit: commit, upstream: upstream, branch: args.branch)
         | 
| 510 604 | 
             
                                  end
         | 
| 605 | 
            +
                                when :commit, :'no-commit'
         | 
| 606 | 
            +
                                  format_desc action, flag, 'branch/commit+,opts*'
         | 
| 607 | 
            +
                                  task flag do |_, args|
         | 
| 608 | 
            +
                                    args = param_guard(action, flag, args: args.to_a)
         | 
| 609 | 
            +
                                    merge flag, args
         | 
| 610 | 
            +
                                  end
         | 
| 511 611 | 
             
                                when :send
         | 
| 512 | 
            -
                                  format_desc(action, flag, VAL_GIT[ | 
| 612 | 
            +
                                  format_desc(action, flag, VAL_GIT[action.to_sym][:send], arg: nil)
         | 
| 513 613 | 
             
                                  task flag, [:command] do |_, args|
         | 
| 514 | 
            -
                                    command = param_guard(action, flag, args: args, key: :command | 
| 515 | 
            -
             | 
| 614 | 
            +
                                    command = param_guard(action, flag, args: args, key: :command,
         | 
| 615 | 
            +
                                                                        values: VAL_GIT[action.to_sym][:send])
         | 
| 616 | 
            +
                                    __send__(action, flag, command: command)
         | 
| 516 617 | 
             
                                  end
         | 
| 517 618 | 
             
                                end
         | 
| 518 619 | 
             
                              when 'rev'
         | 
| @@ -533,6 +634,11 @@ module Squared | |
| 533 634 | 
             
                                  task flag, [:ref] do |_, args|
         | 
| 534 635 | 
             
                                    rev_parse(flag, ref: args.ref)
         | 
| 535 636 | 
             
                                  end
         | 
| 637 | 
            +
                                when :build
         | 
| 638 | 
            +
                                  format_desc action, flag, OPT_GIT[:status]
         | 
| 639 | 
            +
                                  task flag do |_, args|
         | 
| 640 | 
            +
                                    revbuild flag, args.to_a
         | 
| 641 | 
            +
                                  end
         | 
| 536 642 | 
             
                                else
         | 
| 537 643 | 
             
                                  format_desc action, flag, 'opts*,args*'
         | 
| 538 644 | 
             
                                  task flag do |_, args|
         | 
| @@ -552,6 +658,18 @@ module Squared | |
| 552 658 | 
             
                                    __send__(action == 'refs' ? :ls_remote : :ls_files, flag, args.to_a)
         | 
| 553 659 | 
             
                                  end
         | 
| 554 660 | 
             
                                end
         | 
| 661 | 
            +
                              when 'git'
         | 
| 662 | 
            +
                                format_desc(action, flag, 'opts*', before: case flag
         | 
| 663 | 
            +
                                                                           when :rm
         | 
| 664 | 
            +
                                                                             'source+,destination'
         | 
| 665 | 
            +
                                                                           when :revert
         | 
| 666 | 
            +
                                                                             'commit+'
         | 
| 667 | 
            +
                                                                           else
         | 
| 668 | 
            +
                                                                             'pathspec*'
         | 
| 669 | 
            +
                                                                           end)
         | 
| 670 | 
            +
                                task flag do |_, args|
         | 
| 671 | 
            +
                                  git(flag, args.to_a)
         | 
| 672 | 
            +
                                end
         | 
| 555 673 | 
             
                              end
         | 
| 556 674 | 
             
                            end
         | 
| 557 675 | 
             
                          end
         | 
| @@ -564,8 +682,18 @@ module Squared | |
| 564 682 | 
             
                      super
         | 
| 565 683 | 
             
                    end
         | 
| 566 684 |  | 
| 685 | 
            +
                    def depend(*, **)
         | 
| 686 | 
            +
                      workspace.rev_clear name
         | 
| 687 | 
            +
                      super
         | 
| 688 | 
            +
                    end
         | 
| 689 | 
            +
             | 
| 690 | 
            +
                    def clean(*, **)
         | 
| 691 | 
            +
                      workspace.rev_clear name
         | 
| 692 | 
            +
                      super
         | 
| 693 | 
            +
                    end
         | 
| 694 | 
            +
             | 
| 567 695 | 
             
                    def pull(flag = nil, opts = [], sync: invoked_sync?('pull', flag), remote: nil)
         | 
| 568 | 
            -
                      cmd = git_session | 
| 696 | 
            +
                      cmd, opts = git_session('pull', flag && "--#{flag}", opts: opts)
         | 
| 569 697 | 
             
                      if (val = option('rebase', ignore: false))
         | 
| 570 698 | 
             
                        cmd << case val
         | 
| 571 699 | 
             
                               when '0'
         | 
| @@ -578,8 +706,8 @@ module Squared | |
| 578 706 | 
             
                                  no: OPT_GIT[:no][:pull] + OPT_GIT[:no][:fetch][:pull], remote: remote, flag: flag)
         | 
| 579 707 | 
             
                      source(sync: sync, sub: if verbose
         | 
| 580 708 | 
             
                                                [
         | 
| 581 | 
            -
                                                  { pat: /^(.+)(\|\s+\d+\s+)([^-]*)(-+)(.*)$/, styles: :red, index: 4 },
         | 
| 582 | 
            -
                                                  { pat: /^(.+)(\|\s+\d+\s+)(\++)(-*)(.*)$/, styles: :green, index: 3 }
         | 
| 709 | 
            +
                                                  { pat: /^(.+)(\|\s+\d+\s+)([^-]*)(-+)(.*)$/, styles: color(:red), index: 4 },
         | 
| 710 | 
            +
                                                  { pat: /^(.+)(\|\s+\d+\s+)(\++)(-*)(.*)$/, styles: color(:green), index: 3 }
         | 
| 583 711 | 
             
                                                ]
         | 
| 584 712 | 
             
                                              end, **threadargs)
         | 
| 585 713 | 
             
                    end
         | 
| @@ -588,7 +716,7 @@ module Squared | |
| 588 716 | 
             
                               command: nil)
         | 
| 589 717 | 
             
                      return pull(:rebase, sync: sync) unless flag
         | 
| 590 718 |  | 
| 591 | 
            -
                      cmd = git_session | 
| 719 | 
            +
                      cmd, opts = git_session('rebase', opts: opts)
         | 
| 592 720 | 
             
                      case flag
         | 
| 593 721 | 
             
                      when :branch
         | 
| 594 722 | 
             
                        branch = option_sanitize(opts, OPT_GIT[:rebase], no: OPT_GIT[:no][:rebase]).first
         | 
| @@ -617,7 +745,7 @@ module Squared | |
| 617 745 | 
             
                    end
         | 
| 618 746 |  | 
| 619 747 | 
             
                    def fetch(flag = nil, opts = [], sync: invoked_sync?('fetch', flag), remote: nil)
         | 
| 620 | 
            -
                      cmd = git_session | 
| 748 | 
            +
                      cmd, opts = git_session('fetch', opts: opts)
         | 
| 621 749 | 
             
                      cmd << '--all' if !remote && !opts.include?('multiple') && option('all')
         | 
| 622 750 | 
             
                      cmd << '--verbose' if verbose && !opts.include?('quiet')
         | 
| 623 751 | 
             
                      append_pull(opts, collect_hash(OPT_GIT[:fetch]), no: collect_hash(OPT_GIT[:no][:fetch]),
         | 
| @@ -649,9 +777,8 @@ module Squared | |
| 649 777 |  | 
| 650 778 | 
             
                    def stash(flag = nil, opts = [], sync: invoked_sync?('stash', flag))
         | 
| 651 779 | 
             
                      if flag
         | 
| 652 | 
            -
                        cmd = git_session | 
| 653 | 
            -
                         | 
| 654 | 
            -
                        refs = option_sanitize(opts, list).first
         | 
| 780 | 
            +
                        cmd, opts = git_session('stash', flag, opts: opts)
         | 
| 781 | 
            +
                        refs = option_sanitize(opts, OPT_GIT[:stash][:common] + OPT_GIT[:stash].fetch(flag, [])).first
         | 
| 655 782 | 
             
                        case flag
         | 
| 656 783 | 
             
                        when :push
         | 
| 657 784 | 
             
                          append_pathspec refs
         | 
| @@ -666,14 +793,14 @@ module Squared | |
| 666 793 | 
             
                          return
         | 
| 667 794 | 
             
                        end
         | 
| 668 795 | 
             
                      else
         | 
| 669 | 
            -
                        git_session | 
| 796 | 
            +
                        git_session('stash', 'push', opts: opts)
         | 
| 670 797 | 
             
                        append_option(%w[all keep-index include-untracked staged].freeze, no: true, ignore: false)
         | 
| 671 798 | 
             
                        append_message option('message', 'm', ignore: false)
         | 
| 672 799 | 
             
                      end
         | 
| 673 800 | 
             
                      source(banner: !quiet?, sync: sync, **threadargs)
         | 
| 674 801 | 
             
                    end
         | 
| 675 802 |  | 
| 676 | 
            -
                    def status( | 
| 803 | 
            +
                    def status(*)
         | 
| 677 804 | 
             
                      cmd = git_session 'status'
         | 
| 678 805 | 
             
                      cmd << (option('long') ? '--long' : '--short')
         | 
| 679 806 | 
             
                      if (val = option('ignore-submodules', ignore: false))
         | 
| @@ -690,24 +817,76 @@ module Squared | |
| 690 817 | 
             
                      end
         | 
| 691 818 | 
             
                      append_pathspec
         | 
| 692 819 | 
             
                      out, banner, from = source(io: true)
         | 
| 693 | 
            -
                      if sync
         | 
| 694 | 
            -
                        print_item banner
         | 
| 695 | 
            -
                        banner = nil
         | 
| 696 | 
            -
                      end
         | 
| 697 820 | 
             
                      ret = write_lines(out, banner: banner, sub: if verbose
         | 
| 821 | 
            +
                                                                    r = color(:red)
         | 
| 822 | 
            +
                                                                    g = color(:green)
         | 
| 698 823 | 
             
                                                                    [
         | 
| 699 | 
            -
                                                                      { pat: /^(.)([A-Z?!])(.+)$/, styles:  | 
| 700 | 
            -
                                                                      { pat: /^([A-Z?!])(.+)$/, styles:  | 
| 701 | 
            -
                                                                      { pat: /^(\?\?)(.+)$/, styles:  | 
| 824 | 
            +
                                                                      { pat: /^(.)([A-Z?!])(.+)$/, styles: r, index: 2 },
         | 
| 825 | 
            +
                                                                      { pat: /^([A-Z?!])(.+)$/, styles: g },
         | 
| 826 | 
            +
                                                                      { pat: /^(\?\?)(.+)$/, styles: r },
         | 
| 702 827 | 
             
                                                                      { pat: /^(## )(.+)(\.{3})(.+)$/,
         | 
| 703 | 
            -
                                                                        styles: [nil,  | 
| 828 | 
            +
                                                                        styles: [nil, g, nil, r], index: -1 }
         | 
| 704 829 | 
             
                                                                    ]
         | 
| 705 830 | 
             
                                                                  end)
         | 
| 706 831 | 
             
                      list_result(ret, 'files', from: from, action: 'modified')
         | 
| 707 832 | 
             
                    end
         | 
| 708 833 |  | 
| 834 | 
            +
                    def revbuild(flag = nil, opts = [], sync: invoked_sync?('revbuild', flag), **kwargs)
         | 
| 835 | 
            +
                      statusargs = lambda do
         | 
| 836 | 
            +
                        {
         | 
| 837 | 
            +
                          include: relativepath(as_a(kwargs[:include]), all: true),
         | 
| 838 | 
            +
                          exclude: relativepath(as_a(kwargs[:exclude]), all: true)
         | 
| 839 | 
            +
                        }
         | 
| 840 | 
            +
                      end
         | 
| 841 | 
            +
                      unless workspace.closed
         | 
| 842 | 
            +
                        if @revbuild
         | 
| 843 | 
            +
                          statusargs.().each { |key, val| @revbuild[key] += val }
         | 
| 844 | 
            +
                        else
         | 
| 845 | 
            +
                          @revbuild = statusargs.()
         | 
| 846 | 
            +
                        end
         | 
| 847 | 
            +
                        return
         | 
| 848 | 
            +
                      end
         | 
| 849 | 
            +
                      sha = source(git_output('rev-parse --verify HEAD'), io: true, banner: false, stdout: true).first.to_s.chomp
         | 
| 850 | 
            +
                      return if sha.empty?
         | 
| 851 | 
            +
             | 
| 852 | 
            +
                      args = []
         | 
| 853 | 
            +
                      kwargs = kwargs.key?(:include) || kwargs.key?(:exclude) ? statusargs.() : @revbuild || {}
         | 
| 854 | 
            +
                      case flag
         | 
| 855 | 
            +
                      when :build
         | 
| 856 | 
            +
                        opts = option_sanitize(opts, OPT_GIT[:status], target: args).first
         | 
| 857 | 
            +
                        option_clear opts
         | 
| 858 | 
            +
                      else
         | 
| 859 | 
            +
                        args << basic_option('untracked-files', flag) if (flag = option('untracked-files', prefix: 'git'))
         | 
| 860 | 
            +
                        args << basic_option('ignore-submodules', flag) if (flag = option('ignore-submodules', prefix: 'git'))
         | 
| 861 | 
            +
                        args << basic_option('ignored', flag) if (flag = option('ignored', prefix: 'git'))
         | 
| 862 | 
            +
                      end
         | 
| 863 | 
            +
                      if (cur = workspace.rev_entry(name)) && cur['revision'] == sha
         | 
| 864 | 
            +
                        files = status_digest(*args, **kwargs)
         | 
| 865 | 
            +
                        if cur['files'].size == files.size && cur['files'].find { |key, val| files[key] != val }.nil?
         | 
| 866 | 
            +
                          if verbose
         | 
| 867 | 
            +
                            if (since = workspace.rev_timesince(name, 'build'))
         | 
| 868 | 
            +
                              puts log_message(Logger::INFO, name, 'no changes', subject: 'revbuild', hint: "#{since} ago")
         | 
| 869 | 
            +
                            else
         | 
| 870 | 
            +
                              workspace.rev_timeutc(name, 'build')
         | 
| 871 | 
            +
                            end
         | 
| 872 | 
            +
                          end
         | 
| 873 | 
            +
                          return
         | 
| 874 | 
            +
                        end
         | 
| 875 | 
            +
                      end
         | 
| 876 | 
            +
                      start = epochtime
         | 
| 877 | 
            +
                      build(@output, sync: sync, from: :'git:revbuild')
         | 
| 878 | 
            +
                    rescue StandardError => e
         | 
| 879 | 
            +
                      warn log_message(Logger::WARN, e, pass: true) if warning?
         | 
| 880 | 
            +
                    else
         | 
| 881 | 
            +
                      if verbose
         | 
| 882 | 
            +
                        msg = sub_style('completed', styles: theme[:active])
         | 
| 883 | 
            +
                        puts log_message(Logger::INFO, name, msg, subject: 'revbuild', hint: time_format(epochtime - start))
         | 
| 884 | 
            +
                      end
         | 
| 885 | 
            +
                      workspace.rev_write(name, { 'revision' => sha, 'files' => status_digest(*args, **kwargs) }, utc: 'build')
         | 
| 886 | 
            +
                    end
         | 
| 887 | 
            +
             | 
| 709 888 | 
             
                    def reset(flag, opts = [], refs: nil, ref: nil, mode: nil, commit: nil)
         | 
| 710 | 
            -
                      cmd = git_session | 
| 889 | 
            +
                      cmd, opts = git_session('reset', opts: opts)
         | 
| 711 890 | 
             
                      case flag
         | 
| 712 891 | 
             
                      when :commit, :index
         | 
| 713 892 | 
             
                        out = option_sanitize(opts, OPT_GIT[:reset] + VAL_GIT[:reset], no: OPT_GIT[:no][:reset]).first
         | 
| @@ -732,21 +911,19 @@ module Squared | |
| 732 911 | 
             
                        return
         | 
| 733 912 | 
             
                      end
         | 
| 734 913 | 
             
                      unless ref == false
         | 
| 735 | 
            -
                        append_commit  | 
| 914 | 
            +
                        append_commit(ref, head: true)
         | 
| 736 915 | 
             
                        append_pathspec refs if refs
         | 
| 737 916 | 
             
                      end
         | 
| 738 917 | 
             
                      source
         | 
| 739 918 | 
             
                    end
         | 
| 740 919 |  | 
| 741 920 | 
             
                    def checkout(flag, opts = [], branch: nil, origin: nil, create: nil, commit: nil, detach: nil)
         | 
| 742 | 
            -
                      cmd = git_session | 
| 921 | 
            +
                      cmd, opts = git_session('checkout', opts: opts)
         | 
| 743 922 | 
             
                      append_option 'force', 'merge'
         | 
| 744 923 | 
             
                      case flag
         | 
| 745 924 | 
             
                      when :branch
         | 
| 746 925 | 
             
                        cmd << '--detach' if detach == 'd' || option('detach')
         | 
| 747 | 
            -
                         | 
| 748 | 
            -
                          cmd << shell_option('track', val)
         | 
| 749 | 
            -
                        end
         | 
| 926 | 
            +
                        append_option('track', equals: true)
         | 
| 750 927 | 
             
                        cmd << if create
         | 
| 751 928 | 
             
                                 shell_option(create, branch)
         | 
| 752 929 | 
             
                               else
         | 
| @@ -778,7 +955,7 @@ module Squared | |
| 778 955 | 
             
                    end
         | 
| 779 956 |  | 
| 780 957 | 
             
                    def tag(flag, opts = [], refs: [], message: nil, commit: nil)
         | 
| 781 | 
            -
                      cmd = git_session | 
| 958 | 
            +
                      cmd, opts = git_session('tag', opts: opts)
         | 
| 782 959 | 
             
                      case flag
         | 
| 783 960 | 
             
                      when :add
         | 
| 784 961 | 
             
                        if option('sign')
         | 
| @@ -786,8 +963,8 @@ module Squared | |
| 786 963 | 
             
                        elsif !session_arg?('s', 'sign', 'u', 'local-user')
         | 
| 787 964 | 
             
                          cmd << '--annotate'
         | 
| 788 965 | 
             
                        end
         | 
| 789 | 
            -
                        if !commit && message && ( | 
| 790 | 
            -
                          commit =  | 
| 966 | 
            +
                        if !commit && message && (sha = commithash(message))
         | 
| 967 | 
            +
                          commit = sha
         | 
| 791 968 | 
             
                        else
         | 
| 792 969 | 
             
                          append_message message
         | 
| 793 970 | 
             
                        end
         | 
| @@ -810,41 +987,27 @@ module Squared | |
| 810 987 | 
             
                      source
         | 
| 811 988 | 
             
                    end
         | 
| 812 989 |  | 
| 813 | 
            -
                    def logx(flag, opts = [], range: [])
         | 
| 814 | 
            -
                      cmd = git_session | 
| 815 | 
            -
                       | 
| 990 | 
            +
                    def logx(flag, opts = [], range: [], index: [])
         | 
| 991 | 
            +
                      cmd, opts = git_session('log', opts: opts)
         | 
| 992 | 
            +
                      refs = option_sanitize(opts, collect_hash(OPT_GIT[:log]), no: collect_hash(OPT_GIT[:no][:log])).first
         | 
| 816 993 | 
             
                      case flag
         | 
| 817 994 | 
             
                      when :between, :contain
         | 
| 818 995 | 
             
                        cmd << shell_quote(range.join(flag == :between ? '..' : '...'))
         | 
| 819 996 | 
             
                      else
         | 
| 820 | 
            -
                         | 
| 821 | 
            -
                          val.start_with?('^') || (!%r{^[.\\/]}.match?(val) && !%r{[\\/]$}.match?(val)) || commithash(val)
         | 
| 822 | 
            -
                        end
         | 
| 823 | 
            -
                        cmd.merge(commit.map { |val| commithash(val) || shell_quote(val) }) unless commit.empty?
         | 
| 997 | 
            +
                        cmd.merge(index)
         | 
| 824 998 | 
             
                      end
         | 
| 825 999 | 
             
                      append_nocolor
         | 
| 826 | 
            -
                      append_pathspec  | 
| 1000 | 
            +
                      append_pathspec refs
         | 
| 827 1001 | 
             
                      source(exception: false)
         | 
| 828 1002 | 
             
                    end
         | 
| 829 1003 |  | 
| 830 | 
            -
                    def diff(flag, opts = [], refs: [], branch: nil, range: [])
         | 
| 831 | 
            -
                      cmd = git_session | 
| 1004 | 
            +
                    def diff(flag, opts = [], refs: [], branch: nil, range: [], index: [])
         | 
| 1005 | 
            +
                      cmd, opts = git_session('diff', opts: opts)
         | 
| 832 1006 | 
             
                      files = option_sanitize(opts, collect_hash(OPT_GIT[:diff]) + OPT_GIT[:log][:diff],
         | 
| 833 1007 | 
             
                                              no: OPT_GIT[:no][:log][:diff]).first
         | 
| 834 1008 | 
             
                      case flag
         | 
| 835 1009 | 
             
                      when :files, :view, :between, :contain
         | 
| 836 1010 | 
             
                        cmd.delete('--cached')
         | 
| 837 | 
            -
                      else
         | 
| 838 | 
            -
                        items = files.dup
         | 
| 839 | 
            -
                        sha = nil
         | 
| 840 | 
            -
                        files.clear
         | 
| 841 | 
            -
                        items.each do |val|
         | 
| 842 | 
            -
                          if (s = commithash(val))
         | 
| 843 | 
            -
                            (sha ||= []).push(s)
         | 
| 844 | 
            -
                          else
         | 
| 845 | 
            -
                            files << val
         | 
| 846 | 
            -
                          end
         | 
| 847 | 
            -
                        end
         | 
| 848 1011 | 
             
                      end
         | 
| 849 1012 | 
             
                      append_nocolor
         | 
| 850 1013 | 
             
                      if flag == :files
         | 
| @@ -859,15 +1022,14 @@ module Squared | |
| 859 1022 | 
             
                          cmd.delete('--merge-base')
         | 
| 860 1023 | 
             
                          cmd << shell_quote(range.join(flag == :between ? '..' : '...'))
         | 
| 861 1024 | 
             
                        else
         | 
| 862 | 
            -
                          cmd << '--cached' if flag == :cached
         | 
| 863 1025 | 
             
                          cmd << '--merge-base' if option('merge-base')
         | 
| 864 1026 | 
             
                          cmd << shell_quote(branch) if branch
         | 
| 865 | 
            -
                          if  | 
| 1027 | 
            +
                          if !index.empty?
         | 
| 866 1028 | 
             
                            if session_arg?('cached')
         | 
| 867 | 
            -
                              raise_error( | 
| 868 | 
            -
                              cmd <<  | 
| 1029 | 
            +
                              raise_error("one commit only: #{index.join(', ')}", hint: '--cached') if index.size > 1
         | 
| 1030 | 
            +
                              cmd << index.first
         | 
| 869 1031 | 
             
                            else
         | 
| 870 | 
            -
                              cmd.merge( | 
| 1032 | 
            +
                              cmd.merge(index)
         | 
| 871 1033 | 
             
                            end
         | 
| 872 1034 | 
             
                          elsif (n = option('index'))
         | 
| 873 1035 | 
             
                            cmd << "HEAD~#{n}"
         | 
| @@ -884,12 +1046,12 @@ module Squared | |
| 884 1046 | 
             
                      if !message && !amend
         | 
| 885 1047 | 
             
                        return if pass
         | 
| 886 1048 |  | 
| 887 | 
            -
                        raise_error(' | 
| 1049 | 
            +
                        raise_error('missing message', hint: 'GIT_MESSAGE="description"')
         | 
| 888 1050 | 
             
                      end
         | 
| 889 1051 | 
             
                      pathspec = if flag == :all || (amend && refs.size == 1 && refs.first == '*')
         | 
| 890 1052 | 
             
                                   '--all'
         | 
| 891 1053 | 
             
                                 elsif (refs = projectmap(refs)).empty?
         | 
| 892 | 
            -
                                   raise_error | 
| 1054 | 
            +
                                   raise_error 'no qualified pathspec'
         | 
| 893 1055 | 
             
                                 else
         | 
| 894 1056 | 
             
                                   "-- #{refs.join(' ')}"
         | 
| 895 1057 | 
             
                                 end
         | 
| @@ -898,13 +1060,15 @@ module Squared | |
| 898 1060 | 
             
                      upstream = nil
         | 
| 899 1061 | 
             
                      source(git_output('fetch --no-tags --quiet'), io: true, banner: false)
         | 
| 900 1062 | 
             
                      source(git_output('branch -vv --list'), io: true, banner: false).first.each do |val|
         | 
| 901 | 
            -
                        next unless ( | 
| 1063 | 
            +
                        next unless (r = /^\*\s(\S+)\s+(\h+)(?:\s\[(.+?)(?=\]\s)\])?\s/.match(val))
         | 
| 902 1064 |  | 
| 903 | 
            -
                        branch =  | 
| 904 | 
            -
                        if  | 
| 1065 | 
            +
                        branch = r[1]
         | 
| 1066 | 
            +
                        if r[3]
         | 
| 1067 | 
            +
                          origin = r[3][%r{^(.+)/#{Regexp.escape(branch)}$}, 1]
         | 
| 1068 | 
            +
                        else
         | 
| 905 1069 | 
             
                          unless (origin = option('repository', prefix: 'git', ignore: false))
         | 
| 906 | 
            -
                            out = source(git_output('log -n1 --format=%h%d'), io: true,  | 
| 907 | 
            -
                            if out =~ /^#{ | 
| 1070 | 
            +
                            out = source(git_output('log -n1 --format=%h%d'), io: true, banner: false, stdout: true).first
         | 
| 1071 | 
            +
                            if out =~ /^#{r[2]} \(HEAD -> #{Regexp.escape(branch)}, (.+?)\)$/
         | 
| 908 1072 | 
             
                              split_escape($1).each do |val|
         | 
| 909 1073 | 
             
                                next unless val.end_with?("/#{branch}")
         | 
| 910 1074 |  | 
| @@ -914,12 +1078,10 @@ module Squared | |
| 914 1078 | 
             
                            end
         | 
| 915 1079 | 
             
                          end
         | 
| 916 1080 | 
             
                          upstream = true if origin
         | 
| 917 | 
            -
                        elsif data[3] =~ %r{^(.+)/#{Regexp.escape(branch)}$}
         | 
| 918 | 
            -
                          origin = $1
         | 
| 919 1081 | 
             
                        end
         | 
| 920 1082 | 
             
                        break
         | 
| 921 1083 | 
             
                      end
         | 
| 922 | 
            -
                      raise_error | 
| 1084 | 
            +
                      raise_error 'work tree is not usable' unless origin && branch
         | 
| 923 1085 | 
             
                      cmd = git_session('commit', option('dry-run') && '--dry-run', options: false)
         | 
| 924 1086 | 
             
                      if amend
         | 
| 925 1087 | 
             
                        cmd << '--amend'
         | 
| @@ -946,8 +1108,24 @@ module Squared | |
| 946 1108 | 
             
                      source b
         | 
| 947 1109 | 
             
                    end
         | 
| 948 1110 |  | 
| 949 | 
            -
                    def  | 
| 950 | 
            -
                      cmd = git_session | 
| 1111 | 
            +
                    def merge(flag, opts = [], command: nil)
         | 
| 1112 | 
            +
                      cmd, opts = git_session('merge', opts: opts)
         | 
| 1113 | 
            +
                      case flag
         | 
| 1114 | 
            +
                      when :commit, :'no-commit'
         | 
| 1115 | 
            +
                        refs = option_sanitize(opts, OPT_GIT[:merge], no: OPT_GIT[:no][:merge]).first
         | 
| 1116 | 
            +
                        raise_error 'no branch/commit' if refs.empty?
         | 
| 1117 | 
            +
                        cmd << "--#{flag}" << '--'
         | 
| 1118 | 
            +
                        append_commit(*refs)
         | 
| 1119 | 
            +
                      else
         | 
| 1120 | 
            +
                        return unless VAL_GIT[:merge][:send].include?(command)
         | 
| 1121 | 
            +
             | 
| 1122 | 
            +
                        cmd << "--#{command}"
         | 
| 1123 | 
            +
                      end
         | 
| 1124 | 
            +
                      source
         | 
| 1125 | 
            +
                    end
         | 
| 1126 | 
            +
             | 
| 1127 | 
            +
                    def branch(flag = nil, opts = [], refs: [], ref: nil, target: nil)
         | 
| 1128 | 
            +
                      cmd, opts = git_session('branch', opts: opts)
         | 
| 951 1129 | 
             
                      stdout = false
         | 
| 952 1130 | 
             
                      case flag
         | 
| 953 1131 | 
             
                      when :create
         | 
| @@ -974,7 +1152,7 @@ module Squared | |
| 974 1152 | 
             
                        end
         | 
| 975 1153 | 
             
                        ref = nil
         | 
| 976 1154 | 
             
                      when :delete
         | 
| 977 | 
            -
                        force, list = refs.partition { |val| val | 
| 1155 | 
            +
                        force, list = refs.partition { |val| val.match?(/^[\^~]/) }
         | 
| 978 1156 | 
             
                        force.each do |val|
         | 
| 979 1157 | 
             
                          dr = val[0, 3]
         | 
| 980 1158 | 
             
                          d = dr.include?('^') ? '-D' : '-d'
         | 
| @@ -994,10 +1172,9 @@ module Squared | |
| 994 1172 | 
             
                        cmd << '--edit-description'
         | 
| 995 1173 | 
             
                      when :current
         | 
| 996 1174 | 
             
                        cmd << '--show-current'
         | 
| 997 | 
            -
                       | 
| 998 | 
            -
                        opts = option_sanitize(opts, OPT_GIT[:branch], no: OPT_GIT[:no][:branch]).first
         | 
| 1175 | 
            +
                      when :list
         | 
| 999 1176 | 
             
                        grep = []
         | 
| 1000 | 
            -
                        opts.each do |opt|
         | 
| 1177 | 
            +
                        option_sanitize(opts, OPT_GIT[:branch], no: OPT_GIT[:no][:branch]).first.each do |opt|
         | 
| 1001 1178 | 
             
                          if opt =~ /^(v+)$/
         | 
| 1002 1179 | 
             
                            cmd << "-#{$1}"
         | 
| 1003 1180 | 
             
                          else
         | 
| @@ -1009,42 +1186,46 @@ module Squared | |
| 1009 1186 | 
             
                        out, banner, from = source(io: true)
         | 
| 1010 1187 | 
             
                        print_item banner
         | 
| 1011 1188 | 
             
                        ret = write_lines(out, sub: [
         | 
| 1012 | 
            -
                          { pat: /^(\*\s+)(\S+)(\s*)$/, styles: :green, index: 2 },
         | 
| 1013 | 
            -
                          { pat: %r{^(\s*)(remotes/\S+)(.*)$}, styles: :red, index: 2 }
         | 
| 1189 | 
            +
                          { pat: /^(\*\s+)(\S+)(\s*)$/, styles: color(:green), index: 2 },
         | 
| 1190 | 
            +
                          { pat: %r{^(\s*)(remotes/\S+)(.*)$}, styles: color(:red), index: 2 }
         | 
| 1014 1191 | 
             
                        ])
         | 
| 1015 1192 | 
             
                        list_result(ret, 'branches', from: from)
         | 
| 1016 1193 | 
             
                        return
         | 
| 1194 | 
            +
                      else
         | 
| 1195 | 
            +
                        head = source(git_output('rev-parse --abbrev-ref HEAD'), io: true, banner: false, stdout: true).first.chomp
         | 
| 1196 | 
            +
                        if head.empty?
         | 
| 1197 | 
            +
                          ret = 0
         | 
| 1198 | 
            +
                        else
         | 
| 1199 | 
            +
                          out, banner, from = source(cmd << '-vv --no-abbrev --list', io: true)
         | 
| 1200 | 
            +
                          ret = write_lines(out, grep: /^\*\s+#{Regexp.escape(head)}\s/, banner: banner, first: true) do |line|
         | 
| 1201 | 
            +
                            next line if stdin?
         | 
| 1202 | 
            +
             | 
| 1203 | 
            +
                            data = line.sub(/^\*\s+/, '').split(/\s+/)
         | 
| 1204 | 
            +
                            a = sub_style(data[0], styles: theme[:inline])
         | 
| 1205 | 
            +
                            b = sub_style(data[1], styles: theme[:extra])
         | 
| 1206 | 
            +
                            r = /\A(?:\[(.+?)(?=\]\s)\]\s)?(.+)\z/m.match(data[2..-1].join(' '))
         | 
| 1207 | 
            +
                            [" Branch: #{a + (r[1] ? " (#{r[1]})" : '')}", " Commit: #{b}", "Message: #{r[2]}"].compact.join("\n")
         | 
| 1208 | 
            +
                          end
         | 
| 1209 | 
            +
                          on :last, from
         | 
| 1210 | 
            +
                        end
         | 
| 1211 | 
            +
                        if ret == 0
         | 
| 1212 | 
            +
                          warn log_message(Logger::WARN, name, 'no ref found', subject: 'branch', hint: 'head', pass: true)
         | 
| 1213 | 
            +
                        end
         | 
| 1214 | 
            +
                        return
         | 
| 1017 1215 | 
             
                      end
         | 
| 1018 1216 | 
             
                      cmd << shell_escape(target) if target
         | 
| 1019 1217 | 
             
                      cmd << shell_escape(ref) if ref
         | 
| 1020 1218 | 
             
                      source(stdout: stdout)
         | 
| 1021 1219 | 
             
                    end
         | 
| 1022 1220 |  | 
| 1023 | 
            -
                    def restore(flag, opts = [], tree: nil)
         | 
| 1024 | 
            -
                      cmd = git_session 'restore'
         | 
| 1025 | 
            -
                      refs = option_sanitize(opts, OPT_GIT[:restore], no: OPT_GIT[:no][:restore]).first
         | 
| 1026 | 
            -
                      if flag == :source
         | 
| 1027 | 
            -
                        cmd << '--patch' if refs.empty?
         | 
| 1028 | 
            -
                        cmd << shell_option('source', tree)
         | 
| 1029 | 
            -
                      else
         | 
| 1030 | 
            -
                        cmd << "--#{flag}"
         | 
| 1031 | 
            -
                      end
         | 
| 1032 | 
            -
                      if session_arg?('p', 'patch')
         | 
| 1033 | 
            -
                        option_clear refs
         | 
| 1034 | 
            -
                      else
         | 
| 1035 | 
            -
                        append_pathspec(refs, expect: true)
         | 
| 1036 | 
            -
                      end
         | 
| 1037 | 
            -
                      source(sync: false, stderr: true)
         | 
| 1038 | 
            -
                    end
         | 
| 1039 | 
            -
             | 
| 1040 1221 | 
             
                    def show(format, opts = [])
         | 
| 1041 | 
            -
                      cmd = git_session | 
| 1222 | 
            +
                      cmd, opts = git_session('show', opts: opts)
         | 
| 1042 1223 | 
             
                      if format
         | 
| 1043 1224 | 
             
                        case (val = format.downcase)
         | 
| 1044 1225 | 
             
                        when 'oneline', 'short', 'medium', 'full', 'fuller', 'reference', 'email', 'raw'
         | 
| 1045 1226 | 
             
                          cmd << basic_option('format', val)
         | 
| 1046 1227 | 
             
                        else
         | 
| 1047 | 
            -
                          if format | 
| 1228 | 
            +
                          if format.match?(/^t?format:/) || format.include?('%')
         | 
| 1048 1229 | 
             
                            cmd << quote_option('pretty', format)
         | 
| 1049 1230 | 
             
                          else
         | 
| 1050 1231 | 
             
                            opts << format
         | 
| @@ -1062,18 +1243,19 @@ module Squared | |
| 1062 1243 | 
             
                    end
         | 
| 1063 1244 |  | 
| 1064 1245 | 
             
                    def rev_parse(flag, opts = [], ref: nil, size: nil)
         | 
| 1065 | 
            -
                      cmd = git_session | 
| 1066 | 
            -
             | 
| 1067 | 
            -
             | 
| 1068 | 
            -
             | 
| 1069 | 
            -
             | 
| 1246 | 
            +
                      cmd, opts = git_session('rev-parse', opts: opts)
         | 
| 1247 | 
            +
                      cmd << if flag == :parseopt
         | 
| 1248 | 
            +
                               '--parseopt'
         | 
| 1249 | 
            +
                             elsif opts.delete('sq-quote')
         | 
| 1250 | 
            +
                               '--sq-quote'
         | 
| 1251 | 
            +
                             end
         | 
| 1070 1252 | 
             
                      case flag
         | 
| 1071 1253 | 
             
                      when :commit
         | 
| 1072 1254 | 
             
                        cmd << ((n = size.to_i) > 0 ? basic_option('short', [n, 5].max) : '--verify')
         | 
| 1073 | 
            -
                        append_commit  | 
| 1255 | 
            +
                        append_commit(ref, head: true)
         | 
| 1074 1256 | 
             
                      when :branch
         | 
| 1075 1257 | 
             
                        cmd << '--abbrev-ref'
         | 
| 1076 | 
            -
                        append_commit  | 
| 1258 | 
            +
                        append_commit(ref, head: true)
         | 
| 1077 1259 | 
             
                      else
         | 
| 1078 1260 | 
             
                        args = option_sanitize(opts, OPT_GIT[:rev_parse][flag]).first
         | 
| 1079 1261 | 
             
                        append_value(args, escape: session_arg?('sq-quote'))
         | 
| @@ -1082,7 +1264,7 @@ module Squared | |
| 1082 1264 | 
             
                    end
         | 
| 1083 1265 |  | 
| 1084 1266 | 
             
                    def ls_remote(flag, opts = [], remote: nil)
         | 
| 1085 | 
            -
                      cmd = git_session | 
| 1267 | 
            +
                      cmd, opts = git_session('ls-remote', '--refs', opts: opts)
         | 
| 1086 1268 | 
             
                      cmd << "--#{flag}" unless flag == :remote
         | 
| 1087 1269 | 
             
                      grep = option_sanitize(opts, OPT_GIT[:ls_remote]).first
         | 
| 1088 1270 | 
             
                      cmd << shell_quote(remote) if remote
         | 
| @@ -1093,7 +1275,7 @@ module Squared | |
| 1093 1275 | 
             
                    end
         | 
| 1094 1276 |  | 
| 1095 1277 | 
             
                    def ls_files(flag, opts = [])
         | 
| 1096 | 
            -
                      git_session | 
| 1278 | 
            +
                      opts = git_session('ls-files', "--#{flag}", opts: opts).last
         | 
| 1097 1279 | 
             
                      grep = option_sanitize(opts, OPT_GIT[:ls_files]).first
         | 
| 1098 1280 | 
             
                      out, banner, from = source(io: true)
         | 
| 1099 1281 | 
             
                      print_item banner
         | 
| @@ -1101,10 +1283,51 @@ module Squared | |
| 1101 1283 | 
             
                      list_result(ret, 'files', from: from, grep: grep)
         | 
| 1102 1284 | 
             
                    end
         | 
| 1103 1285 |  | 
| 1286 | 
            +
                    def git(flag, opts = [])
         | 
| 1287 | 
            +
                      cmd, opts = git_session(flag, opts: opts)
         | 
| 1288 | 
            +
                      refs = option_sanitize(opts, OPT_GIT[flag], no: OPT_GIT[:no][flag]).first
         | 
| 1289 | 
            +
                      refs = projectmap(refs) unless flag == :revert
         | 
| 1290 | 
            +
                      sync = false
         | 
| 1291 | 
            +
                      stderr = true
         | 
| 1292 | 
            +
                      case flag
         | 
| 1293 | 
            +
                      when :clean
         | 
| 1294 | 
            +
                        unless refs.empty?
         | 
| 1295 | 
            +
                          cmd << '--'
         | 
| 1296 | 
            +
                          cmd.merge(refs)
         | 
| 1297 | 
            +
                        end
         | 
| 1298 | 
            +
                        sync = true
         | 
| 1299 | 
            +
                        stderr = false
         | 
| 1300 | 
            +
                      when :restore
         | 
| 1301 | 
            +
                        if session_arg?('p', 'patch')
         | 
| 1302 | 
            +
                          option_clear refs
         | 
| 1303 | 
            +
                        else
         | 
| 1304 | 
            +
                          append_pathspec(refs, expect: true)
         | 
| 1305 | 
            +
                        end
         | 
| 1306 | 
            +
                      when :revert
         | 
| 1307 | 
            +
                        if VAL_GIT[:rebase][:send].any? { |val| session_arg?(val) }
         | 
| 1308 | 
            +
                          option_clear refs
         | 
| 1309 | 
            +
                        elsif refs.empty?
         | 
| 1310 | 
            +
                          raise_error 'no commit given'
         | 
| 1311 | 
            +
                        else
         | 
| 1312 | 
            +
                          append_commit(*refs)
         | 
| 1313 | 
            +
                        end
         | 
| 1314 | 
            +
                      when :mv
         | 
| 1315 | 
            +
                        raise_error 'no source/destination' unless refs.size > 1
         | 
| 1316 | 
            +
                        cmd.merge(refs)
         | 
| 1317 | 
            +
                      when :rm
         | 
| 1318 | 
            +
                        append_pathspec(refs, expect: true)
         | 
| 1319 | 
            +
                      end
         | 
| 1320 | 
            +
                      source(sync: sync, stderr: stderr)
         | 
| 1321 | 
            +
                    end
         | 
| 1322 | 
            +
             | 
| 1104 1323 | 
             
                    def clone?
         | 
| 1105 1324 | 
             
                      ref?(workspace.baseref) && workspace.git_clone?(path, name) ? 1 : false
         | 
| 1106 1325 | 
             
                    end
         | 
| 1107 1326 |  | 
| 1327 | 
            +
                    def revbuild?
         | 
| 1328 | 
            +
                      build? && !!workspace.revfile
         | 
| 1329 | 
            +
                    end
         | 
| 1330 | 
            +
             | 
| 1108 1331 | 
             
                    def enabled?(*, **kwargs)
         | 
| 1109 1332 | 
             
                      super || (kwargs[:base] == false && !!clone?)
         | 
| 1110 1333 | 
             
                    end
         | 
| @@ -1117,7 +1340,7 @@ module Squared | |
| 1117 1340 | 
             
                      if cmd.respond_to?(:done)
         | 
| 1118 1341 | 
             
                        if io && banner == false
         | 
| 1119 1342 | 
             
                          from = nil
         | 
| 1120 | 
            -
                        elsif !from && (from = cmd.drop(1).find { |val| val | 
| 1343 | 
            +
                        elsif !from && (from = cmd.drop(1).find { |val| val.match?(/^[a-z][a-z\-]{2,}$/) })
         | 
| 1121 1344 | 
             
                          from = :"git:#{from}"
         | 
| 1122 1345 | 
             
                        end
         | 
| 1123 1346 | 
             
                        banner &&= cmd.temp { |val| val.start_with?('--work-tree') || val.start_with?('--git-dir') }
         | 
| @@ -1163,13 +1386,13 @@ module Squared | |
| 1163 1386 | 
             
                        ret = on(:error, from, e)
         | 
| 1164 1387 | 
             
                        raise if exception && ret != true
         | 
| 1165 1388 |  | 
| 1166 | 
            -
                        warn log_message(Logger::WARN, e) if warning?
         | 
| 1389 | 
            +
                        warn log_message(Logger::WARN, e, pass: true) if warning?
         | 
| 1167 1390 | 
             
                      else
         | 
| 1168 1391 | 
             
                        on :last, from
         | 
| 1169 1392 | 
             
                      end
         | 
| 1170 1393 | 
             
                    end
         | 
| 1171 1394 |  | 
| 1172 | 
            -
                    def write_lines(data, banner: nil, loglevel: nil, grep: nil, sub: nil, pass: false)
         | 
| 1395 | 
            +
                    def write_lines(data, banner: nil, loglevel: nil, grep: nil, sub: nil, pass: false, first: false)
         | 
| 1173 1396 | 
             
                      grep = as_a(grep).map do |val|
         | 
| 1174 1397 | 
             
                        next val if val.is_a?(Regexp)
         | 
| 1175 1398 |  | 
| @@ -1182,6 +1405,7 @@ module Squared | |
| 1182 1405 | 
             
                      data.each do |line|
         | 
| 1183 1406 | 
             
                        next if grep&.none? { |pat| pat.match?(line) }
         | 
| 1184 1407 |  | 
| 1408 | 
            +
                        line = yield line if block_given?
         | 
| 1185 1409 | 
             
                        if loglevel
         | 
| 1186 1410 | 
             
                          log&.add loglevel, line
         | 
| 1187 1411 | 
             
                        else
         | 
| @@ -1193,8 +1417,9 @@ module Squared | |
| 1193 1417 | 
             
                          end
         | 
| 1194 1418 | 
             
                        end
         | 
| 1195 1419 | 
             
                        ret += 1
         | 
| 1420 | 
            +
                        break if first
         | 
| 1196 1421 | 
             
                      end
         | 
| 1197 | 
            -
                      print_item banner, out if banner && (ret > 0 || !pass)
         | 
| 1422 | 
            +
                      print_item banner, out if banner && (ret > 0 || (!pass && !first))
         | 
| 1198 1423 | 
             
                      ret
         | 
| 1199 1424 | 
             
                    end
         | 
| 1200 1425 |  | 
| @@ -1217,6 +1442,21 @@ module Squared | |
| 1217 1442 | 
             
                      on :last, from
         | 
| 1218 1443 | 
             
                    end
         | 
| 1219 1444 |  | 
| 1445 | 
            +
                    def status_digest(*args, algorithm: Digest::SHA256, **kwargs)
         | 
| 1446 | 
            +
                      glob = kwargs.fetch(:include, [])
         | 
| 1447 | 
            +
                      pass = kwargs.fetch(:exclude, [])
         | 
| 1448 | 
            +
                      ret = {}
         | 
| 1449 | 
            +
                      out = source(git_output('status -s --porcelain', *args), io: true, banner: false).first
         | 
| 1450 | 
            +
                      out.each do |line|
         | 
| 1451 | 
            +
                        next unless (file = line[/^[A-Z ?!]{3}"?(.+?)"?$/, 1])
         | 
| 1452 | 
            +
                        next if !glob.empty? && glob.none? { |val| File.fnmatch?(val, file, File::FNM_DOTMATCH) }
         | 
| 1453 | 
            +
                        next if !pass.empty? && pass.any? { |val| File.fnmatch?(val, file, File::FNM_DOTMATCH) }
         | 
| 1454 | 
            +
             | 
| 1455 | 
            +
                        ret[file] = algorithm.hexdigest(File.read(basepath(file)))
         | 
| 1456 | 
            +
                      end
         | 
| 1457 | 
            +
                      ret
         | 
| 1458 | 
            +
                    end
         | 
| 1459 | 
            +
             | 
| 1220 1460 | 
             
                    def append_pull(opts, list, target: @session, no: nil, flag: nil, remote: nil)
         | 
| 1221 1461 | 
             
                      cmd << '--force' if option('force')
         | 
| 1222 1462 | 
             
                      rsm = append_submodules(target: target)
         | 
| @@ -1256,9 +1496,13 @@ module Squared | |
| 1256 1496 | 
             
                      option_clear(out, target: target, subject: flag.to_s) if flag
         | 
| 1257 1497 | 
             
                    end
         | 
| 1258 1498 |  | 
| 1259 | 
            -
                    def append_commit(val, target: @session)
         | 
| 1260 | 
            -
                      val | 
| 1261 | 
            -
                       | 
| 1499 | 
            +
                    def append_commit(*val, target: @session, head: false)
         | 
| 1500 | 
            +
                      val.compact!
         | 
| 1501 | 
            +
                      if !val.empty?
         | 
| 1502 | 
            +
                        val.each { |ref| target << (commithash(ref) || shell_quote(ref)) }
         | 
| 1503 | 
            +
                      elsif head
         | 
| 1504 | 
            +
                        target << (append_head(target: target) || 'HEAD')
         | 
| 1505 | 
            +
                      end
         | 
| 1262 1506 | 
             
                    end
         | 
| 1263 1507 |  | 
| 1264 1508 | 
             
                    def append_pathspec(files = [], target: @session, expect: false, parent: false)
         | 
| @@ -1270,9 +1514,9 @@ module Squared | |
| 1270 1514 | 
             
                        end
         | 
| 1271 1515 | 
             
                        files = projectmap(files, parent: parent)
         | 
| 1272 1516 | 
             
                        if !files.empty?
         | 
| 1273 | 
            -
                          target <<  | 
| 1517 | 
            +
                          target << '--' << files.join(' ')
         | 
| 1274 1518 | 
             
                        elsif expect
         | 
| 1275 | 
            -
                          raise_error(parent ? 'pathspec not present' : 'pathspec not within worktree' | 
| 1519 | 
            +
                          raise_error(parent ? 'pathspec not present' : 'pathspec not within worktree')
         | 
| 1276 1520 | 
             
                        end
         | 
| 1277 1521 | 
             
                      end
         | 
| 1278 1522 | 
             
                    end
         | 
| @@ -1307,9 +1551,13 @@ module Squared | |
| 1307 1551 | 
             
                      end
         | 
| 1308 1552 | 
             
                    end
         | 
| 1309 1553 |  | 
| 1310 | 
            -
                    def git_session(*cmd, worktree: true, **kwargs)
         | 
| 1554 | 
            +
                    def git_session(*cmd, opts: nil, worktree: true, **kwargs)
         | 
| 1311 1555 | 
             
                      dir = worktree ? ["--work-tree=#{shell_quote(path)}", "--git-dir=#{shell_quote(gitpath)}"] : []
         | 
| 1312 | 
            -
                      session('git', *dir,  | 
| 1556 | 
            +
                      ret = session('git', *dir, **kwargs)
         | 
| 1557 | 
            +
                      return ret.merge(cmd) unless opts
         | 
| 1558 | 
            +
             | 
| 1559 | 
            +
                      opts = option_sanitize(opts, OPT_GIT[:common]).first
         | 
| 1560 | 
            +
                      [ret.merge(cmd), opts]
         | 
| 1313 1561 | 
             
                    end
         | 
| 1314 1562 |  | 
| 1315 1563 | 
             
                    def git_output(*cmd, **kwargs)
         | 
| @@ -1327,11 +1575,11 @@ module Squared | |
| 1327 1575 | 
             
                    end
         | 
| 1328 1576 |  | 
| 1329 1577 | 
             
                    def gitpath
         | 
| 1330 | 
            -
                      basepath | 
| 1578 | 
            +
                      basepath '.git'
         | 
| 1331 1579 | 
             
                    end
         | 
| 1332 1580 |  | 
| 1333 1581 | 
             
                    def commithash(val)
         | 
| 1334 | 
            -
                      val | 
| 1582 | 
            +
                      val[/^#\{(\h{5,40})\}$/, 1]
         | 
| 1335 1583 | 
             
                    end
         | 
| 1336 1584 |  | 
| 1337 1585 | 
             
                    def threadargs
         |