braid 1.1.6 → 1.1.7
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.
- checksums.yaml +4 -4
- data/exe/braid +11 -0
- data/lib/braid/check_gem.rb +58 -0
- data/lib/braid/command.rb +13 -0
- data/lib/braid/commands/diff.rb +1 -1
- data/lib/braid/config.rb +28 -4
- data/{bin/braid → lib/braid/main.rb} +1 -16
- data/lib/braid/mirror.rb +110 -23
- data/lib/braid/operations.rb +5 -4
- data/lib/braid/operations_lite.rb +19 -1
- data/lib/braid/sorbet/fake_runtime.rb +68 -0
- data/lib/braid/sorbet/setup.rb +18 -0
- data/lib/braid/version.rb +1 -1
- data/lib/braid.rb +21 -0
- metadata +12 -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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ad14f6dd7a5d428c5796bbd19039aae6e59f5803d93da3889fe0a92a8afd032b
|
4
|
+
data.tar.gz: 9d6bcfd73c5a306f853e6b9653edda5aff6a7bbd3541b6ab644bd7b96bc510e6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 817cd08525921352f103a81f05b6c550e8eaff62fb647c04d0ecb746e6ed425a78ef1bea4a477786675c406b0e23982f36a52eea205278a3b25ccee6e64ecdf5
|
7
|
+
data.tar.gz: 76d9e62b1d58a437a8eae77f879b5a66ec6a30cda270d267cc458e621a3f1560fa192f5cc435b5e718c9f093691464d458b049d2e236864d0faea6e801c89629
|
data/exe/braid
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# I haven't found a clean way to get Sorbet to check this file since the name
|
4
|
+
# doesn't end in `.rb`. (If I add `--file bin/braid` to `sorbet/config`, then
|
5
|
+
# the Sorbet language server doesn't support using Watchman.) So move all the
|
6
|
+
# interesting code to `lib/braid/main.rb` and leave this as a launcher that
|
7
|
+
# doesn't have any code worth type checking. ~ Matt 2022-04-02
|
8
|
+
|
9
|
+
require_relative '../lib/braid/check_gem'
|
10
|
+
|
11
|
+
require 'braid/main'
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# Braid has several entry points that run code from Ruby gems (either Braid
|
2
|
+
# itself or dependencies such as Sorbet) and expect to get the correct versions
|
3
|
+
# (either the same copy of Braid or versions of dependencies consistent with the
|
4
|
+
# gemspec or Gemfile, as applicable). This currently applies to the following
|
5
|
+
# entry points:
|
6
|
+
#
|
7
|
+
# - The Braid CLI launcher: `exe/braid`
|
8
|
+
# - The Braid test suite under `spec`
|
9
|
+
# - The Rakefile: Has a target to run the Sorbet type checker and wants the
|
10
|
+
# correct version for the current version of Braid.
|
11
|
+
#
|
12
|
+
# If these entry points are invoked without first activating the correct gems,
|
13
|
+
# then other globally installed versions might be used by default, leading to
|
14
|
+
# unintended behavior. So we have each of these entry points check that a Braid
|
15
|
+
# gem is active in the current Ruby interpreter _and_ the entry point is part of
|
16
|
+
# that same Braid gem. If so, we can assume that the correct versions of
|
17
|
+
# dependencies are active as well.
|
18
|
+
#
|
19
|
+
# To implement the check, the entry point file loads its corresponding copy of
|
20
|
+
# check_gem.rb via `require_relative`, and that copy of check_gem.rb checks that
|
21
|
+
# it is part of the active Braid gem. Note that loading check_gem.rb via
|
22
|
+
# `require 'lib/braid/check_gem'` would defeat the purpose, since it would
|
23
|
+
# activate whatever Braid gem is visible and then check that that gem's copy of
|
24
|
+
# check_gem.rb is part of the active Braid gem (which it would always be), not
|
25
|
+
# whether the _caller_ is part of the active Braid gem.
|
26
|
+
#
|
27
|
+
# Common means of activating the correct gems before invoking an entry point
|
28
|
+
# include:
|
29
|
+
#
|
30
|
+
# - Bundler: Used when developing Braid itself and possibly by some projects
|
31
|
+
# that use Braid. It modifies the environment so that every Ruby subprocess
|
32
|
+
# activates the gems from the Gemfile.lock.
|
33
|
+
# - A wrapper for `exe/braid` generated by `gem install --wrappers`.
|
34
|
+
#
|
35
|
+
# Note that we don't have every `.rb` file that is part of Braid load
|
36
|
+
# check_gem.rb: that would be too invasive and only helps in the case where a
|
37
|
+
# user manually added Braid's `lib` dir to the load path, which is a mistake
|
38
|
+
# that a reasonable user is much less likely to make than just running an entry
|
39
|
+
# point without the proper setup.
|
40
|
+
#
|
41
|
+
# TODO: Is there a more standard way to do this check? One would think it would
|
42
|
+
# be potentially applicable to any Ruby project, though maybe most don't care as
|
43
|
+
# much as we do about catching the problem up front.
|
44
|
+
|
45
|
+
braid_spec = Gem.loaded_specs['braid']
|
46
|
+
if braid_spec.nil? || __FILE__ != braid_spec.gem_dir + '/lib/braid/check_gem.rb'
|
47
|
+
STDERR.puts <<-MSG
|
48
|
+
Error: The RubyGems environment is not set up correctly for Braid.
|
49
|
+
- If you're using a copy of Braid installed globally via 'gem install' or
|
50
|
+
similar, use the 'braid' wrapper script (which is typically installed at a
|
51
|
+
location like ~/.gem/ruby/bin/braid depending on your environment) instead of
|
52
|
+
running 'exe/braid' directly.
|
53
|
+
- If you're using a copy of Braid managed by Bundler (including when developing
|
54
|
+
Braid itself), prepend 'bundle exec' to your command or use a binstub
|
55
|
+
generated by 'bundle binstubs'.
|
56
|
+
MSG
|
57
|
+
exit(1)
|
58
|
+
end
|
data/lib/braid/command.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
|
+
# typed: true
|
1
2
|
module Braid
|
2
3
|
class Command
|
4
|
+
extend T::Sig
|
5
|
+
|
3
6
|
class InvalidRevision < BraidError
|
4
7
|
end
|
5
8
|
|
@@ -23,10 +26,12 @@ module Braid
|
|
23
26
|
exit(1)
|
24
27
|
end
|
25
28
|
|
29
|
+
sig {params(str: String).void}
|
26
30
|
def self.msg(str)
|
27
31
|
puts "Braid: #{str}"
|
28
32
|
end
|
29
33
|
|
34
|
+
sig {params(str: String).void}
|
30
35
|
def msg(str)
|
31
36
|
self.class.msg(str)
|
32
37
|
end
|
@@ -35,16 +40,19 @@ module Braid
|
|
35
40
|
@config ||= Config.new({'mode' => config_mode})
|
36
41
|
end
|
37
42
|
|
43
|
+
sig {returns(T::Boolean)}
|
38
44
|
def verbose?
|
39
45
|
Braid.verbose
|
40
46
|
end
|
41
47
|
|
48
|
+
sig {returns(T::Boolean)}
|
42
49
|
def force?
|
43
50
|
Braid.force
|
44
51
|
end
|
45
52
|
|
46
53
|
private
|
47
54
|
|
55
|
+
sig {returns(Config::ConfigMode)}
|
48
56
|
def config_mode
|
49
57
|
Config::MODE_MAY_WRITE
|
50
58
|
end
|
@@ -63,14 +71,17 @@ module Braid
|
|
63
71
|
git.remote_rm(mirror.remote) unless options['keep']
|
64
72
|
end
|
65
73
|
|
74
|
+
sig {returns(T::Boolean)}
|
66
75
|
def use_local_cache?
|
67
76
|
Braid.use_local_cache
|
68
77
|
end
|
69
78
|
|
79
|
+
sig {void}
|
70
80
|
def self.verify_git_version!
|
71
81
|
git.require_version!(REQUIRED_GIT_VERSION)
|
72
82
|
end
|
73
83
|
|
84
|
+
sig {void}
|
74
85
|
def self.check_working_dir!
|
75
86
|
# If we aren't in a git repository at all, git.is_inside_worktree will
|
76
87
|
# propagate a "fatal: Not a git repository" ShellException.
|
@@ -82,6 +93,7 @@ module Braid
|
|
82
93
|
end
|
83
94
|
end
|
84
95
|
|
96
|
+
sig {void}
|
85
97
|
def bail_on_local_changes!
|
86
98
|
git.ensure_clean!
|
87
99
|
end
|
@@ -100,6 +112,7 @@ module Braid
|
|
100
112
|
end
|
101
113
|
end
|
102
114
|
|
115
|
+
sig {void}
|
103
116
|
def add_config_file
|
104
117
|
git.rm(OLD_CONFIG_FILE) if File.exist?(OLD_CONFIG_FILE)
|
105
118
|
git.add(CONFIG_FILE)
|
data/lib/braid/commands/diff.rb
CHANGED
@@ -35,7 +35,7 @@ module Braid
|
|
35
35
|
|
36
36
|
# XXX: Warn if the user specifies file paths that are outside the
|
37
37
|
# mirror? Currently, they just won't match anything.
|
38
|
-
git.diff_to_stdout(*mirror.diff_args(
|
38
|
+
git.diff_to_stdout(*mirror.diff_args(options['git_diff_args']))
|
39
39
|
|
40
40
|
clear_remote(mirror, options)
|
41
41
|
end
|
data/lib/braid/config.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# typed: true
|
1
2
|
require 'yaml'
|
2
3
|
require 'json'
|
3
4
|
require 'yaml/store'
|
@@ -48,6 +49,10 @@ require 'yaml/store'
|
|
48
49
|
|
49
50
|
module Braid
|
50
51
|
class Config
|
52
|
+
extend T::Sig
|
53
|
+
|
54
|
+
# TODO (typing): Migrate to T::Enum?
|
55
|
+
ConfigMode = T.type_alias { Integer }
|
51
56
|
|
52
57
|
MODE_UPGRADE = 1
|
53
58
|
MODE_READ_ONLY = 2
|
@@ -56,11 +61,13 @@ module Braid
|
|
56
61
|
CURRENT_CONFIG_VERSION = 1
|
57
62
|
|
58
63
|
class PathAlreadyInUse < BraidError
|
64
|
+
sig {returns(String)}
|
59
65
|
def message
|
60
66
|
"path already in use: #{super}"
|
61
67
|
end
|
62
68
|
end
|
63
69
|
class MirrorDoesNotExist < BraidError
|
70
|
+
sig {returns(String)}
|
64
71
|
def message
|
65
72
|
"mirror does not exist: #{super}"
|
66
73
|
end
|
@@ -70,9 +77,15 @@ module Braid
|
|
70
77
|
end
|
71
78
|
|
72
79
|
# For upgrade-config command only. XXX: Ideally would be immutable.
|
73
|
-
|
80
|
+
sig {returns(Integer)}
|
81
|
+
attr_reader :config_version
|
82
|
+
sig {returns(T::Boolean)}
|
83
|
+
attr_reader :config_existed
|
84
|
+
sig {returns(T::Array[String])}
|
85
|
+
attr_reader :breaking_change_descs
|
74
86
|
|
75
87
|
# options: config_file, old_config_files, mode
|
88
|
+
sig {params(options: T.untyped).void}
|
76
89
|
def initialize(options = {})
|
77
90
|
@config_file = options['config_file'] || CONFIG_FILE
|
78
91
|
old_config_files = options['old_config_files'] || [OLD_CONFIG_FILE]
|
@@ -150,6 +163,7 @@ MSG
|
|
150
163
|
|
151
164
|
end
|
152
165
|
|
166
|
+
sig {params(url: String, options: T.untyped).returns(Mirror)}
|
153
167
|
def add_from_options(url, options)
|
154
168
|
mirror = Mirror.new_from_options(url, options)
|
155
169
|
|
@@ -157,33 +171,39 @@ MSG
|
|
157
171
|
mirror
|
158
172
|
end
|
159
173
|
|
174
|
+
sig {returns(T::Array[Mirror])}
|
160
175
|
def mirrors
|
161
176
|
@db.keys
|
162
177
|
end
|
163
178
|
|
179
|
+
sig {params(path: String).returns(T.nilable(Mirror))}
|
164
180
|
def get(path)
|
165
181
|
key = path.to_s.sub(/\/$/, '')
|
166
182
|
attributes = @db[key]
|
167
183
|
attributes ? Mirror.new(path, attributes) : nil
|
168
184
|
end
|
169
185
|
|
186
|
+
sig {params(path: String).returns(Mirror)}
|
170
187
|
def get!(path)
|
171
188
|
mirror = get(path)
|
172
189
|
raise MirrorDoesNotExist, path unless mirror
|
173
190
|
mirror
|
174
191
|
end
|
175
192
|
|
193
|
+
sig {params(mirror: Mirror).void}
|
176
194
|
def add(mirror)
|
177
195
|
raise PathAlreadyInUse, mirror.path if get(mirror.path)
|
178
196
|
write_mirror(mirror)
|
179
197
|
write_db
|
180
198
|
end
|
181
199
|
|
200
|
+
sig {params(mirror: Mirror).void}
|
182
201
|
def remove(mirror)
|
183
202
|
@db.delete(mirror.path)
|
184
203
|
write_db
|
185
204
|
end
|
186
205
|
|
206
|
+
sig {params(mirror: Mirror).void}
|
187
207
|
def update(mirror)
|
188
208
|
raise MirrorDoesNotExist, mirror.path unless get(mirror.path)
|
189
209
|
write_mirror(mirror)
|
@@ -191,6 +211,7 @@ MSG
|
|
191
211
|
end
|
192
212
|
|
193
213
|
# Public for upgrade-config command only.
|
214
|
+
sig {void}
|
194
215
|
def write_db
|
195
216
|
new_db = {}
|
196
217
|
@db.keys.sort.each do |key|
|
@@ -211,11 +232,12 @@ MSG
|
|
211
232
|
|
212
233
|
private
|
213
234
|
|
235
|
+
sig {params(config_file: String, old_config_files: T::Array[String]).returns(T.untyped)}
|
214
236
|
def load_config(config_file, old_config_files)
|
215
237
|
(old_config_files + [config_file]).each do |file|
|
216
238
|
next unless File.exist?(file)
|
217
239
|
begin
|
218
|
-
store = YAML::Store.new(file)
|
240
|
+
store = T.let(YAML::Store, T.untyped).new(file)
|
219
241
|
data = {}
|
220
242
|
store.transaction(true) do
|
221
243
|
store.roots.each do |path|
|
@@ -228,15 +250,17 @@ MSG
|
|
228
250
|
return data if data
|
229
251
|
end
|
230
252
|
end
|
231
|
-
|
253
|
+
nil
|
232
254
|
end
|
233
255
|
|
256
|
+
sig {params(mirror: Mirror).void}
|
234
257
|
def write_mirror(mirror)
|
235
258
|
@db[mirror.path] = clean_attributes(mirror.attributes)
|
236
259
|
end
|
237
260
|
|
261
|
+
sig {params(hash: T::Hash[String, T.untyped]).returns(T::Hash[String, T.untyped])}
|
238
262
|
def clean_attributes(hash)
|
239
|
-
hash.reject { |
|
263
|
+
hash.reject { |_, v| v.nil? }
|
240
264
|
end
|
241
265
|
end
|
242
266
|
end
|
@@ -1,18 +1,3 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
# Duplicated from Braid::Command.run. :(
|
4
|
-
def die(msg)
|
5
|
-
puts "Braid: Error: #{msg}"
|
6
|
-
exit(1)
|
7
|
-
end
|
8
|
-
|
9
|
-
# If we assume Ruby >= 2.0, we can use __dir__.
|
10
|
-
libdir = File.expand_path(File.dirname(File.realpath(__FILE__)) + '/../lib')
|
11
|
-
unless File.exists?(libdir)
|
12
|
-
# Don't silently fall back to a different globally installed copy of Braid!
|
13
|
-
die "Cannot find Braid's 'lib' directory."
|
14
|
-
end
|
15
|
-
$LOAD_PATH.unshift(libdir)
|
16
1
|
require 'braid'
|
17
2
|
|
18
3
|
require 'rubygems'
|
@@ -138,7 +123,7 @@ Main {
|
|
138
123
|
|
139
124
|
mixin :optional_local_path, :option_verbose, :option_keep_remote
|
140
125
|
|
141
|
-
synopsis
|
126
|
+
synopsis(Main::Usage.default_synopsis(self) + ' [-- git_diff_arg*]')
|
142
127
|
|
143
128
|
run {
|
144
129
|
if @argv.length > 0 && @argv[0] == '--'
|
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,6 +79,7 @@ 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
|
|
@@ -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
@@ -61,7 +61,7 @@ module Braid
|
|
61
61
|
|
62
62
|
# hax!
|
63
63
|
def version
|
64
|
-
|
64
|
+
_, out, _ = exec!("#{self.class.command} --version")
|
65
65
|
out.sub(/^.* version/, '').strip.sub(/ .*$/, '').strip
|
66
66
|
end
|
67
67
|
|
@@ -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}")
|
@@ -303,7 +304,7 @@ module Braid
|
|
303
304
|
# According to
|
304
305
|
# https://lore.kernel.org/git/e48a281a4d3db0a04c0609fcb8658e4fcc797210.1646166271.git.gitgitgadget@gmail.com/,
|
305
306
|
# `--prefix=` is valid if the path is empty.
|
306
|
-
|
307
|
+
invoke(:read_tree, "--prefix=#{path}", update_worktree ? '-u' : '-i', item)
|
307
308
|
end
|
308
309
|
end
|
309
310
|
|
@@ -370,7 +371,7 @@ module Braid
|
|
370
371
|
end
|
371
372
|
|
372
373
|
def status_clean?
|
373
|
-
|
374
|
+
_, out, _ = exec('git status')
|
374
375
|
!out.split("\n").grep(/nothing to commit/).empty?
|
375
376
|
end
|
376
377
|
|
@@ -383,7 +384,7 @@ module Braid
|
|
383
384
|
end
|
384
385
|
|
385
386
|
def branch
|
386
|
-
|
387
|
+
_, out, _ = exec!("git branch | grep '*'")
|
387
388
|
out[2..-1]
|
388
389
|
end
|
389
390
|
|
@@ -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]
|