braid 1.1.5 → 1.1.8

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.
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