braid 1.1.7 → 1.1.9
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/lib/braid/check_gem.rb +7 -0
- data/lib/braid/command.rb +6 -0
- data/lib/braid/commands/add.rb +41 -1
- data/lib/braid/commands/diff.rb +2 -1
- data/lib/braid/commands/push.rb +7 -5
- 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/main.rb +17 -3
- data/lib/braid/mirror.rb +31 -16
- data/lib/braid/operations.rb +241 -80
- data/lib/braid/sorbet/fake_runtime.rb +10 -0
- data/lib/braid/version.rb +4 -1
- data/lib/braid.rb +2 -3
- metadata +20 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0ddcc3421c5a2dffcf7634e71118d7392d9f506ab49064f25eb2c38f7f0d67b1
|
4
|
+
data.tar.gz: d3ad647f242d287ece52de41be7edb56cfdc3c469336ee84bb35c15257ca4d36
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f002d88fa8bb752ab878d746c8b1ead6c73b80518d37a06f261eba2b99540c6081c7991a9f404ec0ca26c39165b6e9345725e4b4f49f52ad132ca1e359465e39
|
7
|
+
data.tar.gz: 4da0947c03c1846b073b331f4118f8b07e42b4138c3450b3f2ce6ba967e3a8451edf843622e3e6b6bbe4f5c70951866d7c6abbcc7e1211d10501b75fefa8e095
|
data/lib/braid/check_gem.rb
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
# Since this file can't safely depend on the Sorbet runtime, it isn't prudent to
|
2
|
+
# try to commit to `typed: true` even if the file currently passes type checking
|
3
|
+
# without needing any references to `T`. Like `exe/braid`, this file doesn't
|
4
|
+
# have much code worth type checking.
|
5
|
+
#
|
6
|
+
# typed: false
|
7
|
+
|
1
8
|
# Braid has several entry points that run code from Ruby gems (either Braid
|
2
9
|
# itself or dependencies such as Sorbet) and expect to get the correct versions
|
3
10
|
# (either the same copy of Braid or versions of dependencies consistent with the
|
data/lib/braid/command.rb
CHANGED
@@ -17,6 +17,11 @@ module Braid
|
|
17
17
|
klass.new.run(*args)
|
18
18
|
|
19
19
|
rescue BraidError => error
|
20
|
+
handle_error(error)
|
21
|
+
end
|
22
|
+
|
23
|
+
sig {params(error: BraidError).returns(T.noreturn)}
|
24
|
+
def self.handle_error(error)
|
20
25
|
case error
|
21
26
|
when Operations::ShellExecutionError
|
22
27
|
msg "Shell error: #{error.message}"
|
@@ -36,6 +41,7 @@ module Braid
|
|
36
41
|
self.class.msg(str)
|
37
42
|
end
|
38
43
|
|
44
|
+
sig {returns(Config)}
|
39
45
|
def config
|
40
46
|
@config ||= Config.new({'mode' => config_mode})
|
41
47
|
end
|
data/lib/braid/commands/add.rb
CHANGED
@@ -1,15 +1,55 @@
|
|
1
|
+
# typed: true
|
1
2
|
module Braid
|
2
3
|
module Commands
|
3
4
|
class Add < Command
|
5
|
+
# Returns the default branch name of the repository at the given URL, or
|
6
|
+
# nil if it couldn't be determined.
|
7
|
+
#
|
8
|
+
# We won't be able to determine a default branch in certain cases that we
|
9
|
+
# expect to be unusual in the context of Braid, such as if the HEAD is
|
10
|
+
# detached or points to a ref outside of `refs/heads`. (Presumably, the
|
11
|
+
# same thing happens if the server is too old to report symrefs to us.)
|
12
|
+
# In those cases, a plausible alternative behavior would be to just lock
|
13
|
+
# the mirror to the remote HEAD revision, but that's probably not what the
|
14
|
+
# user wants. It's much more likely that something is wrong and Braid
|
15
|
+
# should report an error.
|
16
|
+
def get_default_branch_name(url)
|
17
|
+
head_targets = []
|
18
|
+
# The `HEAD` parameter here doesn't appear to do an exact match (it
|
19
|
+
# appears to match any ref with `HEAD` as the last path component, such
|
20
|
+
# as `refs/remotes/origin/HEAD` in the unusual case where the repository
|
21
|
+
# contains its own remote-tracking branches), but it reduces the data we
|
22
|
+
# have to scan a bit.
|
23
|
+
git.ls_remote(['--symref', url, 'HEAD']).split("\n").each do |line|
|
24
|
+
m = /^ref: (.*)\tHEAD$/.match(line)
|
25
|
+
head_targets.push(m[1]) if m
|
26
|
+
end
|
27
|
+
return nil unless head_targets.size == 1
|
28
|
+
m = /^refs\/heads\/(.*)$/.match(head_targets[0])
|
29
|
+
return nil unless m
|
30
|
+
m[1]
|
31
|
+
end
|
32
|
+
|
4
33
|
def run(url, options = {})
|
5
34
|
with_reset_on_error do
|
35
|
+
if options['branch'].nil? && options['tag'].nil? && options['revision'].nil?
|
36
|
+
default_branch = get_default_branch_name(url)
|
37
|
+
if default_branch.nil?
|
38
|
+
raise BraidError, <<-MSG
|
39
|
+
Failed to detect the default branch of the remote repository. Please specify
|
40
|
+
the branch you want to use via the --branch option.
|
41
|
+
MSG
|
42
|
+
end
|
43
|
+
options['branch'] = default_branch
|
44
|
+
end
|
45
|
+
|
6
46
|
mirror = config.add_from_options(url, options)
|
7
47
|
add_config_file
|
8
48
|
|
9
49
|
mirror.branch = nil if options['revision']
|
10
50
|
raise BraidError, 'Can not add mirror specifying both a revision and a tag' if options['revision'] && mirror.tag
|
11
51
|
|
12
|
-
branch_message =
|
52
|
+
branch_message = mirror.branch.nil? ? '' : " branch '#{mirror.branch}'"
|
13
53
|
tag_message = mirror.tag.nil? ? '' : " tag '#{mirror.tag}'"
|
14
54
|
revision_message = options['revision'] ? " at #{display_revision(mirror, options['revision'])}" : ''
|
15
55
|
msg "Adding mirror of '#{mirror.url}'#{branch_message}#{tag_message}#{revision_message}."
|
data/lib/braid/commands/diff.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# typed: true
|
1
2
|
module Braid
|
2
3
|
module Commands
|
3
4
|
class Diff < Command
|
@@ -35,7 +36,7 @@ module Braid
|
|
35
36
|
|
36
37
|
# XXX: Warn if the user specifies file paths that are outside the
|
37
38
|
# mirror? Currently, they just won't match anything.
|
38
|
-
git.diff_to_stdout(
|
39
|
+
git.diff_to_stdout(mirror.diff_args(options['git_diff_args']))
|
39
40
|
|
40
41
|
clear_remote(mirror, options)
|
41
42
|
end
|
data/lib/braid/commands/push.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# typed: true
|
1
2
|
require 'fileutils'
|
2
3
|
require 'tmpdir'
|
3
4
|
|
@@ -43,7 +44,8 @@ module Braid
|
|
43
44
|
end
|
44
45
|
clone_dir = Dir.tmpdir + "/braid_push.#{$$}"
|
45
46
|
Dir.mkdir(clone_dir)
|
46
|
-
|
47
|
+
# TODO (typing): Remove this `T.must` somehow?
|
48
|
+
remote_url = T.must(git.remote_url(mirror.remote))
|
47
49
|
if remote_url == mirror.cached_url
|
48
50
|
remote_url = mirror.url
|
49
51
|
elsif File.directory?(remote_url)
|
@@ -67,7 +69,7 @@ module Braid
|
|
67
69
|
File.open('.git/objects/info/alternates', 'wb') { |f|
|
68
70
|
f.puts(odb_paths)
|
69
71
|
}
|
70
|
-
git.fetch(remote_url, mirror.remote_ref)
|
72
|
+
git.fetch(remote_url, [mirror.remote_ref])
|
71
73
|
new_tree = git.make_tree_with_item(base_revision,
|
72
74
|
mirror.remote_path || '', local_mirror_item)
|
73
75
|
if git.require_version('2.27')
|
@@ -105,11 +107,11 @@ module Braid
|
|
105
107
|
# Update HEAD the same way git.checkout(base_revision) would, but
|
106
108
|
# don't populate the index or working tree (to save us the trouble of
|
107
109
|
# emptying them again before the git.read_tree).
|
108
|
-
git.update_ref('--no-deref', 'HEAD', base_revision)
|
109
|
-
git.
|
110
|
+
git.update_ref(['--no-deref', 'HEAD', base_revision])
|
111
|
+
git.read_tree_um(new_tree)
|
110
112
|
system('git commit -v')
|
111
113
|
msg "Pushing changes to remote branch #{branch}."
|
112
|
-
git.push(remote_url, "HEAD:refs/heads/#{branch}")
|
114
|
+
git.push([remote_url, "HEAD:refs/heads/#{branch}"])
|
113
115
|
end
|
114
116
|
FileUtils.rm_r(clone_dir)
|
115
117
|
|
data/lib/braid/commands/setup.rb
CHANGED
data/lib/braid/main.rb
CHANGED
@@ -1,13 +1,24 @@
|
|
1
|
+
# typed: true
|
2
|
+
|
1
3
|
require 'braid'
|
2
4
|
|
3
5
|
require 'rubygems'
|
4
6
|
require 'main'
|
5
7
|
|
8
|
+
# This is needed for `T` below to resolve to `Braid::T` when using the fake
|
9
|
+
# Sorbet runtime. TODO: Indent the contents and accept the large diff?
|
10
|
+
module Braid
|
11
|
+
|
6
12
|
Home = File.expand_path(ENV['HOME'] || '~')
|
7
13
|
|
8
14
|
# mostly blantantly stolen from ara's punch script
|
9
15
|
# main kicks ass!
|
10
|
-
Main {
|
16
|
+
T.unsafe(Main).run {
|
17
|
+
# `Main` is somewhat mind-bending and I'm unsure what the type of `self`
|
18
|
+
# actually is here, but whatever it is, we don't have a type declaration for
|
19
|
+
# it.
|
20
|
+
T.bind(self, T.untyped)
|
21
|
+
|
11
22
|
description <<-TXT
|
12
23
|
braid is a simple tool to help track git repositories inside a git repository.
|
13
24
|
|
@@ -20,7 +31,8 @@ Main {
|
|
20
31
|
# The "main" library doesn't provide a way to do this??
|
21
32
|
def check_no_extra_args!
|
22
33
|
if @argv.length > 0
|
23
|
-
|
34
|
+
Braid::Command.handle_error(
|
35
|
+
Braid::BraidError.new('Extra argument(s) passed to command.'))
|
24
36
|
end
|
25
37
|
end
|
26
38
|
|
@@ -123,7 +135,7 @@ Main {
|
|
123
135
|
|
124
136
|
mixin :optional_local_path, :option_verbose, :option_keep_remote
|
125
137
|
|
126
|
-
synopsis(Main::Usage.default_synopsis(self) + ' [-- git_diff_arg*]')
|
138
|
+
synopsis(T.unsafe(Main::Usage).default_synopsis(self) + ' [-- git_diff_arg*]')
|
127
139
|
|
128
140
|
run {
|
129
141
|
if @argv.length > 0 && @argv[0] == '--'
|
@@ -318,3 +330,5 @@ Main {
|
|
318
330
|
|
319
331
|
run { help! }
|
320
332
|
}
|
333
|
+
|
334
|
+
end
|
data/lib/braid/mirror.rb
CHANGED
@@ -86,7 +86,7 @@ DESC
|
|
86
86
|
raise NoTagAndBranch if options['tag'] && options['branch']
|
87
87
|
|
88
88
|
tag = options['tag']
|
89
|
-
branch = options['branch']
|
89
|
+
branch = options['branch']
|
90
90
|
|
91
91
|
path = (options['path'] || extract_path_from_url(url, options['remote_path'])).sub(/\/$/, '')
|
92
92
|
raise PathRequired unless path
|
@@ -107,7 +107,7 @@ DESC
|
|
107
107
|
branch.nil? && tag.nil?
|
108
108
|
end
|
109
109
|
|
110
|
-
sig {params(commit:
|
110
|
+
sig {params(commit: Operations::Git::ObjectExpr).returns(T::Boolean)}
|
111
111
|
def merged?(commit)
|
112
112
|
# tip from spearce in #git:
|
113
113
|
# `test z$(git merge-base A B) = z$(git rev-parse --verify A)`
|
@@ -115,9 +115,7 @@ DESC
|
|
115
115
|
!!base_revision && git.merge_base(commit, base_revision) == commit
|
116
116
|
end
|
117
117
|
|
118
|
-
|
119
|
-
# Braid::Operations::Git::TreeItem.
|
120
|
-
sig {params(revision: String).returns(T.untyped)}
|
118
|
+
sig {params(revision: String).returns(Operations::Git::TreeItem)}
|
121
119
|
def upstream_item_for_revision(revision)
|
122
120
|
git.get_tree_item(revision, self.remote_path)
|
123
121
|
end
|
@@ -211,16 +209,28 @@ DESC
|
|
211
209
|
git.remote_url(remote) == cached_url
|
212
210
|
end
|
213
211
|
|
214
|
-
sig {returns(
|
212
|
+
sig {returns(Operations::Git::ObjectID)}
|
215
213
|
def base_revision
|
216
|
-
#
|
217
|
-
#
|
218
|
-
#
|
219
|
-
|
220
|
-
|
221
|
-
|
214
|
+
# TODO (typing): We think `revision` should always be non-nil here these
|
215
|
+
# days and we can completely drop the `inferred_revision` code, but we're
|
216
|
+
# waiting for a better time to actually make this runtime behavior change
|
217
|
+
# and accept any risk of breakage
|
218
|
+
# (https://github.com/cristibalan/braid/pull/105/files#r857150464).
|
219
|
+
#
|
220
|
+
# Temporary variable
|
221
|
+
# (https://sorbet.org/docs/flow-sensitive#limitations-of-flow-sensitivity)
|
222
|
+
revision1 = revision
|
223
|
+
if revision1
|
224
|
+
git.rev_parse(revision1)
|
222
225
|
else
|
223
|
-
inferred_revision
|
226
|
+
# NOTE: Given that `inferred_revision` does appear to return nil on one
|
227
|
+
# code path, using this `T.must` and giving `base_revision` a
|
228
|
+
# non-nilable return type presents a theoretical risk of leading us to
|
229
|
+
# make changes to callers that break things at runtime. But we judge
|
230
|
+
# this a lesser evil than making the return type nilable and changing
|
231
|
+
# all callers to type-check successfully with that when we hope to
|
232
|
+
# revert the change soon anyway.
|
233
|
+
T.must(inferred_revision)
|
224
234
|
end
|
225
235
|
end
|
226
236
|
|
@@ -310,7 +320,12 @@ DESC
|
|
310
320
|
|
311
321
|
sig {returns(String)}
|
312
322
|
def remote
|
313
|
-
|
323
|
+
# Ensure that we replace any characters in the mirror path that might be
|
324
|
+
# problematic in a Git ref name. Theoretically, this may introduce
|
325
|
+
# collisions between mirrors, but we don't expect that to be much of a
|
326
|
+
# problem because Braid doesn't keep remotes by default after a command
|
327
|
+
# exits.
|
328
|
+
"#{branch || tag || 'revision'}_braid_#{path}".gsub(/[^-A-Za-z0-9]/, '_')
|
314
329
|
end
|
315
330
|
|
316
331
|
private
|
@@ -322,8 +337,8 @@ DESC
|
|
322
337
|
|
323
338
|
sig {returns(T.nilable(String))}
|
324
339
|
def inferred_revision
|
325
|
-
local_commits = git.rev_list('HEAD', "-- #{path}").split("\n")
|
326
|
-
remote_hashes = git.rev_list("--pretty=format
|
340
|
+
local_commits = git.rev_list(['HEAD', "-- #{path}"]).split("\n")
|
341
|
+
remote_hashes = git.rev_list(["--pretty=format:%T", remote]).split('commit ').map do |chunk|
|
327
342
|
chunk.split("\n", 2).map { |value| value.strip }
|
328
343
|
end
|
329
344
|
hash = T.let(nil, T.nilable(String))
|
data/lib/braid/operations.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
1
3
|
require 'singleton'
|
2
4
|
require 'rubygems'
|
5
|
+
require 'shellwords'
|
3
6
|
require 'tempfile'
|
4
7
|
|
5
8
|
module Braid
|
@@ -7,45 +10,64 @@ module Braid
|
|
7
10
|
|
8
11
|
module Operations
|
9
12
|
class ShellExecutionError < BraidError
|
13
|
+
# TODO (typing): Should this be nilable?
|
14
|
+
sig {returns(T.nilable(String))}
|
10
15
|
attr_reader :err, :out
|
11
16
|
|
17
|
+
sig {params(err: T.nilable(String), out: T.nilable(String)).void}
|
12
18
|
def initialize(err = nil, out = nil)
|
13
19
|
@err = err
|
14
20
|
@out = out
|
15
21
|
end
|
16
22
|
|
23
|
+
sig {returns(String)}
|
17
24
|
def message
|
18
|
-
@err.to_s.split("\n").first
|
25
|
+
first_line = @err.to_s.split("\n").first
|
26
|
+
# Currently, first_line can be nil if @err was empty, but Sorbet thinks
|
27
|
+
# that the `message` method of an Exception should always return non-nil
|
28
|
+
# (although override checking isn't enforced as of this writing), so
|
29
|
+
# handle nil here. This seems ad-hoc but better than putting in a
|
30
|
+
# `T.must` that we know has a risk of being wrong. Hopefully this will
|
31
|
+
# be fixed better in https://github.com/cristibalan/braid/issues/90.
|
32
|
+
first_line.nil? ? '' : first_line
|
19
33
|
end
|
20
34
|
end
|
21
35
|
class VersionTooLow < BraidError
|
36
|
+
sig {params(command: String, version: String, required: String).void}
|
22
37
|
def initialize(command, version, required)
|
23
38
|
@command = command
|
24
|
-
|
39
|
+
# TODO (typing): Probably should not be nilable
|
40
|
+
@version = T.let(version.to_s.split("\n").first, T.nilable(String))
|
25
41
|
@required = required
|
26
42
|
end
|
27
43
|
|
44
|
+
sig {returns(String)}
|
28
45
|
def message
|
29
46
|
"#{@command} version too low: #{@version}. #{@required} needed."
|
30
47
|
end
|
31
48
|
end
|
32
49
|
class UnknownRevision < BraidError
|
50
|
+
sig {returns(String)}
|
33
51
|
def message
|
34
52
|
"unknown revision: #{super}"
|
35
53
|
end
|
36
54
|
end
|
37
55
|
class LocalChangesPresent < BraidError
|
56
|
+
sig {returns(String)}
|
38
57
|
def message
|
39
58
|
'local changes are present'
|
40
59
|
end
|
41
60
|
end
|
42
61
|
class MergeError < BraidError
|
62
|
+
sig {returns(String)}
|
43
63
|
attr_reader :conflicts_text
|
44
64
|
|
65
|
+
sig {params(conflicts_text: String).void}
|
45
66
|
def initialize(conflicts_text)
|
46
67
|
@conflicts_text = conflicts_text
|
47
68
|
end
|
48
69
|
|
70
|
+
sig {returns(String)}
|
49
71
|
def message
|
50
72
|
'could not merge'
|
51
73
|
end
|
@@ -53,18 +75,24 @@ module Braid
|
|
53
75
|
|
54
76
|
# The command proxy is meant to encapsulate commands such as git, that work with subcommands.
|
55
77
|
class Proxy
|
78
|
+
extend T::Sig
|
56
79
|
include Singleton
|
57
80
|
|
58
|
-
|
59
|
-
|
81
|
+
# TODO (typing): We could make this method abstract if our fake Sorbet
|
82
|
+
# runtime supported abstract methods.
|
83
|
+
sig {returns(String)}
|
84
|
+
def self.command
|
85
|
+
raise InternalError, 'Proxy.command not overridden'
|
60
86
|
end
|
61
87
|
|
62
88
|
# hax!
|
89
|
+
sig {returns(String)}
|
63
90
|
def version
|
64
|
-
_, out, _ = exec!(
|
91
|
+
_, out, _ = exec!([self.class.command, '--version'])
|
65
92
|
out.sub(/^.* version/, '').strip.sub(/ .*$/, '').strip
|
66
93
|
end
|
67
94
|
|
95
|
+
sig {params(required: String).returns(T::Boolean)}
|
68
96
|
def require_version(required)
|
69
97
|
# Gem::Version is intended for Ruby gem versions, but various web sites
|
70
98
|
# suggest it as a convenient way of comparing version strings in
|
@@ -73,77 +101,115 @@ module Braid
|
|
73
101
|
Gem::Version.new(version) >= Gem::Version.new(required)
|
74
102
|
end
|
75
103
|
|
104
|
+
sig {params(required: String).void}
|
76
105
|
def require_version!(required)
|
77
106
|
require_version(required) || raise(VersionTooLow.new(self.class.command, version, required))
|
78
107
|
end
|
79
108
|
|
80
109
|
private
|
81
110
|
|
111
|
+
sig {params(name: String).returns(T::Array[String])}
|
82
112
|
def command(name)
|
83
113
|
# stub
|
84
|
-
name
|
114
|
+
[name]
|
85
115
|
end
|
86
116
|
|
87
|
-
|
88
|
-
|
117
|
+
sig {params(arg: String, args: T::Array[String]).returns(String)}
|
118
|
+
def invoke(arg, args)
|
119
|
+
exec!(command(arg) + args)[1].strip # return stdout
|
89
120
|
end
|
90
121
|
|
91
|
-
|
92
|
-
|
93
|
-
|
122
|
+
# Some of the unit tests want to mock out `exec`, but they have no way to
|
123
|
+
# construct a real Process::Status and thus use an integer instead. We
|
124
|
+
# have to accommodate this in the type annotation to avoid runtime type
|
125
|
+
# check failures during the tests. In normal use of Braid, this will
|
126
|
+
# always be a real Process::Status. Fortunately, allowing Integer doesn't
|
127
|
+
# seem to cause any other problems right now.
|
128
|
+
ProcessStatusOrInteger = T.type_alias { T.any(Process::Status, Integer) }
|
94
129
|
|
130
|
+
sig {params(cmd: T::Array[String]).returns([ProcessStatusOrInteger, String, String])}
|
95
131
|
def exec(cmd)
|
96
|
-
cmd.strip!
|
97
|
-
|
98
132
|
Operations::with_modified_environment({'LANG' => 'C'}) do
|
99
133
|
log(cmd)
|
100
|
-
|
134
|
+
# The special `[cmd[0], cmd[0]]` syntax ensures that `cmd[0]` is
|
135
|
+
# interpreted as the path of the executable and not a shell command
|
136
|
+
# even if `cmd` has only one element. See the documentation:
|
137
|
+
# https://ruby-doc.org/core-3.1.2/Process.html#method-c-spawn.
|
138
|
+
# Granted, this shouldn't matter for Braid for two reasons: (1)
|
139
|
+
# `cmd[0]` is always "git", which doesn't contain any shell special
|
140
|
+
# characters, and (2) `cmd` always has at least one additional
|
141
|
+
# argument (the Git subcommand). However, it's still nice to make our
|
142
|
+
# intent clear.
|
143
|
+
out, err, status = T.unsafe(Open3).capture3([cmd[0], cmd[0]], *cmd[1..])
|
101
144
|
[status, out, err]
|
102
145
|
end
|
103
146
|
end
|
104
147
|
|
148
|
+
sig {params(cmd: T::Array[String]).returns([ProcessStatusOrInteger, String, String])}
|
105
149
|
def exec!(cmd)
|
106
150
|
status, out, err = exec(cmd)
|
107
151
|
raise ShellExecutionError.new(err, out) unless status == 0
|
108
152
|
[status, out, err]
|
109
153
|
end
|
110
154
|
|
155
|
+
sig {params(cmd: T::Array[String]).returns(ProcessStatusOrInteger)}
|
111
156
|
def system(cmd)
|
112
|
-
cmd.strip!
|
113
|
-
|
114
157
|
# Without this, "braid diff" output came out in the wrong order on Windows.
|
115
158
|
$stdout.flush
|
116
159
|
$stderr.flush
|
117
160
|
Operations::with_modified_environment({'LANG' => 'C'}) do
|
118
|
-
|
161
|
+
# See the comment in `exec` about the `[cmd[0], cmd[0]]` syntax.
|
162
|
+
T.unsafe(Kernel).system([cmd[0], cmd[0]], *cmd[1..])
|
119
163
|
return $?
|
120
164
|
end
|
121
165
|
end
|
122
166
|
|
167
|
+
sig {params(str: String).void}
|
123
168
|
def msg(str)
|
124
169
|
puts "Braid: #{str}"
|
125
170
|
end
|
126
171
|
|
172
|
+
sig {params(cmd: T::Array[String]).void}
|
127
173
|
def log(cmd)
|
128
|
-
|
174
|
+
# Note: `Shellwords.shelljoin` follows Bourne shell quoting rules, as
|
175
|
+
# its documentation states. This may not be what a Windows user
|
176
|
+
# expects, but it's not worth the trouble to try to find a library that
|
177
|
+
# produces something better on Windows, especially because it's unclear
|
178
|
+
# which of Windows's several different quoted formats we would use
|
179
|
+
# (e.g., CommandLineToArgvW, cmd.exe, or PowerShell). The most
|
180
|
+
# important thing is to use _some_ unambiguous representation.
|
181
|
+
msg "Executing `#{Shellwords.shelljoin(cmd)}` in #{Dir.pwd}" if verbose?
|
129
182
|
end
|
130
183
|
|
184
|
+
sig {returns(T::Boolean)}
|
131
185
|
def verbose?
|
132
186
|
Braid.verbose
|
133
187
|
end
|
134
188
|
end
|
135
189
|
|
136
190
|
class Git < Proxy
|
191
|
+
|
192
|
+
sig {returns(String)}
|
193
|
+
def self.command
|
194
|
+
'git'
|
195
|
+
end
|
196
|
+
|
197
|
+
# A string representing a Git object ID (i.e., hash). This type alias is
|
198
|
+
# used as documentation and is not enforced, so there's a risk that we
|
199
|
+
# mistakenly mark something as an ObjectID when it can actually be a
|
200
|
+
# String that is not an ObjectID.
|
201
|
+
ObjectID = T.type_alias { String }
|
202
|
+
|
203
|
+
# A string containing an expression that can be evaluated to an object ID
|
204
|
+
# by `git rev-parse`. Ditto the remark about lack of enforcement.
|
205
|
+
ObjectExpr = T.type_alias { String }
|
206
|
+
|
137
207
|
# Get the physical path to a file in the git repository (e.g.,
|
138
208
|
# 'MERGE_MSG'), taking into account worktree configuration. The returned
|
139
209
|
# path may be absolute or relative to the current working directory.
|
210
|
+
sig {params(path: String).returns(String)}
|
140
211
|
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
|
212
|
+
invoke('rev-parse', ['--git-path', path])
|
147
213
|
end
|
148
214
|
|
149
215
|
# If the current directory is not inside a git repository at all, this
|
@@ -151,8 +217,9 @@ module Braid
|
|
151
217
|
# propagated as a ShellExecutionError. is_inside_worktree can return
|
152
218
|
# false when inside a bare repository and in certain other rare cases such
|
153
219
|
# as when the GIT_WORK_TREE environment variable is set.
|
220
|
+
sig {returns(T::Boolean)}
|
154
221
|
def is_inside_worktree
|
155
|
-
invoke(
|
222
|
+
invoke('rev-parse', ['--is-inside-work-tree']) == 'true'
|
156
223
|
end
|
157
224
|
|
158
225
|
# Get the prefix of the current directory relative to the worktree. Empty
|
@@ -160,21 +227,23 @@ module Braid
|
|
160
227
|
# In some cases in which the current directory is not inside a worktree at
|
161
228
|
# all, this will successfully return an empty string, so it may be
|
162
229
|
# desirable to check is_inside_worktree first.
|
230
|
+
sig {returns(String)}
|
163
231
|
def relative_working_dir
|
164
|
-
invoke(
|
232
|
+
invoke('rev-parse', ['--show-prefix'])
|
165
233
|
end
|
166
234
|
|
167
|
-
|
168
|
-
|
235
|
+
sig {params(message: T.nilable(String), args: T::Array[String]).returns(T::Boolean)}
|
236
|
+
def commit(message, args = [])
|
237
|
+
cmd = ['git', 'commit', '--no-verify']
|
169
238
|
message_file = nil
|
170
239
|
if message # allow nil
|
171
240
|
message_file = Tempfile.new('braid_commit')
|
172
241
|
message_file.print("Braid: #{message}")
|
173
242
|
message_file.flush
|
174
243
|
message_file.close
|
175
|
-
cmd
|
244
|
+
cmd += ['-F', T.must(message_file.path)]
|
176
245
|
end
|
177
|
-
cmd
|
246
|
+
cmd += args
|
178
247
|
status, out, err = exec(cmd)
|
179
248
|
message_file.unlink if message_file
|
180
249
|
|
@@ -187,50 +256,55 @@ module Braid
|
|
187
256
|
end
|
188
257
|
end
|
189
258
|
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
def checkout(treeish)
|
196
|
-
invoke(:checkout, treeish)
|
197
|
-
true
|
259
|
+
sig {params(remote: T.nilable(String), args: T::Array[String]).void}
|
260
|
+
def fetch(remote = nil, args = [])
|
261
|
+
args = ['-n', remote] + args if remote
|
262
|
+
exec!(['git', 'fetch'] + args)
|
198
263
|
end
|
199
264
|
|
200
265
|
# Returns the base commit or nil.
|
266
|
+
sig {params(target: ObjectExpr, source: ObjectExpr).returns(T.nilable(ObjectID))}
|
201
267
|
def merge_base(target, source)
|
202
|
-
invoke(
|
268
|
+
invoke('merge-base', [target, source])
|
203
269
|
rescue ShellExecutionError
|
204
270
|
nil
|
205
271
|
end
|
206
272
|
|
207
|
-
|
208
|
-
|
273
|
+
sig {params(expr: ObjectExpr).returns(ObjectID)}
|
274
|
+
def rev_parse(expr)
|
275
|
+
invoke('rev-parse', [expr])
|
209
276
|
rescue ShellExecutionError
|
210
|
-
raise UnknownRevision,
|
277
|
+
raise UnknownRevision, expr
|
211
278
|
end
|
212
279
|
|
213
280
|
# Implies tracking.
|
281
|
+
#
|
282
|
+
# TODO (typing): Remove the return value if we're confident that nothing
|
283
|
+
# uses it, here and in similar cases.
|
284
|
+
sig {params(remote: String, path: String).returns(TrueClass)}
|
214
285
|
def remote_add(remote, path)
|
215
|
-
invoke(
|
286
|
+
invoke('remote', ['add', remote, path])
|
216
287
|
true
|
217
288
|
end
|
218
289
|
|
290
|
+
sig {params(remote: String).returns(TrueClass)}
|
219
291
|
def remote_rm(remote)
|
220
|
-
invoke(
|
292
|
+
invoke('remote', ['rm', remote])
|
221
293
|
true
|
222
294
|
end
|
223
295
|
|
224
296
|
# Checks git remotes.
|
297
|
+
sig {params(remote: String).returns(T.nilable(String))}
|
225
298
|
def remote_url(remote)
|
226
299
|
key = "remote.#{remote}.url"
|
227
|
-
invoke(
|
300
|
+
invoke('config', [key])
|
228
301
|
rescue ShellExecutionError
|
229
302
|
nil
|
230
303
|
end
|
231
304
|
|
305
|
+
sig {params(target: ObjectExpr).returns(TrueClass)}
|
232
306
|
def reset_hard(target)
|
233
|
-
invoke(
|
307
|
+
invoke('reset', ['--hard', target])
|
234
308
|
true
|
235
309
|
end
|
236
310
|
|
@@ -242,41 +316,59 @@ module Braid
|
|
242
316
|
# 'recursive' part (i.e., merge of bases) does not come into play and only
|
243
317
|
# the trees matter. But for some reason, Git's smartest tree merge
|
244
318
|
# algorithm is only available via the 'recursive' strategy.
|
319
|
+
sig {params(base_treeish: ObjectExpr, local_treeish: ObjectExpr, remote_treeish: ObjectExpr).returns(TrueClass)}
|
245
320
|
def merge_trees(base_treeish, local_treeish, remote_treeish)
|
246
|
-
invoke(
|
321
|
+
invoke('merge-recursive', [base_treeish, '--', local_treeish, remote_treeish])
|
247
322
|
true
|
248
323
|
rescue ShellExecutionError => error
|
249
324
|
# 'CONFLICT' messages go to stdout.
|
250
325
|
raise MergeError, error.out
|
251
326
|
end
|
252
327
|
|
328
|
+
sig {params(prefix: String).returns(String)}
|
253
329
|
def read_ls_files(prefix)
|
254
|
-
invoke('ls-files', prefix)
|
330
|
+
invoke('ls-files', [prefix])
|
255
331
|
end
|
256
332
|
|
257
333
|
class BlobWithMode
|
334
|
+
extend T::Sig
|
335
|
+
sig {params(hash: ObjectID, mode: String).void}
|
258
336
|
def initialize(hash, mode)
|
259
337
|
@hash = hash
|
260
338
|
@mode = mode
|
261
339
|
end
|
262
|
-
|
340
|
+
sig {returns(ObjectID)}
|
341
|
+
attr_reader :hash
|
342
|
+
sig {returns(String)}
|
343
|
+
attr_reader :mode
|
263
344
|
end
|
264
345
|
# Allow the class to be referenced as `git.BlobWithMode`.
|
346
|
+
sig {returns(T.class_of(BlobWithMode))}
|
265
347
|
def BlobWithMode
|
266
348
|
Git::BlobWithMode
|
267
349
|
end
|
350
|
+
# An ObjectID used as a TreeItem represents a tree.
|
351
|
+
TreeItem = T.type_alias { T.any(ObjectID, BlobWithMode) }
|
268
352
|
|
269
353
|
# Get the item at the given path in the given tree. If it's a tree, just
|
270
354
|
# return its hash; if it's a blob, return a BlobWithMode object. (This is
|
271
355
|
# how we remember the mode for single-file mirrors.)
|
356
|
+
# TODO (typing): Should `path` be nilable?
|
357
|
+
sig {params(tree: ObjectExpr, path: T.nilable(String)).returns(TreeItem)}
|
272
358
|
def get_tree_item(tree, path)
|
273
359
|
if path.nil? || path == ''
|
274
360
|
tree
|
275
361
|
else
|
276
|
-
m = /^([^ ]*) ([^ ]*) ([^\t]*)\t.*$/.match(invoke(
|
277
|
-
|
278
|
-
|
279
|
-
|
362
|
+
m = /^([^ ]*) ([^ ]*) ([^\t]*)\t.*$/.match(invoke('ls-tree', [tree, path]))
|
363
|
+
if m.nil?
|
364
|
+
# This can happen if the user runs `braid add` with a `--path` that
|
365
|
+
# doesn't exist. TODO: Make the error message more user-friendly in
|
366
|
+
# that case.
|
367
|
+
raise ShellExecutionError, 'No tree item exists at the given path'
|
368
|
+
end
|
369
|
+
mode = T.must(m[1])
|
370
|
+
type = T.must(m[2])
|
371
|
+
hash = T.must(m[3])
|
280
372
|
if type == 'tree'
|
281
373
|
hash
|
282
374
|
elsif type == 'blob'
|
@@ -291,37 +383,47 @@ module Braid
|
|
291
383
|
# path. If update_worktree is true, then update the worktree, otherwise
|
292
384
|
# disregard the state of the worktree (most useful with a temporary index
|
293
385
|
# file).
|
386
|
+
sig {params(item: TreeItem, path: String, update_worktree: T::Boolean).void}
|
294
387
|
def add_item_to_index(item, path, update_worktree)
|
295
388
|
if item.is_a?(BlobWithMode)
|
296
|
-
|
297
|
-
# wasn't added until 2.0.0.
|
298
|
-
invoke(:update_index, '--add', '--cacheinfo', item.mode, item.hash, path)
|
389
|
+
invoke('update-index', ['--add', '--cacheinfo', "#{item.mode},#{item.hash},#{path}"])
|
299
390
|
if update_worktree
|
300
391
|
# XXX If this fails, we've already updated the index.
|
301
|
-
invoke(
|
392
|
+
invoke('checkout-index', [path])
|
302
393
|
end
|
303
394
|
else
|
304
395
|
# According to
|
305
396
|
# https://lore.kernel.org/git/e48a281a4d3db0a04c0609fcb8658e4fcc797210.1646166271.git.gitgitgadget@gmail.com/,
|
306
397
|
# `--prefix=` is valid if the path is empty.
|
307
|
-
invoke(
|
398
|
+
invoke('read-tree', ["--prefix=#{path}", update_worktree ? '-u' : '-i', item])
|
308
399
|
end
|
309
400
|
end
|
310
401
|
|
311
402
|
# Read tree into the root of the index. This may not be the preferred way
|
312
403
|
# to do it, but it seems to work.
|
404
|
+
sig {params(treeish: ObjectExpr).void}
|
313
405
|
def read_tree_im(treeish)
|
314
|
-
invoke(
|
315
|
-
|
406
|
+
invoke('read-tree', ['-im', treeish])
|
407
|
+
end
|
408
|
+
|
409
|
+
sig {params(treeish: ObjectExpr).void}
|
410
|
+
def read_tree_um(treeish)
|
411
|
+
invoke('read-tree', ['-um', treeish])
|
316
412
|
end
|
317
413
|
|
318
414
|
# Write a tree object for the current index and return its ID.
|
415
|
+
sig {returns(ObjectID)}
|
319
416
|
def write_tree
|
320
|
-
invoke(
|
417
|
+
invoke('write-tree', [])
|
321
418
|
end
|
322
419
|
|
323
420
|
# Execute a block using a temporary git index file, initially empty.
|
324
|
-
|
421
|
+
sig {
|
422
|
+
type_parameters(:R).params(
|
423
|
+
blk: T.proc.returns(T.type_parameter(:R))
|
424
|
+
).returns(T.type_parameter(:R))
|
425
|
+
}
|
426
|
+
def with_temporary_index(&blk)
|
325
427
|
Dir.mktmpdir('braid_index') do |dir|
|
326
428
|
Operations::with_modified_environment(
|
327
429
|
{'GIT_INDEX_FILE' => File.join(dir, 'index')}) do
|
@@ -330,6 +432,7 @@ module Braid
|
|
330
432
|
end
|
331
433
|
end
|
332
434
|
|
435
|
+
sig {params(main_content: T.nilable(ObjectExpr), item_path: String, item: TreeItem).returns(ObjectID)}
|
333
436
|
def make_tree_with_item(main_content, item_path, item)
|
334
437
|
with_temporary_index do
|
335
438
|
# If item_path is '', then rm_r_cached will fail. But in that case,
|
@@ -344,103 +447,161 @@ module Braid
|
|
344
447
|
end
|
345
448
|
end
|
346
449
|
|
450
|
+
sig {params(args: T::Array[String]).returns(T.nilable(String))}
|
347
451
|
def config(args)
|
348
|
-
invoke(
|
452
|
+
invoke('config', args) rescue nil
|
349
453
|
end
|
350
454
|
|
455
|
+
sig {params(path: String).void}
|
456
|
+
def add(path)
|
457
|
+
invoke('add', [path])
|
458
|
+
end
|
459
|
+
|
460
|
+
sig {params(path: String).void}
|
461
|
+
def rm(path)
|
462
|
+
invoke('rm', [path])
|
463
|
+
end
|
464
|
+
|
465
|
+
sig {params(path: String).returns(TrueClass)}
|
351
466
|
def rm_r(path)
|
352
|
-
invoke(
|
467
|
+
invoke('rm', ['-r', path])
|
353
468
|
true
|
354
469
|
end
|
355
470
|
|
356
471
|
# Remove from index only.
|
472
|
+
sig {params(path: String).returns(TrueClass)}
|
357
473
|
def rm_r_cached(path)
|
358
|
-
invoke(
|
474
|
+
invoke('rm', ['-r', '--cached', path])
|
359
475
|
true
|
360
476
|
end
|
361
477
|
|
478
|
+
sig {params(path: String, treeish: ObjectExpr).returns(ObjectID)}
|
362
479
|
def tree_hash(path, treeish = 'HEAD')
|
363
|
-
out = invoke(
|
364
|
-
out.split[2]
|
480
|
+
out = invoke('ls-tree', [treeish, '-d', path])
|
481
|
+
T.must(out.split[2])
|
482
|
+
end
|
483
|
+
|
484
|
+
sig {params(args: T::Array[String]).returns(String)}
|
485
|
+
def diff(args)
|
486
|
+
invoke('diff', args)
|
365
487
|
end
|
366
488
|
|
367
|
-
|
489
|
+
sig {params(args: T::Array[String]).returns(ProcessStatusOrInteger)}
|
490
|
+
def diff_to_stdout(args)
|
368
491
|
# For now, ignore the exit code. It can be 141 (SIGPIPE) if the user
|
369
492
|
# quits the pager before reading all the output.
|
370
|
-
system(
|
493
|
+
system(['git', 'diff'] + args)
|
371
494
|
end
|
372
495
|
|
496
|
+
sig {returns(T::Boolean)}
|
373
497
|
def status_clean?
|
374
|
-
_, out, _ = exec('git status')
|
498
|
+
_, out, _ = exec(['git', 'status'])
|
375
499
|
!out.split("\n").grep(/nothing to commit/).empty?
|
376
500
|
end
|
377
501
|
|
502
|
+
sig {void}
|
378
503
|
def ensure_clean!
|
379
504
|
status_clean? || raise(LocalChangesPresent)
|
380
505
|
end
|
381
506
|
|
507
|
+
sig {returns(ObjectID)}
|
382
508
|
def head
|
383
509
|
rev_parse('HEAD')
|
384
510
|
end
|
385
511
|
|
386
|
-
|
387
|
-
|
388
|
-
|
512
|
+
sig {void}
|
513
|
+
def init
|
514
|
+
invoke('init', [])
|
515
|
+
end
|
516
|
+
|
517
|
+
sig {params(args: T::Array[String]).void}
|
518
|
+
def clone(args)
|
519
|
+
invoke('clone', args)
|
520
|
+
end
|
521
|
+
|
522
|
+
# Wrappers for Git commands that were called via `method_missing` before
|
523
|
+
# the move to static typing but for which the existing calls don't follow
|
524
|
+
# a clear enough pattern around which we could design a narrower API than
|
525
|
+
# forwarding an arbitrary argument list. We may narrow the API in the
|
526
|
+
# future if it becomes clear what it should be.
|
527
|
+
|
528
|
+
sig {params(args: T::Array[String]).returns(String)}
|
529
|
+
def rev_list(args)
|
530
|
+
invoke('rev-list', args)
|
531
|
+
end
|
532
|
+
|
533
|
+
sig {params(args: T::Array[String]).void}
|
534
|
+
def update_ref(args)
|
535
|
+
invoke('update-ref', args)
|
536
|
+
end
|
537
|
+
|
538
|
+
sig {params(args: T::Array[String]).void}
|
539
|
+
def push(args)
|
540
|
+
invoke('push', args)
|
389
541
|
end
|
390
542
|
|
391
|
-
|
392
|
-
|
393
|
-
invoke(
|
543
|
+
sig {params(args: T::Array[String]).returns(String)}
|
544
|
+
def ls_remote(args)
|
545
|
+
invoke('ls-remote', args)
|
394
546
|
end
|
395
547
|
|
396
548
|
private
|
397
549
|
|
550
|
+
sig {params(name: String).returns(T::Array[String])}
|
398
551
|
def command(name)
|
399
|
-
|
552
|
+
[self.class.command, name]
|
400
553
|
end
|
401
554
|
end
|
402
555
|
|
403
556
|
class GitCache
|
557
|
+
extend T::Sig
|
404
558
|
include Singleton
|
405
559
|
|
560
|
+
sig {params(url: String).void}
|
406
561
|
def fetch(url)
|
407
562
|
dir = path(url)
|
408
563
|
|
409
564
|
# remove local cache if it was created with --no-checkout
|
410
|
-
if File.
|
565
|
+
if File.exist?("#{dir}/.git")
|
411
566
|
FileUtils.rm_r(dir)
|
412
567
|
end
|
413
568
|
|
414
|
-
if File.
|
569
|
+
if File.exist?(dir)
|
415
570
|
Dir.chdir(dir) do
|
416
571
|
git.fetch
|
417
572
|
end
|
418
573
|
else
|
419
574
|
FileUtils.mkdir_p(local_cache_dir)
|
420
|
-
git.clone('--mirror', url, dir)
|
575
|
+
git.clone(['--mirror', url, dir])
|
421
576
|
end
|
422
577
|
end
|
423
578
|
|
579
|
+
sig {params(url: String).returns(String)}
|
424
580
|
def path(url)
|
425
581
|
File.join(local_cache_dir, url.gsub(/[\/:@]/, '_'))
|
426
582
|
end
|
427
583
|
|
428
584
|
private
|
429
585
|
|
586
|
+
sig {returns(String)}
|
430
587
|
def local_cache_dir
|
431
588
|
Braid.local_cache_dir
|
432
589
|
end
|
433
590
|
|
591
|
+
sig {returns(Git)}
|
434
592
|
def git
|
435
593
|
Git.instance
|
436
594
|
end
|
437
595
|
end
|
438
596
|
|
439
597
|
module VersionControl
|
598
|
+
extend T::Sig
|
599
|
+
sig {returns(Git)}
|
440
600
|
def git
|
441
601
|
Git.instance
|
442
602
|
end
|
443
603
|
|
604
|
+
sig {returns(GitCache)}
|
444
605
|
def git_cache
|
445
606
|
GitCache.instance
|
446
607
|
end
|
@@ -36,6 +36,10 @@ module Braid
|
|
36
36
|
def self.must(value)
|
37
37
|
value
|
38
38
|
end
|
39
|
+
def self.unsafe(value)
|
40
|
+
value
|
41
|
+
end
|
42
|
+
def self.bind(value, type); end
|
39
43
|
|
40
44
|
class FakeType
|
41
45
|
include Singleton
|
@@ -52,6 +56,12 @@ module Braid
|
|
52
56
|
def self.untyped
|
53
57
|
FAKE_TYPE
|
54
58
|
end
|
59
|
+
def self.noreturn
|
60
|
+
FAKE_TYPE
|
61
|
+
end
|
62
|
+
def self.any(*types)
|
63
|
+
FAKE_TYPE
|
64
|
+
end
|
55
65
|
Boolean = FAKE_TYPE
|
56
66
|
module Array
|
57
67
|
def self.[](type)
|
data/lib/braid/version.rb
CHANGED
data/lib/braid.rb
CHANGED
@@ -12,14 +12,13 @@ module Braid
|
|
12
12
|
# See the background in the "Supported environments" section of README.md.
|
13
13
|
#
|
14
14
|
# The newest Git feature that Braid is currently known to rely on is
|
15
|
-
# `
|
16
|
-
# 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
|
17
16
|
# doesn't seem worth even a small amount of work to remove that dependency and
|
18
17
|
# support even older versions of Git. So set that as the declared requirement
|
19
18
|
# for now. In general, a reasonable approach might be to try to support the
|
20
19
|
# oldest version of Git in current "long-term support" versions of popular OS
|
21
20
|
# distributions.
|
22
|
-
REQUIRED_GIT_VERSION = '2.
|
21
|
+
REQUIRED_GIT_VERSION = '2.8.0'
|
23
22
|
|
24
23
|
@verbose = T.let(false, T::Boolean)
|
25
24
|
|
metadata
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: braid
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1.
|
4
|
+
version: 1.1.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Cristi Balan
|
8
8
|
- Norbert Crombach
|
9
9
|
- Peter Donald
|
10
10
|
- Matt McCutchen
|
11
|
-
autorequire:
|
11
|
+
autorequire:
|
12
12
|
bindir: exe
|
13
13
|
cert_chain: []
|
14
|
-
date:
|
14
|
+
date: 2023-01-11 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: main
|
@@ -97,6 +97,20 @@ dependencies:
|
|
97
97
|
- - ">="
|
98
98
|
- !ruby/object:Gem::Version
|
99
99
|
version: '0'
|
100
|
+
- !ruby/object:Gem::Dependency
|
101
|
+
name: irb
|
102
|
+
requirement: !ruby/object:Gem::Requirement
|
103
|
+
requirements:
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '0'
|
107
|
+
type: :development
|
108
|
+
prerelease: false
|
109
|
+
version_requirements: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - ">="
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '0'
|
100
114
|
description: A simple tool for tracking vendor branches in git.
|
101
115
|
email: evil@che.lu norbert.crombach@primetheory.org peter@realityforge.org matt@mattmccutchen.net
|
102
116
|
executables:
|
@@ -128,7 +142,7 @@ files:
|
|
128
142
|
homepage: https://github.com/cristibalan/braid
|
129
143
|
licenses: []
|
130
144
|
metadata: {}
|
131
|
-
post_install_message:
|
145
|
+
post_install_message:
|
132
146
|
rdoc_options:
|
133
147
|
- "--line-numbers"
|
134
148
|
- "--inline-source"
|
@@ -148,8 +162,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
148
162
|
- !ruby/object:Gem::Version
|
149
163
|
version: '0'
|
150
164
|
requirements: []
|
151
|
-
rubygems_version: 3.
|
152
|
-
signing_key:
|
165
|
+
rubygems_version: 3.1.4
|
166
|
+
signing_key:
|
153
167
|
specification_version: 4
|
154
168
|
summary: A simple tool for tracking vendor branches in git.
|
155
169
|
test_files: []
|