gel 0.3.0 → 0.8.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +26 -3
  3. data/RELEASING.md +12 -0
  4. data/exe/gel +4 -2
  5. data/gemlib/gel/stub.rb +20 -0
  6. data/lib/gel/catalog/common.rb +4 -2
  7. data/lib/gel/catalog/compact_index.rb +6 -10
  8. data/lib/gel/catalog/dependency_index.rb +10 -10
  9. data/lib/gel/catalog/legacy_index.rb +4 -6
  10. data/lib/gel/catalog/marshal_hacks.rb +2 -0
  11. data/lib/gel/catalog.rb +33 -52
  12. data/lib/gel/catalog_set.rb +100 -0
  13. data/lib/gel/command/help.rb +13 -2
  14. data/lib/gel/command/lock.rb +3 -3
  15. data/lib/gel/command/open.rb +24 -0
  16. data/lib/gel/command/shell_setup.rb +11 -8
  17. data/lib/gel/command/stub.rb +45 -2
  18. data/lib/gel/command/version.rb +7 -0
  19. data/lib/gel/command.rb +43 -6
  20. data/lib/gel/compatibility/rubygems.rb +10 -197
  21. data/lib/gel/compatibility.rb +2 -2
  22. data/lib/gel/config.rb +41 -7
  23. data/lib/gel/db.rb +93 -83
  24. data/lib/gel/direct_gem.rb +16 -4
  25. data/lib/gel/environment.rb +542 -249
  26. data/lib/gel/error.rb +156 -24
  27. data/lib/gel/gemfile_parser.rb +74 -12
  28. data/lib/gel/gemspec_parser.rb +26 -7
  29. data/lib/gel/git_catalog.rb +15 -3
  30. data/lib/gel/git_depot.rb +62 -28
  31. data/lib/gel/httpool.rb +5 -2
  32. data/lib/gel/installer.rb +61 -23
  33. data/lib/gel/lock_loader.rb +87 -112
  34. data/lib/gel/lock_parser.rb +23 -31
  35. data/lib/gel/locked_store.rb +30 -21
  36. data/lib/gel/multi_store.rb +13 -4
  37. data/lib/gel/null_solver.rb +67 -0
  38. data/lib/gel/package/abortable.rb +18 -0
  39. data/lib/gel/package/installer.rb +124 -49
  40. data/lib/gel/package.rb +21 -4
  41. data/lib/gel/path_catalog.rb +1 -1
  42. data/lib/gel/pinboard.rb +4 -2
  43. data/lib/gel/platform.rb +38 -0
  44. data/lib/gel/pub_grub/package.rb +67 -0
  45. data/lib/gel/pub_grub/preference_strategy.rb +10 -6
  46. data/lib/gel/pub_grub/solver.rb +37 -0
  47. data/lib/gel/pub_grub/source.rb +64 -92
  48. data/lib/gel/resolved_gem_set.rb +234 -0
  49. data/lib/gel/runtime.rb +3 -3
  50. data/lib/gel/set.rb +62 -0
  51. data/lib/gel/stdlib.rb +83 -0
  52. data/lib/gel/store.rb +94 -25
  53. data/lib/gel/store_catalog.rb +2 -2
  54. data/lib/gel/store_gem.rb +54 -6
  55. data/lib/gel/stub_set.rb +32 -2
  56. data/lib/gel/support/cgi_escape.rb +34 -0
  57. data/lib/gel/support/gem_platform.rb +0 -2
  58. data/lib/gel/support/sha512.rb +142 -0
  59. data/lib/gel/support/tar/tar_writer.rb +2 -2
  60. data/lib/gel/tail_file.rb +2 -1
  61. data/lib/gel/util.rb +108 -0
  62. data/lib/gel/vendor/pstore.rb +3 -0
  63. data/lib/gel/vendor/pub_grub.rb +3 -0
  64. data/lib/gel/vendor/ruby_digest.rb +3 -0
  65. data/lib/gel/vendor_catalog.rb +38 -0
  66. data/lib/gel/version.rb +1 -1
  67. data/lib/gel.rb +15 -0
  68. data/man/man1/gel-exec.1 +1 -1
  69. data/man/man1/gel-install.1 +1 -1
  70. data/man/man1/gel.1 +14 -1
  71. data/{lib/gel/compatibility → slib}/bundler/cli.rb +0 -0
  72. data/{lib/gel/compatibility → slib}/bundler/friendly_errors.rb +0 -0
  73. data/{lib/gel/compatibility/rubygems/dependency_installer.rb → slib/bundler/gem_helper.rb} +0 -0
  74. data/slib/bundler/gem_tasks.rb +0 -0
  75. data/{lib/gel/compatibility → slib}/bundler/setup.rb +0 -0
  76. data/{lib/gel/compatibility → slib}/bundler.rb +39 -3
  77. data/{lib/gel/compatibility → slib}/rubygems/command.rb +0 -0
  78. data/slib/rubygems/dependency_installer.rb +12 -0
  79. data/{lib/gel/compatibility → slib}/rubygems/gem_runner.rb +0 -0
  80. data/slib/rubygems/package.rb +6 -0
  81. data/slib/rubygems/package_task.rb +7 -0
  82. data/slib/rubygems/specification.rb +0 -0
  83. data/slib/rubygems/version.rb +0 -0
  84. data/slib/rubygems.rb +297 -0
  85. data/vendor/pstore/LICENSE.txt +22 -0
  86. data/vendor/pstore/lib/pstore.rb +488 -0
  87. data/vendor/pub_grub/LICENSE.txt +21 -0
  88. data/vendor/pub_grub/lib/pub_grub/assignment.rb +20 -0
  89. data/vendor/pub_grub/lib/pub_grub/basic_package_source.rb +183 -0
  90. data/vendor/pub_grub/lib/pub_grub/failure_writer.rb +182 -0
  91. data/vendor/pub_grub/lib/pub_grub/incompatibility.rb +143 -0
  92. data/vendor/pub_grub/lib/pub_grub/package.rb +35 -0
  93. data/vendor/pub_grub/lib/pub_grub/partial_solution.rb +121 -0
  94. data/vendor/pub_grub/lib/pub_grub/rubygems.rb +45 -0
  95. data/vendor/pub_grub/lib/pub_grub/solve_failure.rb +17 -0
  96. data/vendor/pub_grub/lib/pub_grub/static_package_source.rb +53 -0
  97. data/vendor/pub_grub/lib/pub_grub/term.rb +105 -0
  98. data/vendor/pub_grub/lib/pub_grub/version.rb +3 -0
  99. data/vendor/pub_grub/lib/pub_grub/version_constraint.rb +124 -0
  100. data/vendor/pub_grub/lib/pub_grub/version_range.rb +399 -0
  101. data/vendor/pub_grub/lib/pub_grub/version_solver.rb +247 -0
  102. data/vendor/pub_grub/lib/pub_grub/version_union.rb +174 -0
  103. data/vendor/pub_grub/lib/pub_grub.rb +31 -0
  104. data/vendor/ruby-digest/UNLICENSE +24 -0
  105. data/vendor/ruby-digest/lib/ruby_digest.rb +812 -0
  106. metadata +95 -19
