braid 1.1.5 → 1.1.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. checksums.yaml +4 -4
  2. data/exe/braid +11 -0
  3. data/lib/braid/check_gem.rb +65 -0
  4. data/lib/braid/command.rb +18 -0
  5. data/lib/braid/commands/add.rb +41 -1
  6. data/lib/braid/commands/diff.rb +2 -1
  7. data/lib/braid/commands/push.rb +1 -0
  8. data/lib/braid/commands/remove.rb +1 -0
  9. data/lib/braid/commands/setup.rb +1 -0
  10. data/lib/braid/commands/status.rb +1 -0
  11. data/lib/braid/commands/update.rb +1 -0
  12. data/lib/braid/commands/upgrade_config.rb +1 -0
  13. data/lib/braid/config.rb +28 -4
  14. data/{bin/braid → lib/braid/main.rb} +16 -17
  15. data/lib/braid/mirror.rb +111 -24
  16. data/lib/braid/operations.rb +20 -18
  17. data/lib/braid/operations_lite.rb +19 -1
  18. data/lib/braid/sorbet/fake_runtime.rb +75 -0
  19. data/lib/braid/sorbet/setup.rb +18 -0
  20. data/lib/braid/version.rb +4 -1
  21. data/lib/braid.rb +23 -3
  22. metadata +26 -75
  23. data/.gitignore +0 -16
  24. data/.travis.yml +0 -15
  25. data/CONTRIBUTING.md +0 -24
  26. data/Gemfile +0 -3
  27. data/README.md +0 -234
  28. data/Rakefile +0 -12
  29. data/_config.yml +0 -1
  30. data/braid.gemspec +0 -35
  31. data/braids-json.schema.json +0 -91
  32. data/config_versions.md +0 -58
  33. data/spec/config_spec.rb +0 -59
  34. data/spec/fixtures/shiny/README +0 -3
  35. data/spec/fixtures/shiny/other-skit/layout.liquid +0 -219
  36. data/spec/fixtures/shiny/skit-layout.liquid.test +0 -2
  37. data/spec/fixtures/shiny/skit1.test +0 -2
  38. data/spec/fixtures/shiny-conf-1.0.9-lock/.braids.json +0 -10
  39. data/spec/fixtures/shiny-conf-1.0.9-lock/expected.braids.json +0 -9
  40. data/spec/fixtures/shiny-conf-1.0.9-lock/skit1/layouts/layout.liquid +0 -219
  41. data/spec/fixtures/shiny-conf-1.0.9-lock/skit1/preview.png +0 -0
  42. data/spec/fixtures/shiny-conf-breaking-changes/.braids +0 -14
  43. data/spec/fixtures/shiny-conf-breaking-changes/Spoon-Knife/README.md +0 -9
  44. data/spec/fixtures/shiny-conf-breaking-changes/Spoon-Knife/index.html +0 -20
  45. data/spec/fixtures/shiny-conf-breaking-changes/Spoon-Knife/styles.css +0 -17
  46. data/spec/fixtures/shiny-conf-breaking-changes/expected.braids.json +0 -10
  47. data/spec/fixtures/shiny-conf-breaking-changes/skit1/layouts/layout.liquid +0 -219
  48. data/spec/fixtures/shiny-conf-breaking-changes/skit1/preview.png +0 -0
  49. data/spec/fixtures/shiny-conf-future/.braids.json +0 -10
  50. data/spec/fixtures/shiny-conf-future/skit1/layouts/layout.liquid +0 -219
  51. data/spec/fixtures/shiny-conf-future/skit1/preview.png +0 -0
  52. data/spec/fixtures/shiny-conf-json-old-name/.braids +0 -9
  53. data/spec/fixtures/shiny-conf-json-old-name/expected.braids.json +0 -10
  54. data/spec/fixtures/shiny-conf-json-old-name/skit1/layouts/layout.liquid +0 -219
  55. data/spec/fixtures/shiny-conf-json-old-name/skit1/preview.png +0 -0
  56. data/spec/fixtures/shiny-conf-yaml/.braids +0 -8
  57. data/spec/fixtures/shiny-conf-yaml/expected.braids.json +0 -10
  58. data/spec/fixtures/shiny-conf-yaml/skit1/layouts/layout.liquid +0 -219
  59. data/spec/fixtures/shiny-conf-yaml/skit1/preview.png +0 -0
  60. data/spec/fixtures/shiny_skit1.2_merged/layouts/layout.liquid +0 -223
  61. data/spec/fixtures/shiny_skit1.2_merged/preview.png +0 -0
  62. data/spec/fixtures/shiny_skit1_conflicting/layouts/layout.liquid +0 -221
  63. data/spec/fixtures/shiny_skit1_conflicting/preview.png +0 -0
  64. data/spec/fixtures/shiny_skit1_mergeable/layouts/layout.liquid +0 -221
  65. data/spec/fixtures/shiny_skit1_mergeable/preview.png +0 -0
  66. data/spec/fixtures/skit1/layouts/layout.liquid +0 -219
  67. data/spec/fixtures/skit1/preview.png +0 -0
  68. data/spec/fixtures/skit1.1/layouts/layout.liquid +0 -219
  69. data/spec/fixtures/skit1.1_with_filter/.gitattributes +0 -1
  70. data/spec/fixtures/skit1.1_with_filter/layouts/layout.liquid +0 -219
  71. data/spec/fixtures/skit1.1_with_filter/preview.png +0 -0
  72. data/spec/fixtures/skit1.1x/layouts/layout.liquid +0 -219
  73. data/spec/fixtures/skit1.2/layouts/layout.liquid +0 -221
  74. data/spec/fixtures/skit1.3/layouts/README.md +0 -1
  75. data/spec/fixtures/skit1.3/layouts/layout.liquid +0 -221
  76. data/spec/fixtures/skit1_with_filter/.gitattributes +0 -1
  77. data/spec/fixtures/skit1_with_filter/layouts/layout.liquid +0 -219
  78. data/spec/fixtures/skit1_with_filter/preview.png +0 -0
  79. data/spec/integration/adding_spec.rb +0 -230
  80. data/spec/integration/config_versioning_spec.rb +0 -222
  81. data/spec/integration/diff_spec.rb +0 -597
  82. data/spec/integration/integration_helper.rb +0 -129
  83. data/spec/integration/push_spec.rb +0 -399
  84. data/spec/integration/remove_spec.rb +0 -81
  85. data/spec/integration/status_spec.rb +0 -165
  86. data/spec/integration/updating_spec.rb +0 -487
  87. data/spec/mirror_spec.rb +0 -119
  88. data/spec/operations_spec.rb +0 -66
  89. data/spec/test_helper.rb +0 -19
