braid 1.1.7 → 1.1.9
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|