data/lib/gel/error.rb CHANGED
@@ -41,39 +41,60 @@ end
41
41
  # Define all UserError subclasses in this file. (Non-reportable errors,
42
42
  # which describe errors in interaction between internal components, can
43
43
  # and should be defined whereever they're used.)
44
+ #
45
+ # To support special cases where external API dictates a different
46
+ # superclass (e.g. an UnsatisfiedDependencyError must be a ::LoadError),
47
+ # while retaining most of the conveniences, it's also possible to
48
+ # include the Gel::UserError::Impl module directly.
44
49
  class Gel::UserError < StandardError
45
- include Gel::ReportableError
50
+ module Impl
51
+ include Gel::ReportableError
46
52
 
47
- def initialize(**context)
48
- @context = context
53
+ def set_context(context)
54
+ @context = context
55
+ end
49
56
 
50
- super message
51
- end
57
+ def [](key)
58
+ @context.fetch(key)
59
+ end
52
60
 
53
- def [](key)
54
- @context.fetch(key)
55
- end
61
+ def message
62
+ self.class.name
63
+ end
56
64
 
57
- def message
58
- self.class.name
59
- end
65
+ def inner_backtrace
66
+ return [] unless cause
60
67
 
61
- def inner_backtrace
62
- return [] unless cause
68
+ bt = cause.backtrace_locations
69
+ ignored_bt = backtrace_locations
63
70
 
