multi_repo 0.4.0 → 0.5.1
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/.github/workflows/ci.yaml +3 -1
- data/CHANGELOG.md +25 -1
- data/lib/multi_repo/service/git.rb +11 -0
- data/lib/multi_repo/service/github.rb +71 -0
- data/lib/multi_repo/version.rb +1 -1
- data/multi_repo.gemspec +1 -0
- data/renovate.json +2 -3
- data/scripts/destroy_remote +1 -5
- data/scripts/pull_request_labeler +17 -38
- data/scripts/pull_request_merger +16 -38
- data/scripts/show_commit_history +6 -1
- metadata +17 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bfc3d56275cbe25e40cecabf411913805b1a83f59395ac0bf721b52130bc1795
|
|
4
|
+
data.tar.gz: b10b82500c86b84030488e687e86bfed2283b83de8911092211dae510c8eb22b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 551aad598181333a741eeb5ab881642f8e455645acb9eea1870494c3f5ce83d5d7a2e7fcbfaf1fa797e652ac8a4c3513907b211db4c5d37560e229bb62c40064
|
|
7
|
+
data.tar.gz: 352099fa70f24b0bd5ecd5493fc7d62dd9ec3c2eb6cdad3b7dde9bef8803b69d1532e303dfe8e37b27a007f9b298a61b515437413e492b21e70eb0bf3eb25c83
|
data/.github/workflows/ci.yaml
CHANGED
|
@@ -14,6 +14,8 @@ jobs:
|
|
|
14
14
|
ruby-version:
|
|
15
15
|
- '3.0'
|
|
16
16
|
- '3.1'
|
|
17
|
+
- '3.2'
|
|
18
|
+
- '3.3'
|
|
17
19
|
env:
|
|
18
20
|
CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
|
|
19
21
|
steps:
|
|
@@ -29,4 +31,4 @@ jobs:
|
|
|
29
31
|
- name: Report code coverage
|
|
30
32
|
if: ${{ github.ref == 'refs/heads/master' && matrix.ruby-version == '3.1' }}
|
|
31
33
|
continue-on-error: true
|
|
32
|
-
uses: paambaati/codeclimate-action@
|
|
34
|
+
uses: paambaati/codeclimate-action@v9
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,28 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.5.1] - 2025-02-20
|
|
8
|
+
### Added
|
|
9
|
+
- Add debugging of octokit request/response if DEBUG env var set [[#38](https://github.com/ManageIQ/multi_repo/pull/38)]
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- Pin json gem to 2.9.1 since 2.10.0+ is broken [[#41](https://github.com/ManageIQ/multi_repo/pull/41)]
|
|
13
|
+
|
|
14
|
+
## [0.5.0] - 2024-11-12
|
|
15
|
+
### Added
|
|
16
|
+
- [pull_request_labeler] Add ability to also add a comment about why the labels are changing [[#30](https://github.com/ManageIQ/multi_repo/pull/30)]
|
|
17
|
+
- [pull_request_labeler] Add normalization of PR formats to org/repo#pr format [[#30](https://github.com/ManageIQ/multi_repo/pull/30)]
|
|
18
|
+
- [pull_request_merger] Add URL support to pull_request_merger [[#37](https://github.com/ManageIQ/multi_repo/pull/37)]
|
|
19
|
+
- [Git Service, GitHub service] Move helper methods into services [[#31](https://github.com/ManageIQ/multi_repo/pull/31)]
|
|
20
|
+
- Add testing with ruby 3.2, 3.3 [[#35](https://github.com/ManageIQ/multi_repo/pull/35)]
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
- [pull_request_labeler] Make add and remove optional [[#30](https://github.com/ManageIQ/multi_repo/pull/30)]
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
- [pull_request_labeler] Fix cli description of --prs [[#30](https://github.com/ManageIQ/multi_repo/pull/30)]
|
|
27
|
+
- [show_commit_history] Handle issue where PR may not be found [[#36](https://github.com/ManageIQ/multi_repo/pull/30)]
|
|
28
|
+
|
|
7
29
|
## [0.4.0] - 2024-03-29
|
|
8
30
|
### Changed
|
|
9
31
|
- Allow overriding the path for a repo [[#28](https://github.com/ManageIQ/multi_repo/pull/28)]
|
|
@@ -18,6 +40,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|
|
18
40
|
- [show_commit_history] Prevent missing ranges from failing the entire run [[#20](https://github.com/ManageIQ/multi_repo/pull/20)]
|
|
19
41
|
- [pull_request_merger] Fixing issue passing kwargs on Ruby 3 [[#23](https://github.com/ManageIQ/multi_repo/pull/23)]
|
|
20
42
|
|
|
21
|
-
[Unreleased]: https://github.com/ManageIQ/more_core_extensions/compare/v0.
|
|
43
|
+
[Unreleased]: https://github.com/ManageIQ/more_core_extensions/compare/v0.5.1...HEAD
|
|
44
|
+
[0.5.1]: https://github.com/ManageIQ/more_core_extensions/compare/v0.5.0...v0.5.1
|
|
45
|
+
[0.5.0]: https://github.com/ManageIQ/more_core_extensions/compare/v0.4.0...v0.5.0
|
|
22
46
|
[0.4.0]: https://github.com/ManageIQ/more_core_extensions/compare/v0.3.1...v0.4.0
|
|
23
47
|
[0.3.1]: https://github.com/ManageIQ/more_core_extensions/compare/v0.3.0...v0.3.1
|
|
@@ -83,6 +83,17 @@ module MultiRepo::Service
|
|
|
83
83
|
true
|
|
84
84
|
end
|
|
85
85
|
|
|
86
|
+
def destroy_remote(remote)
|
|
87
|
+
if dry_run
|
|
88
|
+
puts "** dry-run: git remote rm #{remote}".light_black
|
|
89
|
+
else
|
|
90
|
+
client.remote("rm", remote)
|
|
91
|
+
end
|
|
92
|
+
rescue MiniGit::GitError
|
|
93
|
+
# Ignore missing remotes because we want them destroyed anyway
|
|
94
|
+
nil
|
|
95
|
+
end
|
|
96
|
+
|
|
86
97
|
def remote_branch?(remote, branch)
|
|
87
98
|
client.capturing.ls_remote(remote, branch).present?
|
|
88
99
|
end
|
|
@@ -29,6 +29,13 @@ module MultiRepo::Service
|
|
|
29
29
|
}.compact
|
|
30
30
|
|
|
31
31
|
require 'octokit'
|
|
32
|
+
|
|
33
|
+
if ENV["DEBUG"]
|
|
34
|
+
middleware = Octokit.middleware.dup
|
|
35
|
+
middleware.response :logger
|
|
36
|
+
Octokit.middleware = middleware
|
|
37
|
+
end
|
|
38
|
+
|
|
32
39
|
Octokit::Client.new(params)
|
|
33
40
|
end
|
|
34
41
|
end
|
|
@@ -83,6 +90,24 @@ module MultiRepo::Service
|
|
|
83
90
|
client.workflows(repo_name)[:workflows].select { |w| w.state == "disabled_inactivity" }
|
|
84
91
|
end
|
|
85
92
|
|
|
93
|
+
PR_REGEX = %r{^([^/#]+/[^/#]+)#(\d+)$}
|
|
94
|
+
|
|
95
|
+
# Parse a list of PRs that are in URL or org/repo#pr format into a Array of
|
|
96
|
+
# [repo_name, pr_number] entries.
|
|
97
|
+
def self.parse_prs(*prs)
|
|
98
|
+
prs.flatten.map do |pr|
|
|
99
|
+
# Normalize to org/repo#pr
|
|
100
|
+
normalized_pr = pr.sub("https://github.com/", "").sub("/pull/", "#")
|
|
101
|
+
|
|
102
|
+
if (match = PR_REGEX.match(normalized_pr))
|
|
103
|
+
repo_name, pr_number = match.captures
|
|
104
|
+
[repo_name, pr_number.to_i]
|
|
105
|
+
else
|
|
106
|
+
raise ArgumentError, "Invalid PR '#{pr}'. PR must be a GitHub URL or in org/repo#pr format."
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
86
111
|
attr_reader :dry_run
|
|
87
112
|
|
|
88
113
|
def initialize(dry_run: false)
|
|
@@ -138,6 +163,44 @@ module MultiRepo::Service
|
|
|
138
163
|
end
|
|
139
164
|
end
|
|
140
165
|
|
|
166
|
+
def add_labels_to_an_issue(repo_name, issue_number, labels)
|
|
167
|
+
labels = Array(labels)
|
|
168
|
+
if dry_run
|
|
169
|
+
puts "** dry-run: github.add_labels_to_an_issue(#{repo_name.inspect}, #{issue_number.inspect}, #{labels.inspect})".light_black
|
|
170
|
+
else
|
|
171
|
+
client.add_labels_to_an_issue(repo_name, issue_number, labels)
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def remove_labels_from_an_issue(repo_name, issue_number, labels)
|
|
176
|
+
Array(labels).each do |label|
|
|
177
|
+
if dry_run
|
|
178
|
+
puts "** dry-run: github.remove_label(#{repo_name.inspect}, #{issue_number.inspect}, #{label.inspect})".light_black
|
|
179
|
+
else
|
|
180
|
+
client.remove_label(repo_name, issue_number, label)
|
|
181
|
+
end
|
|
182
|
+
rescue Octokit::NotFound
|
|
183
|
+
# Ignore labels that are not found, because we want them removed anyway
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def add_comment(repo_name, issue_number, body)
|
|
188
|
+
if dry_run
|
|
189
|
+
puts "** dry-run: github.add_comment(#{repo_name.inspect}, #{issue_number.inspect}, #{body.pretty_inspect.chomp})".light_black
|
|
190
|
+
else
|
|
191
|
+
client.add_comment(repo_name, issue_number, body)
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def assign_user(repo_name, issue_number, assignee)
|
|
196
|
+
assignee = assignee[1..] if assignee.start_with?("@")
|
|
197
|
+
if dry_run
|
|
198
|
+
puts "** dry-run: github.update_issue(#{repo_name.inspect}, #{issue_number.inspect}, \"assignee\" => #{assignee.inspect})".light_black
|
|
199
|
+
else
|
|
200
|
+
client.update_issue(repo_name, issue_number, "assignee" => assignee)
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
141
204
|
def create_milestone(repo_name, title, due_on)
|
|
142
205
|
if dry_run
|
|
143
206
|
puts "** dry-run: github.create_milestone(#{repo_name.inspect}, #{title.inspect}, :due_on => #{due_on.strftime("%Y-%m-%d").inspect})".light_black
|
|
@@ -208,6 +271,14 @@ module MultiRepo::Service
|
|
|
208
271
|
end
|
|
209
272
|
end
|
|
210
273
|
|
|
274
|
+
def merge_pull_request(repo_name, pr_number)
|
|
275
|
+
if dry_run
|
|
276
|
+
puts "** dry-run: github.merge_pull_request(#{repo_name.inspect}, #{pr_number.inspect})".light_black
|
|
277
|
+
else
|
|
278
|
+
client.merge_pull_request(repo_name, pr_number)
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
|
|
211
282
|
def create_or_update_repository_secret(repo_name, key, value)
|
|
212
283
|
payload = encode_secret(repo_name, value)
|
|
213
284
|
|
data/lib/multi_repo/version.rb
CHANGED
data/multi_repo.gemspec
CHANGED
|
@@ -25,6 +25,7 @@ Gem::Specification.new do |spec|
|
|
|
25
25
|
spec.add_runtime_dependency "activesupport"
|
|
26
26
|
spec.add_runtime_dependency "colorize"
|
|
27
27
|
spec.add_runtime_dependency "config"
|
|
28
|
+
spec.add_runtime_dependency "json", "~> 2.9.1" # Pin json due to https://github.com/ruby/json/issues/752
|
|
28
29
|
spec.add_runtime_dependency "licensee"
|
|
29
30
|
spec.add_runtime_dependency "minigit"
|
|
30
31
|
spec.add_runtime_dependency "more_core_extensions"
|
data/renovate.json
CHANGED
data/scripts/destroy_remote
CHANGED
|
@@ -9,53 +9,32 @@ end
|
|
|
9
9
|
opts = Optimist.options do
|
|
10
10
|
synopsis "Add or remove labels on a set of pull requests."
|
|
11
11
|
|
|
12
|
-
opt :prs, "The list of PRs to
|
|
13
|
-
opt :add, "Labels to add", :type => :strings
|
|
14
|
-
opt :remove, "Labels to remove", :type => :strings
|
|
12
|
+
opt :prs, "The list of PRs to label", :type => :strings, :required => true
|
|
13
|
+
opt :add, "Labels to add", :type => :strings
|
|
14
|
+
opt :remove, "Labels to remove", :type => :strings
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
# TODO: Normalize any PR format to `org/repo#pr`
|
|
20
|
-
PR_REGEX = %r{^([^/#]+/[^/#]+)#([^/#]+)$}
|
|
21
|
-
Optimist.die :prs, "must be in the form `org/repo#pr`" unless opts[:prs].all? { |pr| pr.match?(PR_REGEX) }
|
|
22
|
-
|
|
23
|
-
def github
|
|
24
|
-
MultiRepo::Service::Github.client
|
|
25
|
-
end
|
|
16
|
+
opt :comment, "Comment explaining the label change", :type => :string
|
|
26
17
|
|
|
27
|
-
|
|
28
|
-
labels = Array(labels)
|
|
29
|
-
if dry_run
|
|
30
|
-
puts "** dry-run: github.add_labels_to_an_issue(#{repo_name.inspect}, #{pr_number.inspect}, #{labels.inspect})".light_black
|
|
31
|
-
else
|
|
32
|
-
github.add_labels_to_an_issue(repo_name, pr_number, labels)
|
|
33
|
-
end
|
|
18
|
+
MultiRepo::CLI.common_options(self, :only => :dry_run)
|
|
34
19
|
end
|
|
35
20
|
|
|
36
|
-
|
|
37
|
-
Array(labels).each do |label|
|
|
38
|
-
remove_label(repo_name, pr_number, label: label, dry_run: dry_run)
|
|
39
|
-
end
|
|
40
|
-
end
|
|
21
|
+
Optimist.die "at least one of --add or --remove is required" unless opts[:add_given] || opts[:remove_given]
|
|
41
22
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
23
|
+
prs =
|
|
24
|
+
begin
|
|
25
|
+
MultiRepo::Service::Github.parse_prs(opts[:prs])
|
|
26
|
+
rescue ArgumentError => err
|
|
27
|
+
Optimist.die :prs, err.message
|
|
47
28
|
end
|
|
48
|
-
rescue Octokit::NotFound
|
|
49
|
-
# Ignore labels that are not found, because we want them removed anyway
|
|
50
|
-
end
|
|
51
29
|
|
|
52
|
-
opts[:
|
|
53
|
-
puts MultiRepo::CLI.header(pr)
|
|
30
|
+
github = MultiRepo::Service::Github.new(:dry_run => opts[:dry_run])
|
|
54
31
|
|
|
55
|
-
|
|
32
|
+
prs.each do |repo_name, pr_number|
|
|
33
|
+
puts MultiRepo::CLI.header("#{repo_name}##{pr_number}")
|
|
56
34
|
|
|
57
|
-
|
|
58
|
-
|
|
35
|
+
github.add_labels_to_an_issue(repo_name, pr_number, opts[:add]) if opts[:add].present?
|
|
36
|
+
github.remove_labels_from_an_issue(repo_name, pr_number, opts[:remove]) if opts[:remove].present?
|
|
37
|
+
github.add_comment(repo_name, pr_number, opts[:comment]) if opts[:comment].present?
|
|
59
38
|
|
|
60
39
|
puts
|
|
61
40
|
end
|
data/scripts/pull_request_merger
CHANGED
|
@@ -16,50 +16,28 @@ opts = Optimist.options do
|
|
|
16
16
|
MultiRepo::CLI.common_options(self)
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
if dry_run
|
|
25
|
-
puts "** dry-run: github.merge_pull_request(#{repo_name.inspect}, #{pr_number.inspect})".light_black
|
|
26
|
-
else
|
|
27
|
-
begin
|
|
28
|
-
MultiRepo::Service::Github.client.merge_pull_request(repo_name, pr_number)
|
|
29
|
-
rescue Octokit::MethodNotAllowed => err
|
|
30
|
-
raise unless err.to_s.include?("Pull Request is not mergeable")
|
|
31
|
-
|
|
32
|
-
puts "** WARN: Pull Request is not mergeable"
|
|
33
|
-
end
|
|
19
|
+
prs =
|
|
20
|
+
begin
|
|
21
|
+
MultiRepo::Service::Github.parse_prs(opts[:prs])
|
|
22
|
+
rescue ArgumentError => err
|
|
23
|
+
Optimist.die :prs, err.message
|
|
34
24
|
end
|
|
35
|
-
end
|
|
36
25
|
|
|
37
|
-
|
|
38
|
-
labels = Array(labels)
|
|
39
|
-
if dry_run
|
|
40
|
-
puts "** dry-run: github.add_labels_to_an_issue(#{repo_name.inspect}, #{pr_number.inspect}, #{labels.inspect})".light_black
|
|
41
|
-
else
|
|
42
|
-
MultiRepo::Service::Github.client.add_labels_to_an_issue(repo_name, pr_number, labels)
|
|
43
|
-
end
|
|
44
|
-
end
|
|
26
|
+
github = MultiRepo::Service::Github.new(:dry_run => opts[:dry_run])
|
|
45
27
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if dry_run
|
|
49
|
-
puts "** dry-run: github.update_issue(#{repo_name.inspect}, #{pr_number.inspect}, \"assignee\" => #{assignee.inspect})".light_black
|
|
50
|
-
else
|
|
51
|
-
MultiRepo::Service::Github.client.update_issue(repo_name, pr_number, "assignee" => assignee)
|
|
52
|
-
end
|
|
53
|
-
end
|
|
28
|
+
prs.each do |repo_name, pr_number|
|
|
29
|
+
puts MultiRepo::CLI.header("#{repo_name}##{pr_number}")
|
|
54
30
|
|
|
55
|
-
|
|
56
|
-
|
|
31
|
+
begin
|
|
32
|
+
github.merge_pull_request(repo_name, pr_number)
|
|
33
|
+
rescue Octokit::MethodNotAllowed => err
|
|
34
|
+
raise unless err.to_s.include?("Pull Request is not mergeable")
|
|
57
35
|
|
|
58
|
-
|
|
36
|
+
puts "** WARN: Pull Request is not mergeable".light_yellow
|
|
37
|
+
end
|
|
59
38
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
assign_user(repo_name, pr_number, **opts)
|
|
39
|
+
github.add_labels_to_an_issue(repo_name, pr_number, opts[:labels]) if opts[:labels].present?
|
|
40
|
+
github.assign_user(repo_name, pr_number, opts[:assignee])
|
|
63
41
|
|
|
64
42
|
puts
|
|
65
43
|
end
|
data/scripts/show_commit_history
CHANGED
|
@@ -60,7 +60,12 @@ MultiRepo::CLI.repos_for(**opts).each do |repo|
|
|
|
60
60
|
log.lines.each do |line|
|
|
61
61
|
next unless (match = line.match(/Merge pull request #(\d+)\b/))
|
|
62
62
|
|
|
63
|
-
pr =
|
|
63
|
+
pr =
|
|
64
|
+
begin
|
|
65
|
+
github.pull_request(repo.name, match[1])
|
|
66
|
+
rescue Octokit::NotFound
|
|
67
|
+
next # PR not found could mean this was a cherry-pick from a different repo, so the PR doesn't exist.
|
|
68
|
+
end
|
|
64
69
|
label = pr.labels.detect { |l| results.key?(l.name) }&.name || "other"
|
|
65
70
|
results[label] << pr
|
|
66
71
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: multi_repo
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- ManageIQ Authors
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 2025-02-20 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: activesupport
|
|
@@ -52,6 +51,20 @@ dependencies:
|
|
|
52
51
|
- - ">="
|
|
53
52
|
- !ruby/object:Gem::Version
|
|
54
53
|
version: '0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: json
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: 2.9.1
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: 2.9.1
|
|
55
68
|
- !ruby/object:Gem::Dependency
|
|
56
69
|
name: licensee
|
|
57
70
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -345,7 +358,6 @@ homepage: http://github.com/ManageIQ/multi_repo
|
|
|
345
358
|
licenses:
|
|
346
359
|
- MIT
|
|
347
360
|
metadata: {}
|
|
348
|
-
post_install_message:
|
|
349
361
|
rdoc_options: []
|
|
350
362
|
require_paths:
|
|
351
363
|
- lib
|
|
@@ -360,8 +372,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
360
372
|
- !ruby/object:Gem::Version
|
|
361
373
|
version: '0'
|
|
362
374
|
requirements: []
|
|
363
|
-
rubygems_version: 3.
|
|
364
|
-
signing_key:
|
|
375
|
+
rubygems_version: 3.6.5
|
|
365
376
|
specification_version: 4
|
|
366
377
|
summary: MultiRepo is a library for managing multiple repositiories and running scripts
|
|
367
378
|
against them.
|