braid 1.1.6 → 1.1.7
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 +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]
|