64
- bt = cause.backtrace_locations
65
- ignored_bt = backtrace_locations
71
+ while bt.last.to_s == ignored_bt.last.to_s
72
+ bt.pop
73
+ ignored_bt.pop
74
+ end
66
75
 
67
- while bt.last.to_s == ignored_bt.last.to_s
68
- bt.pop
69
- ignored_bt.pop
70
- end
76
+ while bt.last.path == ignored_bt.last.path
77
+ bt.pop
78
+ end
71
79
 
72
- while bt.last.path == ignored_bt.last.path
73
- bt.pop
80
+ bt
74
81
  end
82
+ end
83
+
84
+ include Impl
85
+
86
+ def initialize(**context)
87
+ set_context context
88
+ super message
89
+ end
90
+ end
75
91
 
76
- bt
92
+ class Gel::LoadError < ::LoadError
93
+ include Gel::UserError::Impl
94
+
95
+ def initialize(**context)
96
+ set_context context
97
+ super message
77
98
  end
78
99
  end
79
100
 
@@ -122,6 +143,18 @@ module Gel::Error
122
143
  end
123
144
  end
124
145
 
146
+ class OutdatedLockfileError < Gel::UserError
147
+ def message
148
+ "Lockfile out of date; use 'gel lock' or 'gel install' to re-resolve"
149
+ end
150
+ end
151
+
152
+ class NoLockfileError < OutdatedLockfileError
153
+ def message
154
+ "Gemfile has not been resolved; use 'gel lock' or 'gel install' to resolve"
155
+ end
156
+ end
157
+
125
158
  class UnexpectedConfigError < Gel::UserError
126
159
  def initialize(line:)
127
160
  super
@@ -143,12 +176,32 @@ module Gel::Error
143
176
  end
144
177
 
145
178
  class ExtensionBuildError < Gel::UserError
146
- def initialize(program_name:, exitstatus:)
179
+ def initialize(program_name:, exitstatus:, log:, abort: nil)
180
+ super
181
+ end
182
+
183
+ def message
184
+ if self[:exitstatus] == 1 && self[:abort]
185
+ abort_message = self[:abort]
186
+
187
+ if abort_message.include?("\n") || abort_message.size > 100
188
+ "#{self[:program_name].inspect} aborted:\n#{abort_message.gsub(/^/, " ")}"
189
+ else
190
+ "#{self[:program_name].inspect} aborted: #{abort_message.sub(/\A[\[({]?(?:error|err|abort|fatal)[\])}]?:?\s+/i, "")}"
191
+ end
192
+ else
193
+ "#{self[:program_name].inspect} exited with #{self[:exitstatus].inspect}"
194
+ end + "\n\nBuild log: #{self[:log]}"
195
+ end
196
+ end
197
+
198
+ class ExtensionDependencyError < Gel::UserError
199
+ def initialize(dependency:, failure:)
147
200
  super
148
201
  end
149
202
 
150
203
  def message
151
- "#{self[:program_name].inspect} exited with #{self[:exitstatus].inspect}"
204
+ "Depends on #{self[:dependency].inspect}, which failed to #{self[:failure]}"
152
205
  end
