Class: RBS::Collection::Sources::Git
- Inherits:
-
Object
- Object
- RBS::Collection::Sources::Git
- Includes:
- Base
- Defined in:
- lib/rbs/collection/sources/git.rb,
sig/collection/sources.rbs
Defined Under Namespace
Classes: CommandError
Constant Summary collapse
- METADATA_FILENAME =
'.rbs_meta.yaml'
Instance Attribute Summary collapse
-
#name ⇒ String
readonly
Returns the value of attribute name.
-
#remote ⇒ String
readonly
Returns the value of attribute remote.
-
#repo_dir ⇒ String
readonly
Returns the value of attribute repo_dir.
-
#revision ⇒ String
readonly
Returns the value of attribute revision.
Instance Method Summary collapse
- #_install(dest:, name:, version:) ⇒ void
-
#commit_hash? ⇒ Boolean
Returns
trueifrevisionlooks like a commit hash. - #cp_r(src, dest) ⇒ void
- #format_config_entry(name, version) ⇒ String
-
#gem_repo_dir ⇒ Pathname
The full path of
repo_dirin the local git repository. - #gems_versions ⇒ Hash[String, Set[String]]
-
#git(*cmd, **opt) ⇒ String
Executes a git command, raises an error if failed.
-
#git?(*cmd, **opt) ⇒ String?
Executes a git command, returns
nilif failed. -
#git_dir ⇒ Pathname
The full path of local git repository.
- #has?(name, version) ⇒ Boolean
-
#initialize(name:, revision:, remote:, repo_dir:) ⇒ Git
constructor
A new instance of Git.
- #install(dest:, name:, version:, stdout:) ⇒ void
-
#load_metadata(dir:) ⇒ metadata
Load
.rbs_meta.yamlfrom thedir, where is the destination of the RBS file installation, and return the cleaned up content. - #manifest_of(name, version) ⇒ manifest_entry?
- #metadata_content(name:, version:) ⇒ metadata
- #need_to_fetch?(revision) ⇒ Boolean
- #resolved_revision ⇒ String
-
#setup! ⇒ Object
Ensure the git repository exists, and.
- #sh!(*cmd, **opt) ⇒ String
- #to_lockfile ⇒ source_entry
- #versions(name) ⇒ Array[String]
-
#write_metadata(dir:, name:, version:) ⇒ void
Write
.rbs_meta.yaml.
Methods included from Base
Constructor Details
#initialize(name:, revision:, remote:, repo_dir:) ⇒ Git
Returns a new instance of Git.
19 20 21 22 23 24 25 |
# File 'lib/rbs/collection/sources/git.rb', line 19 def initialize(name:, revision:, remote:, repo_dir:) @name = name @remote = remote @repo_dir = repo_dir || 'gems' @revision = revision @need_setup = true end |
Instance Attribute Details
#name ⇒ String (readonly)
Returns the value of attribute name.
17 18 19 |
# File 'lib/rbs/collection/sources/git.rb', line 17 def name @name end |
#remote ⇒ String (readonly)
Returns the value of attribute remote.
17 18 19 |
# File 'lib/rbs/collection/sources/git.rb', line 17 def remote @remote end |
#repo_dir ⇒ String (readonly)
Returns the value of attribute repo_dir.
17 18 19 |
# File 'lib/rbs/collection/sources/git.rb', line 17 def repo_dir @repo_dir end |
#revision ⇒ String (readonly)
Returns the value of attribute revision.
17 18 19 |
# File 'lib/rbs/collection/sources/git.rb', line 17 def revision @revision end |
Instance Method Details
#_install(dest:, name:, version:) ⇒ void
This method returns an undefined value.
88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/rbs/collection/sources/git.rb', line 88 private def _install(dest:, name:, version:) # Should checkout that revision to support symlinks git("reset", "--hard", resolved_revision) dir = dest.join(name, version) dir.mkpath src = gem_repo_dir.join(name, version) cp_r(src, dir) (dir: dir, name: name, version: version) end |
#commit_hash? ⇒ Boolean
Returns true if revision looks like a commit hash
104 105 106 |
# File 'sig/collection/sources.rbs', line 104 private def commit_hash? revision.match?(/\A[a-f0-9]{40}\z/) end |
#cp_r(src, dest) ⇒ void
This method returns an undefined value.
100 101 102 103 104 105 106 107 108 109 110 111 112 |
# File 'lib/rbs/collection/sources/git.rb', line 100 private def cp_r(src, dest) Find.find(src) do |file_src| file_src = Pathname(file_src) # Skip file if it starts with _, such as _test/ Find.prune if file_src.basename.to_s.start_with?('_') file_src_relative = file_src.relative_path_from(src) file_dest = dest.join(file_src_relative) file_dest.dirname.mkpath FileUtils.copy_entry(file_src, file_dest, false, true) unless file_src.directory? end end |
#format_config_entry(name, version) ⇒ String
124 125 126 127 128 129 |
# File 'lib/rbs/collection/sources/git.rb', line 124 private def format_config_entry(name, version) rev = resolved_revision[0..10] desc = "#{name}@#{rev}" "#{name}:#{version} (#{desc})" end |
#gem_repo_dir ⇒ Pathname
The full path of repo_dir in the local git repository
101 102 103 |
# File 'sig/collection/sources.rbs', line 101 private def gem_repo_dir git_dir.join @repo_dir end |
#gems_versions ⇒ Hash[String, Set[String]]
236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 |
# File 'lib/rbs/collection/sources/git.rb', line 236 private def gems_versions @gems_versions ||= begin repo_path = Pathname(repo_dir) paths = git('ls-tree', '--full-tree', '-dr', '--name-only', '-z', resolved_revision, File.join(repo_dir, "")).split("\0").map {|line| Pathname(line) } # @type var versions: Hash[String, Set[String]] versions = {} paths.each do |full_path| path = full_path.relative_path_from(repo_path) gem_name, version = path.descend.take(2) if gem_name versions[gem_name.to_s] ||= Set[] if version && !version.basename.to_s.start_with?('_') versions[gem_name.to_s] << version.basename.to_s end end end versions end end |
#git(*cmd, **opt) ⇒ String
Executes a git command, raises an error if failed
107 108 109 |
# File 'sig/collection/sources.rbs', line 107 private def git(*cmd, **opt) sh! 'git', *cmd, **opt end |
#git?(*cmd, **opt) ⇒ String?
Executes a git command, returns nil if failed
110 111 112 113 114 |
# File 'sig/collection/sources.rbs', line 110 private def git?(*cmd, **opt) git(*cmd, **opt) rescue CommandError nil end |
#git_dir ⇒ Pathname
The full path of local git repository
98 99 100 101 102 103 104 105 106 |
# File 'sig/collection/sources.rbs', line 98 private def git_dir @git_dir ||= ( base = Pathname(ENV['XDG_CACHE_HOME'] || File.("~/.cache")) cache_key = remote.start_with?('.') ? "#{remote}\0#{Dir.pwd}" : remote dir = base.join('rbs', Digest::SHA256.hexdigest(cache_key)) dir.mkpath dir ) end |
#has?(name, version) ⇒ Boolean
27 28 29 30 31 32 33 34 35 |
# File 'lib/rbs/collection/sources/git.rb', line 27 def has?(name, version) setup! do if version (gems_versions[name] || Set[]).include?(version) else gems_versions.key?(name) end end end |
#install(dest:, name:, version:, stdout:) ⇒ void
This method returns an undefined value.
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
# File 'lib/rbs/collection/sources/git.rb', line 44 def install(dest:, name:, version:, stdout:) setup!() gem_dir = dest.join(name, version) colored_io = CLI::ColoredIO.new(stdout: stdout) case when gem_dir.symlink? colored_io.puts_green("Updating to #{format_config_entry(name, version)} from a local source") gem_dir.unlink _install(dest: dest, name: name, version: version) when gem_dir.directory? prev = (dir: gem_dir) if prev == (name: name, version: version) colored_io.puts "Using #{format_config_entry(name, version)}" else colored_io.puts_green("Updating to #{format_config_entry(name, version)} from #{format_config_entry(prev["name"], prev["version"])}") FileUtils.remove_entry_secure(gem_dir.to_s) _install(dest: dest, name: name, version: version) end when !gem_dir.exist? colored_io.puts_green("Installing #{format_config_entry(name, version)}") _install(dest: dest, name: name, version: version) else raise end end |
#load_metadata(dir:) ⇒ metadata
Load .rbs_meta.yaml from the dir, where is the destination of the RBS file installation, and return the cleaned up content
125 126 127 128 129 |
# File 'sig/collection/sources.rbs', line 125 def (dir:) # @type var content: Hash[String, untyped] content = YAML.load_file(dir.join(METADATA_FILENAME).to_s) _ = content.slice("name", "version", "source") end |
#manifest_of(name, version) ⇒ manifest_entry?
74 75 76 77 78 79 80 81 82 83 84 85 86 |
# File 'lib/rbs/collection/sources/git.rb', line 74 def manifest_of(name, version) setup! do path = File.join(repo_dir, name, version, 'manifest.yaml') content = git('cat-file', '-p', "#{resolved_revision}:#{path}") YAML.safe_load(content) rescue CommandError if has?(name, version) nil else raise end end end |
#metadata_content(name:, version:) ⇒ metadata
214 215 216 217 218 219 220 |
# File 'lib/rbs/collection/sources/git.rb', line 214 def (name:, version:) { "name" => name, "version" => version, "source" => to_lockfile } end |
#need_to_fetch?(revision) ⇒ Boolean
159 160 161 162 163 |
# File 'lib/rbs/collection/sources/git.rb', line 159 private def need_to_fetch?(revision) return true unless commit_hash? !git?('cat-file', '-e', revision) end |
#resolved_revision ⇒ String
179 180 181 182 183 184 185 186 187 188 |
# File 'lib/rbs/collection/sources/git.rb', line 179 def resolved_revision @resolved_revision ||= begin if commit_hash? revision else setup! { git('rev-parse', "refs/remotes/origin/#{revision}").chomp } end end end |
#setup! ⇒ void #setup! ⇒ void
Ensure the git repository exists, and
- When
revisionis a commit hash, the commit exists in the local repository, or - When
revisionis a branch name, the latest version is fetched fromorigin
It may require a network connection to fetch or clone the repository from remote.
- If
revisionis a commit hash and the commit doesn't exists in the local repository, it runsgit fetch - If
revisionis a branch name, it runsgit fetchonce per instance
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
# File 'sig/collection/sources.rbs', line 92 private def setup! if @need_setup git_dir.mkpath if git_dir.join('.git').directory? if need_to_fetch?(revision) git 'fetch', 'origin' end else begin # git v2.27.0 or greater git 'clone', '--filter=blob:none', remote, git_dir.to_s, chdir: nil rescue CommandError # The failed `--filter=blob:none` clone may leave behind a # partial repository (e.g. when the server returned an error # mid-clone), which would cause the fallback `git clone` to # fail with "destination path ... already exists and is not # an empty directory". Clean it up before retrying. FileUtils.rm_rf(git_dir) git 'clone', remote, git_dir.to_s, chdir: nil end end @need_setup = false end yield if block_given? end |
#sh!(*cmd, **opt) ⇒ String
204 205 206 207 208 209 210 211 212 |
# File 'lib/rbs/collection/sources/git.rb', line 204 private def sh!(*cmd, **opt) RBS.logger.debug "$ #{cmd.join(' ')}" opt = { chdir: git_dir }.merge(opt).compact (__skip__ = Open3.capture3(*cmd, **opt)).then do |out, err, status| raise CommandError, "Unexpected status #{status.exitstatus}\n\n#{err}" unless status.success? out end end |
#to_lockfile ⇒ source_entry
114 115 116 117 118 119 120 121 122 |
# File 'lib/rbs/collection/sources/git.rb', line 114 def to_lockfile { 'type' => 'git', 'name' => name, 'revision' => resolved_revision, 'remote' => remote, 'repo_dir' => repo_dir, } end |
#versions(name) ⇒ Array[String]
37 38 39 40 41 42 |
# File 'lib/rbs/collection/sources/git.rb', line 37 def versions(name) setup! do versions = gems_versions[name] or raise "Git source `#{name}` doesn't have `#{name}`" versions.sort end end |
#write_metadata(dir:, name:, version:) ⇒ void
This method returns an undefined value.
Write .rbs_meta.yaml
121 122 123 124 125 126 127 |
# File 'sig/collection/sources.rbs', line 121 def (dir:, name:, version:) dir.join(METADATA_FILENAME).write( YAML.dump( (name: name, version: version) ) ) end |