data/lib/braid/mirror.rb CHANGED
@@ -1,21 +1,27 @@
1
+ # typed: strict
1
2
  module Braid
2
3
  class Mirror
4
+ extend T::Sig
5
+
3
6
  # Since Braid 1.1.0, the attributes are written to .braids.json in this
4
7
  # canonical order. For now, the order is chosen to match what Braid 1.0.22
5
8
  # produced for newly added mirrors.
6
- ATTRIBUTES = %w(url branch path tag revision)
9
+ ATTRIBUTES = T.let(%w(url branch path tag revision), T::Array[String])
7
10
 
8
11
  class UnknownType < BraidError
12
+ sig {returns(String)}
9
13
  def message
10
14
  "unknown type: #{super}"
11
15
  end
12
16
  end
13
17
  class PathRequired < BraidError
18
+ sig {returns(String)}
14
19
  def message
15
20
  'path is required'
16
21
  end
17
22
  end
18
23
  class NoTagAndBranch < BraidError
24
+ sig {returns(String)}
19
25
  def message
20
26
  'can not specify both tag and branch configuration'
21
27
  end
@@ -23,11 +29,19 @@ module Braid
23
29
 
24
30
  include Operations::VersionControl
25
31
 
26
- attr_reader :path, :attributes
32
+ sig {returns(String)}
33
+ attr_reader :path
34
+
35
+ # It's going to take significant refactoring to be able to give this a type.
36
+ sig {returns(T::Hash[String, T.untyped])}
37
+ attr_reader :attributes
38
+
39
+ BreakingChangeCallback = T.type_alias { T.proc.params(arg0: String).void }
27
40
 