153
206
  end
154
207
 
@@ -202,9 +255,88 @@ module Gel::Error
202
255
  end
203
256
  end
204
257
 
258
+ class UnsatisfiableRubyVersionError < Gel::UserError
259
+ def initialize(name:, running:, attempted_platforms:)
260
+ super
261
+ end
262
+
263
+ def message
264
+ "None of the available #{self[:name].inspect} packages are compatible with Ruby version #{self[:running].inspect}. Found: #{self[:attempted_platforms].inspect}"
265
+ end
266
+ end
267
+
268
+ class MissingGemError < Gel::UserError
269
+ def initialize(name:)
270
+ super
271
+ end
272
+
273
+ def message
274
+ "Missing gem #{self[:name].inspect}. Do you need to 'gel install'?"
275
+ end
276
+ end
277
+
278
+ class AlreadyActivatedError < Gel::LoadError
279
+ def initialize(name:, existing:, requirements: nil, requested: nil, why:)
280
+ raise ArgumentError, "exactly one of :requirements and :requested must be supplied" unless requirements.nil? != requested.nil?
281
+ super
282
+ end
283
+
284
+ def message
285
+ "Already activated #{self[:name].inspect} #{self[:existing]}" +
286
+ if self[:requirements]
287
+ ", which is incompatible with: #{self[:requirements]}"
288
+ else
289
+ "; cannot also activate #{self[:requested]}"
290
+ end +
291
+ (self[:why] ? " (#{self[:why].join("; ")})" : "")
292
+ end
293
+ end
294
+
295
+ class UnsatisfiedDependencyError < Gel::LoadError
296
+ def initialize(name:, was_locked:, found_any:, requirements:, why:)
297
+ super
298
+ end
299
+
300
+ def message
301
+ if self[:was_locked]
302
+ if self[:found_any]
303
+ "Locked version of gem #{self[:name].inspect} does not satisfy requirements: #{self[:requirements]}"
304
+ else
305
+ "Gem #{self[:name].inspect} is not present in Gemfile; unable to satisfy requirements: #{self[:requirements]}"
306
+ end
307
+ else
308
+ if self[:found_any]
309
+ "No available version of gem #{self[:name].inspect} satisfies requirements: #{self[:requirements]}"
310
+ else
311
+ "No version of gem #{self[:name].inspect} is installed; unable to satisfy requirements: #{self[:requirements]}"
312
+ end
313
+ end + (self[:why] ? " (#{self[:why].join("; ")})" : "")
314
+ end
315
+ end
316
+
317
+ class MissingExecutableError < Gel::UserError
318
+ def initialize(executable:, gem_name:, gem_versions:, locked_gem_version:)
319
+ super
320
+ end
321
+
322
+ def message
323
+ "Locked gem #{self[:gem_name].inspect} #{self[:locked_gem_version].inspect} does not supply executable #{self[:executable].inspect}. Found in other installed versions: #{self[:gem_versions].inspect}"
324
+ end
325
+ end
326
+
205
327
  class ParsedGemspecError < Gel::UserError
206
328
  def message
207
329
  "Gemspec parse failed"
208
330
  end
209
331
  end
332
+
333
+ class GitResolveError < Gel::UserError
334
+ def initialize(remote:, ref:)
335
+ super
336
+ end
337
+
338
+ def message
339
+ "Unable to resolve #{self[:ref].inspect} in git repository #{self[:remote].inspect}"
340
+ end
341
+ end
210
342
  end
@@ -14,6 +14,29 @@ module Gel::GemfileParser
14
14
  raise Gel::Error::GemfileEvaluationError.new(filename: filename)
15
15
  end
16
16
 
