braid 1.1.5 → 1.1.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/exe/braid +11 -0
- data/lib/braid/check_gem.rb +65 -0
- data/lib/braid/command.rb +18 -0
- data/lib/braid/commands/add.rb +41 -1
- data/lib/braid/commands/diff.rb +2 -1
- data/lib/braid/commands/push.rb +1 -0
- data/lib/braid/commands/remove.rb +1 -0
- data/lib/braid/commands/setup.rb +1 -0
- data/lib/braid/commands/status.rb +1 -0
- data/lib/braid/commands/update.rb +1 -0
- data/lib/braid/commands/upgrade_config.rb +1 -0
- data/lib/braid/config.rb +28 -4
- data/{bin/braid → lib/braid/main.rb} +16 -17
- data/lib/braid/mirror.rb +111 -24
- data/lib/braid/operations.rb +20 -18
- data/lib/braid/operations_lite.rb +19 -1
- data/lib/braid/sorbet/fake_runtime.rb +75 -0
- data/lib/braid/sorbet/setup.rb +18 -0
- data/lib/braid/version.rb +4 -1
- data/lib/braid.rb +23 -3
- metadata +26 -75
- data/.gitignore +0 -16
- data/.travis.yml +0 -15
- data/CONTRIBUTING.md +0 -24
- data/Gemfile +0 -3
- data/README.md +0 -234
- data/Rakefile +0 -12
- data/_config.yml +0 -1
- data/braid.gemspec +0 -35
- data/braids-json.schema.json +0 -91
- data/config_versions.md +0 -58
- data/spec/config_spec.rb +0 -59
- data/spec/fixtures/shiny/README +0 -3
- data/spec/fixtures/shiny/other-skit/layout.liquid +0 -219
- data/spec/fixtures/shiny/skit-layout.liquid.test +0 -2
- data/spec/fixtures/shiny/skit1.test +0 -2
- data/spec/fixtures/shiny-conf-1.0.9-lock/.braids.json +0 -10
- data/spec/fixtures/shiny-conf-1.0.9-lock/expected.braids.json +0 -9
- data/spec/fixtures/shiny-conf-1.0.9-lock/skit1/layouts/layout.liquid +0 -219
- data/spec/fixtures/shiny-conf-1.0.9-lock/skit1/preview.png +0 -0
- data/spec/fixtures/shiny-conf-breaking-changes/.braids +0 -14
- data/spec/fixtures/shiny-conf-breaking-changes/Spoon-Knife/README.md +0 -9
- data/spec/fixtures/shiny-conf-breaking-changes/Spoon-Knife/index.html +0 -20
- data/spec/fixtures/shiny-conf-breaking-changes/Spoon-Knife/styles.css +0 -17
- data/spec/fixtures/shiny-conf-breaking-changes/expected.braids.json +0 -10
- data/spec/fixtures/shiny-conf-breaking-changes/skit1/layouts/layout.liquid +0 -219
- data/spec/fixtures/shiny-conf-breaking-changes/skit1/preview.png +0 -0
- data/spec/fixtures/shiny-conf-future/.braids.json +0 -10
- data/spec/fixtures/shiny-conf-future/skit1/layouts/layout.liquid +0 -219
- data/spec/fixtures/shiny-conf-future/skit1/preview.png +0 -0
- data/spec/fixtures/shiny-conf-json-old-name/.braids +0 -9
- data/spec/fixtures/shiny-conf-json-old-name/expected.braids.json +0 -10
- data/spec/fixtures/shiny-conf-json-old-name/skit1/layouts/layout.liquid +0 -219
- data/spec/fixtures/shiny-conf-json-old-name/skit1/preview.png +0 -0
- data/spec/fixtures/shiny-conf-yaml/.braids +0 -8
- data/spec/fixtures/shiny-conf-yaml/expected.braids.json +0 -10
- data/spec/fixtures/shiny-conf-yaml/skit1/layouts/layout.liquid +0 -219
- data/spec/fixtures/shiny-conf-yaml/skit1/preview.png +0 -0
- data/spec/fixtures/shiny_skit1.2_merged/layouts/layout.liquid +0 -223
- data/spec/fixtures/shiny_skit1.2_merged/preview.png +0 -0
- data/spec/fixtures/shiny_skit1_conflicting/layouts/layout.liquid +0 -221
- data/spec/fixtures/shiny_skit1_conflicting/preview.png +0 -0
- data/spec/fixtures/shiny_skit1_mergeable/layouts/layout.liquid +0 -221
- data/spec/fixtures/shiny_skit1_mergeable/preview.png +0 -0
- data/spec/fixtures/skit1/layouts/layout.liquid +0 -219
- data/spec/fixtures/skit1/preview.png +0 -0
- data/spec/fixtures/skit1.1/layouts/layout.liquid +0 -219
- data/spec/fixtures/skit1.1_with_filter/.gitattributes +0 -1
- data/spec/fixtures/skit1.1_with_filter/layouts/layout.liquid +0 -219
- data/spec/fixtures/skit1.1_with_filter/preview.png +0 -0
- data/spec/fixtures/skit1.1x/layouts/layout.liquid +0 -219
- data/spec/fixtures/skit1.2/layouts/layout.liquid +0 -221
- data/spec/fixtures/skit1.3/layouts/README.md +0 -1
- data/spec/fixtures/skit1.3/layouts/layout.liquid +0 -221
- data/spec/fixtures/skit1_with_filter/.gitattributes +0 -1
- data/spec/fixtures/skit1_with_filter/layouts/layout.liquid +0 -219
- data/spec/fixtures/skit1_with_filter/preview.png +0 -0
- data/spec/integration/adding_spec.rb +0 -230
- data/spec/integration/config_versioning_spec.rb +0 -222
- data/spec/integration/diff_spec.rb +0 -597
- data/spec/integration/integration_helper.rb +0 -129
- data/spec/integration/push_spec.rb +0 -399
- data/spec/integration/remove_spec.rb +0 -81
- data/spec/integration/status_spec.rb +0 -165
- data/spec/integration/updating_spec.rb +0 -487
- data/spec/mirror_spec.rb +0 -119
- data/spec/operations_spec.rb +0 -66
- 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
|
-
|
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']
|
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
|
-
|
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
|
-
|
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
|
-
|
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'
|
data/lib/braid/operations.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
#
|
304
|
-
|
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
|
-
|
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
|
-
|
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.
|
409
|
+
if File.exist?("#{dir}/.git")
|
408
410
|
FileUtils.rm_r(dir)
|
409
411
|
end
|
410
412
|
|
411
|
-
if File.
|
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
|
-
|
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
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
|
-
# `
|
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.
|
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
|