41
+ sig {params(path: String, attributes: T::Hash[String, T.untyped], breaking_change_cb: BreakingChangeCallback).void}
28
42
  def initialize(path, attributes = {}, breaking_change_cb = DUMMY_BREAKING_CHANGE_CB)
29
- @path = path.sub(/\/$/, '')
30
- @attributes = attributes.dup
43
+ @path = T.let(path.sub(/\/$/, ''), String)
44
+ @attributes = T.let(attributes.dup, T::Hash[String, T.untyped])
31
45
 
32
46
  # Not that it's terribly important to check for such an old feature. This
33
47
  # is mainly to demonstrate the RemoveMirrorDueToBreakingChange mechanism
@@ -65,13 +79,14 @@ DESC
65
79
  @attributes.delete('squashed')
66
80
  end
67
81
 
82
+ sig {params(url: String, options: T.untyped).returns(Mirror)}
68
83
  def self.new_from_options(url, options = {})
69
84
  url = url.sub(/\/$/, '')
70
85
 
71
86
  raise NoTagAndBranch if options['tag'] && options['branch']
72
87
 
73
88
  tag = options['tag']
74
- branch = options['branch'] || (tag.nil? ? 'master' : nil)
89
+ branch = options['branch']
75
90
 
76
91
  path = (options['path'] || extract_path_from_url(url, options['remote_path'])).sub(/\/$/, '')
77
92
  raise PathRequired unless path
@@ -82,14 +97,17 @@ DESC
82
97
  self.new(path, attributes)
83
98
  end
84
99
 
100
+ sig {params(comparison: Mirror).returns(T::Boolean)}
85
101
  def ==(comparison)
86
102
  path == comparison.path && attributes == comparison.attributes
87
103
  end
88
104
 
105
+ sig {returns(T::Boolean)}
89
106
  def locked?
90
107
  branch.nil? && tag.nil?
91
108
  end
92
109
 
110
+ sig {params(commit: String).returns(T::Boolean)}
93
111
  def merged?(commit)
94
112
  # tip from spearce in #git:
95
113
  # `test z$(git merge-base A B) = z$(git rev-parse --verify A)`
@@ -97,6 +115,9 @@ DESC
97
115
  !!base_revision && git.merge_base(commit, base_revision) == commit
98
116
  end
99
117
 
118
+ # We'll probably call the return type something like
119
+ # Braid::Operations::Git::TreeItem.
120
+ sig {params(revision: String).returns(T.untyped)}
100
121
  def upstream_item_for_revision(revision)
101
122
  git.get_tree_item(revision, self.remote_path)
102
123
  end
@@ -106,6 +127,7 @@ DESC
106
127
  # user-specified arguments. Having the caller run "git diff" is convenient
107
128
  # for now but violates encapsulation a little; we may have to reorganize the
108
129
  # code in order to add features.
130
+ sig {params(user_args: T::Array[String]).returns(T::Array[String])}
109
131
  def diff_args(user_args = [])
110
132
  upstream_item = upstream_item_for_revision(base_revision)
111
133
 
@@ -139,7 +161,7 @@ DESC
139
161
  # before we add the basenames.