17
+ def self.inline(&block)
18
+ filename, _lineno = block.source_location
19
+
20
+ result = GemfileContent.new(filename)
21
+ context = ParseContext.new(result, filename)
22
+ context.instance_eval(&block)
23
+ result
24
+ end
25
+
26
+ class RunningRuby
27
+ def self.version
28
+ RUBY_VERSION
29
+ end
30
+
31
+ def self.engine
32
+ RUBY_ENGINE
33
+ end
34
+
35
+ def self.engine_version
36
+ RUBY_ENGINE_VERSION
37
+ end
38
+ end
39
+
17
40
  class ParseContext
18
41
  def initialize(result, filename)
19
42
  @result = result
@@ -38,29 +61,40 @@ module Gel::GemfileParser
38
61
  @result.git_sources[name] = block
39
62
  end
40
63
 
41
- def ruby(version, engine: nil, engine_version: nil)
42
- req = Gel::Support::GemRequirement.new(version)
43
- unless req.satisfied_by?(Gel::Support::GemVersion.new(RUBY_VERSION))
64
+ def ruby(*versions, engine: nil, engine_version: nil)
65
+ req = Gel::Support::GemRequirement.new(versions)
66
+ running_ruby_version = RunningRuby.version
67
+ running_engine = RunningRuby.engine
68
+ running_engine_version = RunningRuby.engine_version
69
+
70
+ unless req.satisfied_by?(Gel::Support::GemVersion.new(running_ruby_version))
44
71
  raise Gel::Error::MismatchRubyVersionError.new(
45
- running: RUBY_VERSION,
46
- requested: version,
72
+ running: running_ruby_version,
73
+ requested: versions,
47
74
  )
48
75
  end
49
- unless !engine || RUBY_ENGINE == engine
76
+ unless !engine || running_engine == engine
50
77
  raise Gel::Error::MismatchRubyEngineError.new(
51
- running: RUBY_ENGINE,
78
+ running: running_engine,
52
79
  engine: engine,
53
80
  )
54
81
  end
55
82
  if engine_version
56
83
  raise "Cannot specify :engine_version without :engine" unless engine
57
- req = Gel::Support::GemRequirement.new(version)
58
- raise "Running ruby engine version #{RUBY_ENGINE_VERSION} does not match requested #{engine_version.inspect}" unless req.satisfied_by?(Gel::Support::GemVersion.new(RUBY_ENGINE_VERSION))
84
+ req = Gel::Support::GemRequirement.new(engine_version)
85
+ raise "Running ruby engine version #{running_engine_version} does not match requested #{engine_version.inspect}" unless req.satisfied_by?(Gel::Support::GemVersion.new(running_engine_version))
59
86
  end
60
- @result.ruby << [version, engine: engine, engine_version: engine_version]
87
+ @result.ruby << [versions, engine: engine, engine_version: engine_version]
61
88
  end
62
89
 
63
90
  def gem(name, *requirements, **options)
91
+ aliases = GemfileContent::OPTION_ALIASES
92
+ options.keys.each do |key|
93
+ if original = aliases[key]
94
+ raise "Duplicate key #{key.inspect} == #{original.inspect}" if options.key?(original)
95
+ options[original] = options.delete(key)
96
+ end
97
+ end
64
98
  options = @result.flatten(options, @stack)
65
99
  @result.add_gem(name, requirements, options)
66
100
  end
@@ -77,8 +111,8 @@ module Gel::GemfileParser
77
111
  else
78
112
  spec = gemspecs[0]
79
113
  gem spec.name, path: path
80
- spec.development_dependencies.each do |name, constraints|
81
- gem name, constraints, group: development_group
114
+ spec.development_dependencies.each do |dep_name, constraints|
115
+ gem dep_name, constraints, group: development_group
82
116
  end
83
117
  end
84
118
  end
@@ -90,6 +124,20 @@ module Gel::GemfileParser
90
124
  @stack.pop
91
125
  end
92
126
 
127
+ def install_if(*conditions)
128
+ @stack << { install_if: conditions }
129
+ yield
130
+ ensure
131
+ @stack.pop
132
+ end
133
+
134
+ def path(*names)
135
+ @stack << { path: names }
136
+ yield
137
+ ensure
138
+ @stack.pop
139
+ end
140
+
93
141
  def platforms(*names)
94
142
  @stack << { platforms: names }
95
143
  yield
@@ -99,6 +147,10 @@ module Gel::GemfileParser
99
147
  end
100
148
 
101
149
  class GemfileContent
150
+ OPTION_ALIASES = {
151
+ platform: :platforms,
152
+ }
153
+
102
154
  attr_reader :filename
103
155
 
104
156
  attr_reader :sources
@@ -133,6 +185,12 @@ module Gel::GemfileParser
133
185
  raise "Only git sources can specify a :branch" if options[:branch] && !options[:git]
134
186
  raise "Duplicate entry for gem #{name.inspect}" if @gems.assoc(name)
135
187
 
188
+ if options[:install_if]
189
+ options[:install_if] = Array(options[:install_if]).all? do |condition|
190
+ condition.respond_to?(:call) ? condition.call : condition
191
+ end
192
+ end
193
+
136
194
  @gems << [name, requirements, options]
137
195
  end
138
196
 
@@ -158,5 +216,9 @@ module Gel::GemfileParser
158
216
  end
159
217
  end
160
218
  end
219
+
220
+ def gem_names
221
+ @gems.map(&:first).flatten.map(&:to_s)
222
+ end
161
223
  end
162
224
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "ostruct"
4
-
5
3
  class Gel::GemspecParser
6
4
  module Context
7
5
  def self.context
@@ -30,16 +28,19 @@ class Gel::GemspecParser
30
28
  end
31
29
  end
32
30
 
33
- class Result < OpenStruct
31
+ class Result
34
32
  def initialize
35
- super
33
+ @values = {}
34
+
36
35
  self.specification_version = nil
37
36
  self.metadata = {}
38
37
  self.requirements = []
39
38
  self.rdoc_options = []
39
+ self.extra_rdoc_files = []
40
40
  self.development_dependencies = []
41
41
  self.runtime_dependencies = []
42
42
  self.executables = []
43
+ self.extensions = []
43
44
  end
44
45
 
45
46
  def add_development_dependency(name, *versions)
@@ -50,6 +51,18 @@ class Gel::GemspecParser
50
51
  runtime_dependencies << [name, versions.flatten]
51
52
  end
52
53
  alias add_dependency add_runtime_dependency
54
+
55
+ def method_missing(name, *args)
56
+ if name =~ /(.*)=$/
57
+ @values[$1.to_sym] = args.first
58
+ else
59
+ @values[name]
60
+ end
61
+ end
62
+
63
+ def respond_to_missing?(name, *)
64
+ name != :marshal_dump && name != :_dump
65
+ end
53
66
  end
54
67
 
55
68
  def self.parse(content, filename, lineno = 1, root: File.dirname(filename), isolate: true)
@@ -60,13 +73,19 @@ class Gel::GemspecParser
60
73
  in_read, in_write = IO.pipe
61
74
  out_read, out_write = IO.pipe
62
75
 
63
- pid = spawn({ "RUBYLIB" => Gel::Environment.modified_rubylib, "GEL_GEMFILE" => "", "GEL_LOCKFILE" => "" },
76
+ pid = spawn({
77
+ "RUBYLIB" => Gel::Environment.modified_rubylib,
78
+ "RUBYOPT" => "",
79
+ "GEL_DEBUG" => nil,
80
+ "GEL_GEMFILE" => "",
81
+ "GEL_LOCKFILE" => "",
82
+ },
64
83
  RbConfig.ruby,
65
84
  "-r", File.expand_path("compatibility", __dir__),
66
85
  "-r", File.expand_path("gemspec_parser", __dir__),
67
- "-e", "puts Marshal.dump(Gel::GemspecParser.parse($stdin.read, ARGV.shift, ARGV.shift.to_i, root: ARGV.shift, isolate: false))",
86
+ "-e", "IO.new(3, 'w').write Marshal.dump(Gel::GemspecParser.parse($stdin.read, ARGV.shift, ARGV.shift.to_i, root: ARGV.shift, isolate: false))",
68
87
  filename, lineno.to_s, root,
69
- in: in_read, out: out_write)
88
+ in: in_read, 3 => out_write)
70
89
 