140
162
  return [
141
163
  '--relative=' + path,
142
- '--src-prefix=a/' + File.basename(remote_path),
164
+ '--src-prefix=a/' + File.basename(T.must(remote_path)),
143
165
  '--dst-prefix=b/' + File.basename(path),
144
166
  base_tree,
145
167
  # user_args may contain options, which must come before paths.
@@ -156,6 +178,7 @@ DESC
156
178
  end
157
179
 
158
180
  # Precondition: the remote for this mirror is set up.
181
+ sig {returns(String)}
159
182
  def diff
160
183
  fetch_base_revision_if_missing
161
184
  git.diff(diff_args)
@@ -166,6 +189,7 @@ DESC
166
189
  # (https://github.com/cristibalan/braid/issues/71). Mitigate this for
167
190
  # `braid diff` and other commands that need the diff by skipping the fetch
168
191
  # if the base revision is already present in the repository.
192
+ sig {void}
169
193
  def fetch_base_revision_if_missing
170
194
  begin
171
195
  # Without ^{commit}, this will happily pass back an object hash even if
@@ -176,74 +200,133 @@ DESC
176
200
  end
177
201
  end
178
202
 
203
+ sig {void}
179
204
  def fetch
180
205
  git_cache.fetch(url) if cached?
181
206
  git.fetch(remote)
182
207
  end
183
208
 
209
+ sig {returns(T::Boolean)}
184
210
  def cached?
185
211
  git.remote_url(remote) == cached_url
186
212
  end
187
213
 
214
+ sig {returns(String)}
188
215
  def base_revision
189
- if revision
216
+ # Avoid a Sorbet "unreachable code" error.
217
+ # TODO (typing): Is the revision expected to be non-nil nowadays? Can we
218
+ # just remove the `inferred_revision` code path now?
219
+ nilable_revision = T.let(revision, T.nilable(String))
220
+ if nilable_revision
190
221
  git.rev_parse(revision)
191
222
  else
192
223
  inferred_revision
193
224
  end
194
225
  end
195
226
 
227
+ sig {returns(String)}
196
228
  def local_ref
197
229
  return "#{self.remote}/#{self.branch}" unless self.branch.nil?
198
230
  return "tags/#{self.tag}" unless self.tag.nil?
199
- self.revision
231
+ # TODO (typing): Remove this `T.must` if we make `revision` non-nilable.
232
+ T.must(self.revision)
200
233
  end
201
234
 
235
+ # FIXME: The return value is bogus if this mirror has neither a branch nor a
236
+ # tag. It looks like this leads to a real bug affecting `braid push` when
237
+ # `--branch` is specified. File the bug or just fix it.
238
+ sig {returns(String)}
202
239
  def remote_ref
203
240
  self.branch.nil? ? "+refs/tags/#{self.tag}" : "+refs/heads/#{self.branch}"
204
241
  end
205
242
 
243
+ # Accessors for all the attributes listed in `ATTRIBUTES` and stored in
244
+ # `self.attributes`. Most of the accessors use the same names as the
245
+ # underlying attributes. The exception is the `path` attribute, whose
246
+ # accessor is named `remote_path` to avoid a conflict with the `path`
247
+ # accessor for the local path.
248
+ #
249
+ # TODO: Can we reduce this boilerplate and still type the accessors
250
+ # statically? If we move the config attributes to instance variables of the
251
+ # Mirror object, then we can use `attr_accessor`, but we'd have to somehow
252
+ # accommodate existing call sites that access `self.attributes` as a whole.
253
+
254
+ sig {returns(String)}
255
+ def url
256
+ self.attributes['url']
257
+ end
258
+
259
+ sig {params(new_value: String).void}
260
+ def url=(new_value)
261
+ self.attributes['url'] = new_value
262
+ end
263
+
264
+ sig {returns(T.nilable(String))}
265
+ def branch
266
+ self.attributes['branch']
267
+ end
268
+
269
+ sig {params(new_value: T.nilable(String)).void}
270
+ def branch=(new_value)
271
+ self.attributes['branch'] = new_value
272
+ end
273
+
274
+ sig {returns(T.nilable(String))}
206
275
  def remote_path
207
276
  self.attributes['path']
208
277
  end
209
278
 
279
+ sig {params(remote_path: T.nilable(String)).void}
210
280
  def remote_path=(remote_path)
211
281
  self.attributes['path'] = remote_path
212
282
  end
213
283
 
284
+ sig {returns(T.nilable(String))}
285
+ def tag
286
+ self.attributes['tag']
287
+ end
288
+
289
+ sig {params(new_value: T.nilable(String)).void}
290
+ def tag=(new_value)
291
+ self.attributes['tag'] = new_value
292
+ end
293
+
294
+ # The revision may be nil in the middle of `braid add`.
295
+ # TODO (typing): Look into restructuring `braid add` to avoid this?
296
+ sig {returns(T.nilable(String))}
297
+ def revision
298
+ self.attributes['revision']
299
+ end
300
+
301
+ sig {params(new_value: String).void}
302
+ def revision=(new_value)
303
+ self.attributes['revision'] = new_value
304
+ end
305
+
306
+ sig {returns(String)}
214
307
  def cached_url
215
308
  git_cache.path(url)
216
309
  end
217
310
 
311
+ sig {returns(String)}
218
312
  def remote
219
313
  "#{branch || tag || 'revision'}/braid/#{path}".gsub(/\/\./, '/_')
220
314
  end
221
315
 
222
316
  private
223
317
 
224
- DUMMY_BREAKING_CHANGE_CB = lambda { |desc|
318
+ DUMMY_BREAKING_CHANGE_CB = T.let(lambda { |desc|
225
319
  raise InternalError, 'Instantiated a mirror using an unsupported ' +
226
320
  'feature outside of configuration loading.'
227
- }
228
-
229
- def method_missing(name, *args)
230
- if ATTRIBUTES.find { |attribute| name.to_s =~ /^(#{attribute})(=)?$/ }
231
- if $2
232
- attributes[$1] = args[0]
233
- else
234
- attributes[$1]
235
- end
236
- else
237
- raise NameError, "unknown attribute `#{name}'"
238
- end
239
- end
321
+ }, BreakingChangeCallback)
240
322
 
323
+ sig {returns(T.nilable(String))}
241
324
  def inferred_revision
242
325
  local_commits = git.rev_list('HEAD', "-- #{path}").split("\n")
243
326
  remote_hashes = git.rev_list("--pretty=format:\"%T\"", remote).split('commit ').map do |chunk|
244
327
  chunk.split("\n", 2).map { |value| value.strip }
245
328
  end
246
- hash = nil
329
+ hash = T.let(nil, T.nilable(String))
247
330
  local_commits.each do |local_commit|
248
331
  local_tree = git.tree_hash(path, local_commit)
249
332
  match = remote_hashes.find { |_, remote_tree| local_tree == remote_tree }
@@ -255,12 +338,16 @@ DESC
255
338
  hash
256
339
  end
257
340
 
341
+ # TODO (typing): Return should not be nilable
342
+ sig {params(url: String, remote_path: T.nilable(String)).returns(T.nilable(String))}
258
343
  def self.extract_path_from_url(url, remote_path)
259
344
  if remote_path
260
345
  return File.basename(remote_path)
261
346
  end
262
347
 
263
- return nil unless url
348
+ # Avoid a Sorbet "unreachable code" error.
349
+ # TODO (typing): Fix this properly. Probably just remove this line?
350
+ return nil unless T.let(url, T.nilable(String))
264
351
  name = File.basename(url)
265
352
 
266
353
  if File.extname(name) == '.git'
@@ -1,3 +1,5 @@
1
+ # typed: true
2
+
1
3
  require 'singleton'
2
4
  require 'rubygems'
3
5
  require 'tempfile'
@@ -56,12 +58,12 @@ module Braid
56
58
  include Singleton
57
59
 
58
60
  def self.command;
59
- name.split('::').last.downcase;
61
+ T.unsafe(name).split('::').last.downcase;
60
62
  end
61
63
 
62
64
  # hax!
63
65
  def version
64
- status, out, err = exec!("#{self.class.command} --version")
66
+ _, out, _ = exec!("#{self.class.command} --version")
65
67
  out.sub(/^.* version/, '').strip.sub(/ .*$/, '').strip
66
68
  end
67
69
 
@@ -89,6 +91,9 @@ module Braid
89
91
  end
90
92
 
91
93
  def method_missing(name, *args)
94
+ # We have to use this rather than `T.unsafe` because `invoke` is
95
+ # private. See https://sorbet.org/docs/type-assertions#tbind.
96
+ T.bind(self, T.untyped)
92
97
  invoke(name, *args)
93
98
  end
94
99
 
@@ -138,12 +143,7 @@ module Braid
138
143
  # 'MERGE_MSG'), taking into account worktree configuration. The returned
139
144
  # path may be absolute or relative to the current working directory.
140
145
  def repo_file_path(path)
141
- if require_version('2.5') # support for --git-path
142
- invoke(:rev_parse, '--git-path', path)
143
- else
144
- # Git < 2.5 doesn't support linked worktrees anyway.
145
- File.join(invoke(:rev_parse, '--git-dir'), path)
146
- end
146
+ invoke(:rev_parse, '--git-path', path)
147
147
  end
148
148
 
149
149
  # If the current directory is not inside a git repository at all, this
@@ -166,6 +166,7 @@ module Braid
166
166
 
167
167
  def commit(message, *args)
168
168
  cmd = 'git commit --no-verify'
169
+ message_file = nil
169
170
  if message # allow nil
170
171
  message_file = Tempfile.new('braid_commit')
171
172
  message_file.print("Braid: #{message}")
@@ -272,7 +273,7 @@ module Braid
272
273
  if path.nil? || path == ''
273
274
  tree
274
275
  else
275
- m = /^([^ ]*) ([^ ]*) ([^\t]*)\t.*$/.match(invoke(:ls_tree, tree, path))
276
+ m = T.must(/^([^ ]*) ([^ ]*) ([^\t]*)\t.*$/.match(invoke(:ls_tree, tree, path)))
276
277
  mode = m[1]
277
278
  type = m[2]
278
279
  hash = m[3]
@@ -292,16 +293,16 @@ module Braid
292
293
  # file).
293
294
  def add_item_to_index(item, path, update_worktree)
294
295
  if item.is_a?(BlobWithMode)
295
- # Our minimum git version is 1.6.0 and the new --cacheinfo syntax
296
- # wasn't added until 2.0.0.
297
- invoke(:update_index, '--add', '--cacheinfo', item.mode, item.hash, path)
296
+ invoke(:update_index, '--add', '--cacheinfo', "#{item.mode},#{item.hash},#{path}")
298
297
  if update_worktree
299
298
  # XXX If this fails, we've already updated the index.
300
299
  invoke(:checkout_index, path)
301
300
  end
302
301
  else
303
- # Yes, if path == '', "git read-tree --prefix=/" works. :/
304
- invoke(:read_tree, "--prefix=#{path}/", update_worktree ? '-u' : '-i', item)
302
+ # According to
303
+ # https://lore.kernel.org/git/e48a281a4d3db0a04c0609fcb8658e4fcc797210.1646166271.git.gitgitgadget@gmail.com/,
304
+ # `--prefix=` is valid if the path is empty.
305
+ invoke(:read_tree, "--prefix=#{path}", update_worktree ? '-u' : '-i', item)
305
306
  end
306
307
  end
307
308
 
@@ -368,7 +369,7 @@ module Braid
368
369
  end
369
370
 
370
371
  def status_clean?
371
- status, out, err = exec('git status')
372
+ _, out, _ = exec('git status')
372
373
  !out.split("\n").grep(/nothing to commit/).empty?
373
374
  end
374
375
 
@@ -381,12 +382,13 @@ module Braid
381
382
  end
382
383
 
383
384
  def branch
384
- status, out, err = exec!("git branch | grep '*'")
385
+ _, out, _ = exec!("git branch | grep '*'")
385
386
  out[2..-1]
386
387
  end
387
388
 
388
389
  def clone(*args)
389
390
  # overrides builtin
391
+ T.bind(self, T.untyped) # Ditto the comment in `method_missing`.
390
392
  invoke(:clone, *args)
391
393
  end
392
394
 
@@ -404,11 +406,11 @@ module Braid
404
406
  dir = path(url)
405
407
 
406
408
  # remove local cache if it was created with --no-checkout
407
- if File.exists?("#{dir}/.git")
409
+ if File.exist?("#{dir}/.git")
408
410
  FileUtils.rm_r(dir)
409
411
  end
410
412
 
411
- if File.exists?(dir)
413
+ if File.exist?(dir)
412
414
  Dir.chdir(dir) do
413
415
  git.fetch
414
416
  end
@@ -1,9 +1,27 @@
1
+ # typed: strict
2
+
3
+ require 'braid/sorbet/setup'
4
+
1
5
  # One helper that is shared with the integration test harness and has no
2
6
  # dependencies on the rest of Braid.
3
7
  module Braid
4
8
  module Operations
9
+ extend T::Sig
10
+
5
11
  # Want to use https://github.com/thoughtbot/climate_control ?
6
- def self.with_modified_environment(dict)
12
+ #
13
+ # We have to declare the `&blk` parameter in order to reference it in the
14
+ # type annotation, even though the body doesn't use it. This causes the
15
+ # block to be converted to a proc at runtime, which has some performance
16
+ # cost (probably not important in the context of Braid). TODO: Find a way
17
+ # to avoid the performance cost? File a Sorbet enhancement request?
18
+ sig {
19
+ type_parameters(:R).params(
20
+ dict: T::Hash[String, String],
21
+ blk: T.proc.returns(T.type_parameter(:R))
22
+ ).returns(T.type_parameter(:R))
23
+ }
24
+ def self.with_modified_environment(dict, &blk)
7
25
  orig_dict = {}
8
26
  dict.each { |name, value|
9
27
  orig_dict[name] = ENV[name]
@@ -0,0 +1,75 @@
1
+ # typed: ignore
2
+
3
+ # This file contains a fake implementation of the subset of `sorbet-runtime`
4
+ # used by Braid that performs no runtime checks. See the "Type checking"
5
+ # section of development.md for background.
6
+
7
+ require 'singleton'
8
+
9
+ # Create our fake module at `Braid::T` so that if someone loads Braid into the
10
+ # same Ruby interpreter as other code that needs the real sorbet-runtime, we
11
+ # don't break the other code. (We don't officially support loading Braid as a
12
+ # library, but we may as well go ahead and put this infrastructure in place.)
13
+ # Code in the `Braid` module still uses normal references to `T`, so the Sorbet
14
+ # static analyzer (which doesn't read this file) doesn't see anything out of the
15
+ # ordinary, but those references resolve to `Braid::T` at runtime according to
16
+ # Ruby's constant lookup rules.
17
+ module Braid
18
+ module T
19
+
20
+ module Sig
21
+ def sig; end
22
+ end
23
+ def self.let(value, type)
24
+ value
25
+ end
26
+
27
+ # NOTICE: Like everything else in the fake Sorbet runtime (e.g., `sig`),
28
+ # these do not actually perform runtime checks. Currently, if you want a
29
+ # runtime check, you have to implement it yourself. We considered defining
30
+ # wrapper functions with different names to make this clearer, but then we'd
31
+ # lose the extra static checks that Sorbet performs on direct calls to
32
+ # `T.cast` and `T.must`.
33
+ def self.cast(value, type)
34
+ value
35
+ end
36
+ def self.must(value)
37
+ value
38
+ end
39
+ def self.unsafe(value)
40
+ value
41
+ end
42
+ def self.bind(value, type); end
43
+
44
+ class FakeType
45
+ include Singleton
46
+ end
47
+ FAKE_TYPE = FakeType.instance
48
+
49
+ def self.type_alias
50
+ FAKE_TYPE
51
+ end
52
+
53
+ def self.nilable(type)
54
+ FAKE_TYPE
55
+ end
56
+ def self.untyped
57
+ FAKE_TYPE
58
+ end
59
+ def self.noreturn
60
+ FAKE_TYPE
61
+ end
62
+ Boolean = FAKE_TYPE
63
+ module Array
64
+ def self.[](type)
65
+ FAKE_TYPE
66
+ end
67
+ end
68
+ module Hash
69
+ def self.[](key_type, value_type)
70
+ FAKE_TYPE
71
+ end
72
+ end
73
+
74
+ end
75
+ end
@@ -0,0 +1,18 @@
1
+ # typed: strict
2
+
3
+ # Code to set up the Sorbet runtime and Sorbet-related utilities used throughout
4
+ # the Braid code. Called by `lib/braid.rb` and `lib/braid/operations_lite.rb`
5
+ # before any other Braid code is loaded.
6
+
7
+ env_use_sorbet_runtime = ENV['BRAID_USE_SORBET_RUNTIME']
8
+ if env_use_sorbet_runtime == '1'
9
+ require 'sorbet-runtime'
10
+ elsif [nil, '0'].include?(env_use_sorbet_runtime)
11
+ require 'braid/sorbet/fake_runtime'
12
+ else
13
+ puts <<-MSG
14
+ Braid: Error: BRAID_USE_SORBET_RUNTIME environment variable has invalid
15
+ value #{env_use_sorbet_runtime.inspect}; it must be "1", "0", or unset.
16
+ MSG
17
+ exit(1)
18
+ end
data/lib/braid/version.rb CHANGED
@@ -1,3 +1,6 @@
1
+ # Ditto the comment in `check_gem.rb` regarding the `typed` sigil.
2
+ # typed: false
3
+
1
4
  module Braid
2
- VERSION = '1.1.5'.freeze
5
+ VERSION = '1.1.8'.freeze
3
6
  end
data/lib/braid.rb CHANGED
@@ -1,46 +1,65 @@
1
+ # typed: strict
2
+
3
+ require 'braid/sorbet/setup'
1
4
  require 'braid/version'
2
5
 
3
6
  module Braid
7
+ extend T::Sig
8
+
4
9
  OLD_CONFIG_FILE = '.braids'
5
10
  CONFIG_FILE = '.braids.json'
6
11
 
7
12
  # See the background in the "Supported environments" section of README.md.
8
13
  #
9
14
  # The newest Git feature that Braid is currently known to rely on is
10
- # `receive.denyCurrentBranch = updateInstead` (in
11
- # spec/integration/push_spec.rb), which was added in Git 2.3.0 (in 2015). It
15
+ # `git ls-remote --symref`, which was added in Git 2.8.0 (in 2016). It
12
16
  # doesn't seem worth even a small amount of work to remove that dependency and
13
17
  # support even older versions of Git. So set that as the declared requirement
14
18
  # for now. In general, a reasonable approach might be to try to support the
15
19
  # oldest version of Git in current "long-term support" versions of popular OS
16
20
  # distributions.
17
- REQUIRED_GIT_VERSION = '2.3.0'
21
+ REQUIRED_GIT_VERSION = '2.8.0'
18
22
 
23
+ @verbose = T.let(false, T::Boolean)
24
+
25
+ sig {returns(T::Boolean)}
19
26
  def self.verbose
20
27
  !!@verbose
21
28
  end
22
29
 
30
+ # TODO (typing): One would think `new_value` shouldn't be nilable, but
31
+ # apparently `lib/braid/main.rb` passes nil sometimes. Is that easy to fix?
32
+ # (Ditto with `self.force=` below.)
33
+ sig {params(new_value: T.nilable(T::Boolean)).void}
23
34
  def self.verbose=(new_value)
24
35
  @verbose = !!new_value
25
36
  end
26
37
 
38
+ @force = T.let(false, T::Boolean)
39
+
40
+ sig {returns(T::Boolean)}
27
41
  def self.force
28
42
  !!@force
29
43
  end
30
44
 
45
+ sig {params(new_value: T.nilable(T::Boolean)).void}
31
46
  def self.force=(new_value)
32
47
  @force = !!new_value
33
48
  end
34
49
 
50
+ sig {returns(T::Boolean)}
35
51
  def self.use_local_cache
36
52
  [nil, 'true', '1'].include?(ENV['BRAID_USE_LOCAL_CACHE'])
37
53
  end
38
54
 
55
+ sig {returns(String)}
39
56
  def self.local_cache_dir
40
57
  File.expand_path(ENV['BRAID_LOCAL_CACHE_DIR'] || "#{ENV['HOME']}/.braid/cache")
41
58
  end
42
59
 
43
60
  class BraidError < StandardError
61
+ extend T::Sig
62
+ sig {returns(String)}
44
63
  def message
45
64
  value = super
46
65
  value if value != self.class.name
@@ -48,6 +67,7 @@ module Braid
48
67
  end
49
68
 
50
69
  class InternalError < BraidError
70
+ sig {returns(String)}
51
71
  def message
52
72
  "internal error: #{super}"
53
73
  end