71
90
  in_read.close
72
91
  out_write.close
@@ -5,11 +5,12 @@ require_relative "path_catalog"
5
5
  class Gel::GitCatalog
6
6
  attr_reader :git_depot, :remote, :ref_type, :ref
7
7
 
8
- def initialize(git_depot, remote, ref_type, ref)
8
+ def initialize(git_depot, remote, ref_type, ref, revision = nil)
9
9
  @git_depot = git_depot
10
10
  @remote = remote
11
11
  @ref_type = ref_type
12
12
  @ref = ref
13
+ @revision = revision
13
14
 
14
15
  @monitor = Monitor.new
15
16
  @result = nil
@@ -17,11 +18,18 @@ class Gel::GitCatalog
17
18
 
18
19
  def checkout_result
19
20
  @result ||
20
- @monitor.synchronize { @result ||= git_depot.resolve_and_checkout(remote, ref) }
21
+ @monitor.synchronize do
22
+ @result ||=
23
+ if @revision
24
+ [@revision, git_depot.checkout(remote, @revision)]
25
+ else
26
+ git_depot.resolve_and_checkout(remote, ref)
27
+ end
28
+ end
21
29
  end
22
30
 
23
31
  def revision
24
- checkout_result[0]
32
+ @revision || checkout_result[0]
25
33
  end
26
34
 
27
35
  def gem_info(name)
@@ -35,4 +43,8 @@ class Gel::GitCatalog
35
43
  def prepare
36
44
  checkout_result
37
45
  end
46
+
47
+ def path
48
+ git_depot.git_path(remote, revision)
49
+ end
38
50
  end
data/lib/gel/git_depot.rb CHANGED
@@ -1,12 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "vendor/ruby_digest"
4
+
3
5
  class Gel::GitDepot
4
6
  attr_reader :mirror_root
5
7
 
6
- require "logger"
7
- Logger = ::Logger.new($stderr)
8
- Logger.level = $DEBUG ? ::Logger::DEBUG : ::Logger::WARN
9
-
10
8
  def initialize(store, mirror_root = (ENV["GEL_CACHE"] || "~/.cache/gel") + "/git")
11
9
  @store = store
12
10
  @mirror_root = File.expand_path(mirror_root)
@@ -40,15 +38,42 @@ class Gel::GitDepot
40
38
  end
41
39
 
42
40
  def resolve(remote, ref)
43
- mirror = remote(remote) { false } # always update mirror
44
-
45
- r, w = IO.pipe
46
- status = git(remote, "rev-parse", ref || "HEAD", chdir: mirror, out: w)
47
- raise "git rev-parse failed" unless status.success?
41
+ if ref
42
+ # ref could be an arbitrarily-complex ref (HEAD~3 or whatever), so
43
+ # update our mirror and then resolve it locally
44
+
45
+ mirror = remote(remote) { false } # always update mirror
46
+
47
+ r, w = IO.pipe
48
+ status = git(remote, "rev-parse", ref || "HEAD", chdir: mirror, out: w)
49
+ w.close
50
+
51
+ if status.success?
52
+ r.read.chomp
53
+ else
54
+ # We didn't keep stderr, but we can infer the nature of the problem
55
+ # from whether git produced any output: for simple "I don't know what
56
+ # that is" errors, it returns the input, while more fundamental
57
+ # problems die earlier and return nothing.
58
+ if r.read.chomp.empty?
59
+ # This is an internal error: our mirror must be broken
60
+ raise "git rev-parse failed"
61
+ else
62
+ # This is a user error: the ref doesn't exist
63
+ raise Gel::Error::GitResolveError.new(remote: remote, ref: ref)
64
+ end
65
+ end
66
+ else
67
+ # If we just want to know the remote HEAD, we can ask without even
68
+ # touching our mirror
48
69
 
49
- w.close
70
+ r, w = IO.pipe
71
+ status = git(remote, "ls-remote", remote, "HEAD", out: w)
72
+ raise "git ls-remote failed" unless status.success?
50
73
 
51
- r.read.chomp
74
+ w.close
75
+ r.read.split.first
76
+ end
52
77
  end
53
78
 
54
79
  def resolve_and_checkout(remote, ref)
@@ -84,36 +109,45 @@ class Gel::GitDepot
84
109
 
85
110
  t = Time.now
86
111
  pid = spawn("git", *arguments, **kwargs)
87
- logger.debug { "#{remote} [#{pid}] #{command_for_log("git", *arguments)}" }
112
+ logger&.debug { "#{remote} [#{pid}] #{command_for_log("git", *arguments)}" }
88
113
 
89
114
  _, status = Process.waitpid2(pid)
90
- logger.debug { "#{remote} [#{pid}] process exited #{status.exitstatus} (#{status.success? ? "success" : "failure"}) after #{Time.now - t}s" }
115
+ logger&.debug { "#{remote} [#{pid}] process exited #{status.exitstatus} (#{status.success? ? "success" : "failure"}) after #{Time.now - t}s" }
91
116
 
92
117
  status
93
118
  end
94
119
 
95
120
  def ident(remote)
96
121
  short = File.basename(remote, ".git")
97
- digest = Digest(:SHA256).hexdigest(remote)[0..12]
122
+ digest = Gel::Vendor::RubyDigest::SHA256.hexdigest(remote)[0..12]
98
123
  "#{short}-#{digest}"
99
124
  end
100
125
 
101
- require "shellwords"
102
- def shellword(word)
103
- if word =~ /\A[A-Za-z0-9=+\/,.-]+\z/
104
- word
105
- elsif word =~ /'/
106
- "\"#{Shellwords.shellescape(word).gsub(/\\\s/, "\\1")}\""
107
- else
108
- "'#{word}'"
126
+ if $DEBUG
127
+ require "shellwords"
128
+ def shellword(word)
129
+ if word =~ /\A[A-Za-z0-9=+\/,.-]+\z/
130
+ word
131
+ elsif word =~ /'/
132
+ "\"#{Shellwords.shellescape(word).gsub(/\\\s/, "\\1")}\""
133
+ else
134
+ "'#{word}'"
135
+ end
109
136
  end
110
- end
111
137
 
112
- def command_for_log(*parts)
113
- parts.map { |part| shellword(part) }.join(" ")
114
- end
138
+ def command_for_log(*parts)
139
+ parts.map { |part| shellword(part) }.join(" ")
140
+ end
115
141
 
116
- def logger
117
- Logger
142
+ require "logger"
143
+ Logger = ::Logger.new($stderr)
144
+ Logger.level = ::Logger::DEBUG
145
+
146
+ def logger
147
+ Logger
148
+ end
149
+ else
150
+ def logger
151
+ end
118
152
  end
119
153
  end
data/lib/gel/httpool.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "monitor"
4
- require "net/http"
5
4
 
6
5
  class Gel::Httpool
7
6
  include MonitorMixin
@@ -25,7 +24,11 @@ class Gel::Httpool
25
24
  end
26
25
  end
27
26
 
28
- def request(uri, request = Net::HTTP::Get.new(uri))
27
+ def request(uri, request = nil)
28
+ require "net/http"
29
+
30
+ request ||= Net::HTTP::Get.new(uri)
31
+
29
32
  with_connection(uri) do |http|
30
33
  logger.debug { "GET #{uri}" }
31
34