mirrorfile 0.1.0
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 +7 -0
- data/.yardopts +7 -0
- data/CHANGELOG.md +21 -0
- data/LICENSE.txt +21 -0
- data/README.md +77 -0
- data/Rakefile +18 -0
- data/exe/mirror +6 -0
- data/lib/mirrorfile/cli.rb +180 -0
- data/lib/mirrorfile/entry.rb +82 -0
- data/lib/mirrorfile/mirror.rb +235 -0
- data/lib/mirrorfile/mirrorfile.rb +126 -0
- data/lib/mirrorfile/version.rb +7 -0
- data/lib/mirrorfile.rb +62 -0
- data/mirrorfile.gemspec +45 -0
- metadata +119 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 2b29787fe3ec7d699137a8bc8f02dbfafd0ac95693dece15bf7199c977d3aa0e
|
|
4
|
+
data.tar.gz: df9541b78b01255029986e18f3144294f4e60b436b8d928c7e8ff8937177cadc
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: b3fd0d4747c159d0cf957012a7ba997b0dfc062278ef5d3b0b0bd823e23e96df11d78ebd863a1dbfe91beabac0a8e1a207e84cdbca662c0d2fa70a2ef5706172
|
|
7
|
+
data.tar.gz: b0fea1083d5ec020119d35d775d19545662223110306e0ac6ce87598c80bc190a952dcb4113ed7f056845b0074651097b17add026c7743f90cbdc23ac1b1895c
|
data/.yardopts
ADDED
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.1.0] - 2024-01-01
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Initial release
|
|
15
|
+
- `mirror init` command to initialize projects
|
|
16
|
+
- `mirror install` command to clone repositories
|
|
17
|
+
- `mirror update` command to pull latest changes
|
|
18
|
+
- `mirror list` command to show defined mirrors
|
|
19
|
+
- Mirrorfile DSL with `source` and `mirror` methods
|
|
20
|
+
- Rails/Zeitwerk integration for autoloading mirrored code
|
|
21
|
+
- Automatic `.gitignore` management
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Your Name
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Mirrorfile
|
|
2
|
+
|
|
3
|
+
Clone git repositories into a local `mirrors/` folder and keep them updated. Uses a `Mirrorfile` with Bundler-like syntax.
|
|
4
|
+
|
|
5
|
+
## Why use this
|
|
6
|
+
|
|
7
|
+
- You want to vendor gems or libraries without git submodules
|
|
8
|
+
- You need local copies of repos for reference or offline work
|
|
9
|
+
- You want Zeitwerk to autoload code from external repos in Rails
|
|
10
|
+
|
|
11
|
+
## Why not use this
|
|
12
|
+
|
|
13
|
+
- Git submodules already work fine for you
|
|
14
|
+
- You need pinned versions or tags (this just pulls `HEAD`)
|
|
15
|
+
- You want proper dependency management (use Bundler)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
```ruby
|
|
20
|
+
gem install mirrorfile
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
```bash
|
|
25
|
+
mirror init # creates Mirrorfile, .gitignore entry, zeitwerk initializer
|
|
26
|
+
mirror install # clones missing repos
|
|
27
|
+
mirror update # pulls existing repos
|
|
28
|
+
mirror list # shows defined mirrors
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Example Mirrorfile
|
|
32
|
+
|
|
33
|
+
You can change sources mid-file:
|
|
34
|
+
|
|
35
|
+
```ruby
|
|
36
|
+
# frozen_string_literal: true
|
|
37
|
+
|
|
38
|
+
# Rails ecosystem
|
|
39
|
+
source "https://github.com"
|
|
40
|
+
|
|
41
|
+
mirror "rails/rails", as: "rails-source"
|
|
42
|
+
mirror "hotwired/turbo-rails"
|
|
43
|
+
mirror "hotwired/stimulus-rails"
|
|
44
|
+
|
|
45
|
+
# Internal gems
|
|
46
|
+
source "https://git.company.com"
|
|
47
|
+
|
|
48
|
+
mirror "platform/shared-models", as: "shared-models"
|
|
49
|
+
mirror "platform/api-client"
|
|
50
|
+
|
|
51
|
+
# One-off from different host
|
|
52
|
+
mirror "https://gitlab.com/someorg/special-gem"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Contributing
|
|
56
|
+
|
|
57
|
+
Bug reports and pull requests are welcome on GitHub.
|
|
58
|
+
|
|
59
|
+
## Development
|
|
60
|
+
|
|
61
|
+
After checking out the repo:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
bin/setup
|
|
65
|
+
bundle exec rake test
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Generate documentation:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
bundle exec yard doc
|
|
72
|
+
bundle exec yard server # Browse at http://localhost:8808
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## License
|
|
76
|
+
|
|
77
|
+
The gem is available as open source under the terms of the [MIT License](LICENSE.txt).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/gem_tasks"
|
|
4
|
+
require "rake/testtask"
|
|
5
|
+
require "yard"
|
|
6
|
+
|
|
7
|
+
Rake::TestTask.new(:test) do |t|
|
|
8
|
+
t.libs << "test"
|
|
9
|
+
t.libs << "lib"
|
|
10
|
+
t.test_files = FileList["test/**/*_test.rb"]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
YARD::Rake::YardocTask.new do |t|
|
|
14
|
+
t.files = ["lib/**/*.rb"]
|
|
15
|
+
t.options = ["--markup", "markdown"]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
task default: :test
|
data/exe/mirror
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mirrorfile
|
|
4
|
+
# Command-line interface for Mirrorfile.
|
|
5
|
+
#
|
|
6
|
+
# CLI parses command-line arguments and dispatches to the appropriate
|
|
7
|
+
# {Mirror} methods. It provides a simple, git-like interface for
|
|
8
|
+
# managing mirrored repositories.
|
|
9
|
+
#
|
|
10
|
+
# @example Running from command line
|
|
11
|
+
# $ mirror init # Initialize project
|
|
12
|
+
# $ mirror install # Clone repositories
|
|
13
|
+
# $ mirror update # Pull latest changes
|
|
14
|
+
# $ mirror list # Show all mirrors
|
|
15
|
+
#
|
|
16
|
+
# @example Programmatic usage
|
|
17
|
+
# cli = Mirrorfile::CLI.new
|
|
18
|
+
# cli.call(["install"])
|
|
19
|
+
#
|
|
20
|
+
# @since 0.1.0
|
|
21
|
+
class CLI
|
|
22
|
+
# Available CLI commands
|
|
23
|
+
# @return [Array<String>] list of valid commands
|
|
24
|
+
COMMANDS = %w[init install update list help].freeze
|
|
25
|
+
|
|
26
|
+
# Creates a new CLI instance.
|
|
27
|
+
#
|
|
28
|
+
# @param stdout [IO] output stream for normal messages (default: $stdout)
|
|
29
|
+
# @param stderr [IO] output stream for error messages (default: $stderr)
|
|
30
|
+
# @return [CLI] a new CLI instance
|
|
31
|
+
def initialize(stdout: $stdout, stderr: $stderr)
|
|
32
|
+
@stdout = stdout
|
|
33
|
+
@stderr = stderr
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Parses arguments and executes the appropriate command.
|
|
37
|
+
#
|
|
38
|
+
# @param args [Array<String>] command-line arguments (typically ARGV)
|
|
39
|
+
# @return [Integer] exit status code (0 for success, 1 for error)
|
|
40
|
+
#
|
|
41
|
+
# @example
|
|
42
|
+
# cli = Mirrorfile::CLI.new
|
|
43
|
+
# exit_code = cli.call(["install"])
|
|
44
|
+
#
|
|
45
|
+
# @example With error handling
|
|
46
|
+
# cli = Mirrorfile::CLI.new
|
|
47
|
+
# exit cli.call(ARGV)
|
|
48
|
+
def call(args)
|
|
49
|
+
command = args.first
|
|
50
|
+
|
|
51
|
+
case command
|
|
52
|
+
when "init" then init
|
|
53
|
+
when "install" then install
|
|
54
|
+
when "update" then update
|
|
55
|
+
when "list" then list
|
|
56
|
+
when "help" then help
|
|
57
|
+
when "-h", "--help" then help
|
|
58
|
+
when "-v", "--version" then version
|
|
59
|
+
else usage
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
0
|
|
63
|
+
rescue MirrorfileNotFound => e
|
|
64
|
+
@stderr.puts "Error: #{e.message}"
|
|
65
|
+
1
|
|
66
|
+
rescue StandardError => e
|
|
67
|
+
@stderr.puts "Error: #{e.message}"
|
|
68
|
+
@stderr.puts e.backtrace.first(5).map { " #{_1}" } if ENV["DEBUG"]
|
|
69
|
+
1
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
# Executes the init command.
|
|
75
|
+
#
|
|
76
|
+
# @return [void]
|
|
77
|
+
# @api private
|
|
78
|
+
def init
|
|
79
|
+
Mirror.new.init
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Executes the install command.
|
|
83
|
+
#
|
|
84
|
+
# @return [void]
|
|
85
|
+
# @api private
|
|
86
|
+
def install
|
|
87
|
+
@stdout.puts "Installing mirrors..."
|
|
88
|
+
Mirror.new.install
|
|
89
|
+
@stdout.puts "Done."
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Executes the update command.
|
|
93
|
+
#
|
|
94
|
+
# @return [void]
|
|
95
|
+
# @api private
|
|
96
|
+
def update
|
|
97
|
+
@stdout.puts "Updating mirrors..."
|
|
98
|
+
Mirror.new.update
|
|
99
|
+
@stdout.puts "Done."
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Executes the list command.
|
|
103
|
+
#
|
|
104
|
+
# @return [void]
|
|
105
|
+
# @api private
|
|
106
|
+
def list
|
|
107
|
+
entries = Mirror.new.list
|
|
108
|
+
|
|
109
|
+
entries.empty? ? @stdout.puts("No mirrors defined.") : entries.each { @stdout.puts _1 }
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Displays help information.
|
|
113
|
+
#
|
|
114
|
+
# @return [void]
|
|
115
|
+
# @api private
|
|
116
|
+
def help
|
|
117
|
+
@stdout.puts <<~HELP
|
|
118
|
+
Mirrorfile - Manage local mirrors of git repositories
|
|
119
|
+
|
|
120
|
+
Usage: mirror <command>
|
|
121
|
+
|
|
122
|
+
Commands:
|
|
123
|
+
init Initialize project with Mirrorfile, .gitignore entry,
|
|
124
|
+
and Zeitwerk initializer for Rails projects
|
|
125
|
+
install Clone repositories that don't exist locally
|
|
126
|
+
update Pull latest changes for existing repositories
|
|
127
|
+
list Show all defined mirrors
|
|
128
|
+
help Show this help message
|
|
129
|
+
|
|
130
|
+
Options:
|
|
131
|
+
-h, --help Show this help message
|
|
132
|
+
-v, --version Show version number
|
|
133
|
+
|
|
134
|
+
Examples:
|
|
135
|
+
$ mirror init
|
|
136
|
+
$ mirror install
|
|
137
|
+
$ mirror update
|
|
138
|
+
|
|
139
|
+
Mirrorfile syntax:
|
|
140
|
+
source "https://github.com"
|
|
141
|
+
|
|
142
|
+
mirror "rails/rails", as: "rails-source"
|
|
143
|
+
mirror "hotwired/turbo-rails"
|
|
144
|
+
|
|
145
|
+
source "https://gitlab.com"
|
|
146
|
+
|
|
147
|
+
mirror "org/project"
|
|
148
|
+
|
|
149
|
+
For more information, see: https://github.com/n-at-han-k/mirrorfile
|
|
150
|
+
HELP
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Displays version information.
|
|
154
|
+
#
|
|
155
|
+
# @return [void]
|
|
156
|
+
# @api private
|
|
157
|
+
def version
|
|
158
|
+
@stdout.puts "mirrorfile #{Mirrorfile::VERSION}"
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Displays usage information for unknown commands.
|
|
162
|
+
#
|
|
163
|
+
# @return [void]
|
|
164
|
+
# @api private
|
|
165
|
+
def usage
|
|
166
|
+
@stderr.puts <<~USAGE
|
|
167
|
+
Usage: mirror <command>
|
|
168
|
+
|
|
169
|
+
Commands:
|
|
170
|
+
init Create Mirrorfile, .gitignore entry, and Zeitwerk initializer
|
|
171
|
+
install Clone repositories that don't exist locally
|
|
172
|
+
update Pull latest changes for existing repositories
|
|
173
|
+
list Show all defined mirrors
|
|
174
|
+
help Show detailed help
|
|
175
|
+
|
|
176
|
+
Run 'mirror help' for more information.
|
|
177
|
+
USAGE
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mirrorfile
|
|
4
|
+
# Represents a single repository entry to be mirrored.
|
|
5
|
+
#
|
|
6
|
+
# Entry is an immutable data object that holds the URL and local name
|
|
7
|
+
# for a mirrored repository. It provides methods for cloning and updating
|
|
8
|
+
# the repository.
|
|
9
|
+
#
|
|
10
|
+
# @example Creating an entry
|
|
11
|
+
# entry = Mirrorfile::Entry.new(
|
|
12
|
+
# url: "https://github.com/rails/rails",
|
|
13
|
+
# name: "rails-source"
|
|
14
|
+
# )
|
|
15
|
+
# entry.install(Pathname.new("mirrors"))
|
|
16
|
+
#
|
|
17
|
+
# @!attribute [r] url
|
|
18
|
+
# @return [String] the full git URL of the repository
|
|
19
|
+
#
|
|
20
|
+
# @!attribute [r] name
|
|
21
|
+
# @return [String] the local directory name for the clone
|
|
22
|
+
#
|
|
23
|
+
# @since 0.1.0
|
|
24
|
+
Entry = Data.define(:url, :name) do
|
|
25
|
+
# Returns the local path where this repository will be cloned.
|
|
26
|
+
#
|
|
27
|
+
# @param base_dir [Pathname] the base directory containing all mirrors
|
|
28
|
+
# @return [Pathname] the full path to this repository's local directory
|
|
29
|
+
#
|
|
30
|
+
# @example
|
|
31
|
+
# entry = Entry.new(url: "https://github.com/rails/rails", name: "rails")
|
|
32
|
+
# entry.local_path(Pathname.new("/project/mirrors"))
|
|
33
|
+
# #=> #<Pathname:/project/mirrors/rails>
|
|
34
|
+
def local_path(base_dir)
|
|
35
|
+
base_dir.join(name)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Clones the repository if it doesn't already exist locally.
|
|
39
|
+
#
|
|
40
|
+
# This method is idempotent - calling it multiple times will only
|
|
41
|
+
# clone the repository once. If the local directory already exists,
|
|
42
|
+
# no action is taken.
|
|
43
|
+
#
|
|
44
|
+
# @param base_dir [Pathname] the base directory to clone into
|
|
45
|
+
# @return [Boolean, nil] true if clone succeeded, false if failed,
|
|
46
|
+
# nil if already exists
|
|
47
|
+
#
|
|
48
|
+
# @example
|
|
49
|
+
# entry.install(Pathname.new("mirrors"))
|
|
50
|
+
#
|
|
51
|
+
# @see #update
|
|
52
|
+
def install(base_dir)
|
|
53
|
+
dir = local_path(base_dir)
|
|
54
|
+
system("git", "clone", url, dir.to_s) unless dir.exist?
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Updates an existing repository by pulling the latest changes.
|
|
58
|
+
#
|
|
59
|
+
# Uses fast-forward only merges to avoid creating merge commits.
|
|
60
|
+
# If the local directory doesn't exist, no action is taken.
|
|
61
|
+
#
|
|
62
|
+
# @param base_dir [Pathname] the base directory containing the clone
|
|
63
|
+
# @return [Boolean, nil] true if pull succeeded, false if failed,
|
|
64
|
+
# nil if directory doesn't exist
|
|
65
|
+
#
|
|
66
|
+
# @example
|
|
67
|
+
# entry.update(Pathname.new("mirrors"))
|
|
68
|
+
#
|
|
69
|
+
# @see #install
|
|
70
|
+
def update(base_dir)
|
|
71
|
+
dir = local_path(base_dir)
|
|
72
|
+
system("git", "-C", dir.to_s, "pull", "--ff-only") if dir.exist?
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Returns a human-readable representation of the entry.
|
|
76
|
+
#
|
|
77
|
+
# @return [String] formatted string showing url and name
|
|
78
|
+
def to_s
|
|
79
|
+
"#{name} (#{url})"
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mirrorfile
|
|
4
|
+
# Orchestrates mirror operations: init, install, and update.
|
|
5
|
+
#
|
|
6
|
+
# Mirror is the main interface for performing operations on mirrored
|
|
7
|
+
# repositories. It handles loading the Mirrorfile, creating necessary
|
|
8
|
+
# directories and files, and delegating to individual entries.
|
|
9
|
+
#
|
|
10
|
+
# @example Initializing a new project
|
|
11
|
+
# mirror = Mirrorfile::Mirror.new
|
|
12
|
+
# mirror.init
|
|
13
|
+
#
|
|
14
|
+
# @example Installing and updating mirrors
|
|
15
|
+
# mirror = Mirrorfile::Mirror.new
|
|
16
|
+
# mirror.install # Clone missing repositories
|
|
17
|
+
# mirror.update # Pull latest changes
|
|
18
|
+
#
|
|
19
|
+
# @since 0.1.0
|
|
20
|
+
class Mirror
|
|
21
|
+
# @return [Pathname] the project root directory
|
|
22
|
+
attr_reader :root
|
|
23
|
+
|
|
24
|
+
# @return [Pathname] the mirrors directory path
|
|
25
|
+
attr_reader :mirrors_dir
|
|
26
|
+
|
|
27
|
+
# @return [Pathname] the .gitignore file path
|
|
28
|
+
attr_reader :gitignore_path
|
|
29
|
+
|
|
30
|
+
# @return [Pathname] the Mirrorfile path
|
|
31
|
+
attr_reader :mirrorfile_path
|
|
32
|
+
|
|
33
|
+
# @return [Pathname] the Rails initializer path
|
|
34
|
+
attr_reader :initializer_path
|
|
35
|
+
|
|
36
|
+
# Creates a new Mirror instance.
|
|
37
|
+
#
|
|
38
|
+
# @param root [Pathname, String] the project root directory
|
|
39
|
+
# (defaults to current working directory)
|
|
40
|
+
# @return [Mirror] a new Mirror instance
|
|
41
|
+
#
|
|
42
|
+
# @example With default root
|
|
43
|
+
# mirror = Mirrorfile::Mirror.new
|
|
44
|
+
#
|
|
45
|
+
# @example With custom root
|
|
46
|
+
# mirror = Mirrorfile::Mirror.new(root: "/path/to/project")
|
|
47
|
+
def initialize(root: Dir.pwd)
|
|
48
|
+
@root = Pathname.new(root)
|
|
49
|
+
@mirrors_dir = @root.join("mirrors")
|
|
50
|
+
@gitignore_path = @root.join(".gitignore")
|
|
51
|
+
@mirrorfile_path = @root.join("Mirrorfile")
|
|
52
|
+
@initializer_path = @root.join("config/initializers/mirrors.rb")
|
|
53
|
+
@mirrorfile = load_mirrorfile if @mirrorfile_path.exist?
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Clones all repositories that don't exist locally.
|
|
57
|
+
#
|
|
58
|
+
# Creates the mirrors directory if it doesn't exist, then iterates
|
|
59
|
+
# through all entries in the Mirrorfile and clones any that are missing.
|
|
60
|
+
#
|
|
61
|
+
# @return [void]
|
|
62
|
+
# @raise [MirrorfileNotFound] if Mirrorfile doesn't exist
|
|
63
|
+
#
|
|
64
|
+
# @example
|
|
65
|
+
# mirror = Mirrorfile::Mirror.new
|
|
66
|
+
# mirror.install
|
|
67
|
+
#
|
|
68
|
+
# @see Entry#install
|
|
69
|
+
def install
|
|
70
|
+
ensure_mirrorfile!
|
|
71
|
+
mirrors_dir.mkpath
|
|
72
|
+
@mirrorfile.entries.each { _1.install(mirrors_dir) }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Updates all existing local repositories.
|
|
76
|
+
#
|
|
77
|
+
# Iterates through all entries in the Mirrorfile and pulls the latest
|
|
78
|
+
# changes for any that exist locally. Repositories that haven't been
|
|
79
|
+
# cloned are skipped.
|
|
80
|
+
#
|
|
81
|
+
# @return [void]
|
|
82
|
+
# @raise [MirrorfileNotFound] if Mirrorfile doesn't exist
|
|
83
|
+
#
|
|
84
|
+
# @example
|
|
85
|
+
# mirror = Mirrorfile::Mirror.new
|
|
86
|
+
# mirror.update
|
|
87
|
+
#
|
|
88
|
+
# @see Entry#update
|
|
89
|
+
def update
|
|
90
|
+
ensure_mirrorfile!
|
|
91
|
+
@mirrorfile.entries.each { _1.update(mirrors_dir) }
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Initializes a new project with mirror support.
|
|
95
|
+
#
|
|
96
|
+
# This method creates all necessary files and directories for using
|
|
97
|
+
# Mirrorfile in a project:
|
|
98
|
+
#
|
|
99
|
+
# - Creates a Mirrorfile with example syntax
|
|
100
|
+
# - Adds /mirrors to .gitignore (creates file if needed)
|
|
101
|
+
# - Creates a Rails initializer for Zeitwerk autoloading
|
|
102
|
+
#
|
|
103
|
+
# Existing files are not overwritten.
|
|
104
|
+
#
|
|
105
|
+
# @return [void]
|
|
106
|
+
#
|
|
107
|
+
# @example
|
|
108
|
+
# mirror = Mirrorfile::Mirror.new
|
|
109
|
+
# mirror.init
|
|
110
|
+
# # => Creates Mirrorfile, updates .gitignore, creates initializer
|
|
111
|
+
#
|
|
112
|
+
# @see #create_mirrorfile
|
|
113
|
+
# @see #setup_gitignore
|
|
114
|
+
# @see #setup_zeitwerk
|
|
115
|
+
def init
|
|
116
|
+
create_mirrorfile
|
|
117
|
+
setup_gitignore
|
|
118
|
+
setup_zeitwerk
|
|
119
|
+
puts "Initialized mirrors in #{root}"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Lists all entries in the Mirrorfile.
|
|
123
|
+
#
|
|
124
|
+
# @return [Array<Entry>] array of all mirror entries
|
|
125
|
+
# @raise [MirrorfileNotFound] if Mirrorfile doesn't exist
|
|
126
|
+
#
|
|
127
|
+
# @example
|
|
128
|
+
# mirror.list.each { |entry| puts entry }
|
|
129
|
+
def list
|
|
130
|
+
ensure_mirrorfile!
|
|
131
|
+
@mirrorfile.entries.to_a
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
private
|
|
135
|
+
|
|
136
|
+
# Loads and parses the Mirrorfile.
|
|
137
|
+
#
|
|
138
|
+
# @return [Mirrorfile] the parsed Mirrorfile
|
|
139
|
+
# @api private
|
|
140
|
+
def load_mirrorfile
|
|
141
|
+
Mirrorfile.load(mirrorfile_path)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Raises an error if Mirrorfile doesn't exist.
|
|
145
|
+
#
|
|
146
|
+
# @raise [MirrorfileNotFound] if Mirrorfile doesn't exist
|
|
147
|
+
# @return [void]
|
|
148
|
+
# @api private
|
|
149
|
+
def ensure_mirrorfile!
|
|
150
|
+
raise MirrorfileNotFound, "Run 'mirror init' first" unless @mirrorfile
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Creates a new Mirrorfile with example syntax.
|
|
154
|
+
#
|
|
155
|
+
# Does nothing if Mirrorfile already exists.
|
|
156
|
+
#
|
|
157
|
+
# @return [Integer, nil] bytes written or nil if file exists
|
|
158
|
+
# @api private
|
|
159
|
+
def create_mirrorfile
|
|
160
|
+
mirrorfile_path.exist? || mirrorfile_path.write(<<~RUBY)
|
|
161
|
+
# frozen_string_literal: true
|
|
162
|
+
|
|
163
|
+
# Mirror repositories
|
|
164
|
+
#
|
|
165
|
+
# Set a source for subsequent mirror declarations:
|
|
166
|
+
#
|
|
167
|
+
# source "https://github.com"
|
|
168
|
+
#
|
|
169
|
+
# Then declare mirrors with optional custom names:
|
|
170
|
+
#
|
|
171
|
+
# mirror "user/repo"
|
|
172
|
+
# mirror "user/other-repo", as: "custom-name"
|
|
173
|
+
#
|
|
174
|
+
# You can change sources mid-file:
|
|
175
|
+
#
|
|
176
|
+
# source "https://gitlab.com"
|
|
177
|
+
#
|
|
178
|
+
# mirror "org/project"
|
|
179
|
+
#
|
|
180
|
+
# Or use full URLs without a source:
|
|
181
|
+
#
|
|
182
|
+
# mirror "https://bitbucket.org/team/repo"
|
|
183
|
+
|
|
184
|
+
source "https://github.com"
|
|
185
|
+
|
|
186
|
+
# mirror "rails/rails", as: "rails-source"
|
|
187
|
+
RUBY
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Adds /mirrors to .gitignore.
|
|
191
|
+
#
|
|
192
|
+
# Creates .gitignore if it doesn't exist. Appends the ignore
|
|
193
|
+
# pattern only if not already present.
|
|
194
|
+
#
|
|
195
|
+
# @return [Integer, nil] bytes written or nil if already ignored
|
|
196
|
+
# @api private
|
|
197
|
+
def setup_gitignore
|
|
198
|
+
gitignore_path.exist? || gitignore_path.write("")
|
|
199
|
+
|
|
200
|
+
lines = gitignore_path.readlines.map(&:chomp)
|
|
201
|
+
lines.include?("/mirrors") || gitignore_path.write([*lines, "/mirrors"].join("\n") + "\n")
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Creates a Rails initializer for Zeitwerk autoloading.
|
|
205
|
+
#
|
|
206
|
+
# The initializer configures Zeitwerk to autoload code from
|
|
207
|
+
# lib/ directories within mirrored repositories.
|
|
208
|
+
#
|
|
209
|
+
# Does nothing if initializer already exists.
|
|
210
|
+
#
|
|
211
|
+
# @return [Integer, nil] bytes written or nil if file exists
|
|
212
|
+
# @api private
|
|
213
|
+
def setup_zeitwerk
|
|
214
|
+
initializer_path.dirname.mkpath
|
|
215
|
+
initializer_path.exist? || initializer_path.write(<<~RUBY)
|
|
216
|
+
# frozen_string_literal: true
|
|
217
|
+
|
|
218
|
+
# Autoload mirrored repositories
|
|
219
|
+
#
|
|
220
|
+
# This initializer configures Zeitwerk to autoload code from
|
|
221
|
+
# lib/ directories within mirrored repositories.
|
|
222
|
+
#
|
|
223
|
+
# @see https://github.com/fxn/zeitwerk
|
|
224
|
+
|
|
225
|
+
Rails.autoloaders.main.tap do |loader|
|
|
226
|
+
mirrors = Rails.root.join("mirrors")
|
|
227
|
+
|
|
228
|
+
mirrors.glob("*/lib").each do |lib_path|
|
|
229
|
+
loader.push_dir(lib_path)
|
|
230
|
+
end if mirrors.exist?
|
|
231
|
+
end
|
|
232
|
+
RUBY
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mirrorfile
|
|
4
|
+
# Provides the DSL for parsing and evaluating Mirrorfile contents.
|
|
5
|
+
#
|
|
6
|
+
# Mirrorfile implements a domain-specific language similar to Bundler's
|
|
7
|
+
# Gemfile. It allows users to specify repository sources and mirror
|
|
8
|
+
# definitions in a readable, declarative format.
|
|
9
|
+
#
|
|
10
|
+
# @example Mirrorfile DSL
|
|
11
|
+
# source "https://github.com"
|
|
12
|
+
#
|
|
13
|
+
# mirror "rails/rails", as: "rails-source"
|
|
14
|
+
# mirror "hotwired/turbo-rails"
|
|
15
|
+
#
|
|
16
|
+
# source "https://gitlab.com"
|
|
17
|
+
#
|
|
18
|
+
# mirror "org/project"
|
|
19
|
+
#
|
|
20
|
+
# @example Programmatic usage
|
|
21
|
+
# mirrorfile = Mirrorfile::Mirrorfile.new
|
|
22
|
+
# mirrorfile.source("https://github.com")
|
|
23
|
+
# mirrorfile.mirror("rails/rails", as: "rails")
|
|
24
|
+
# mirrorfile.entries.each { |e| puts e.url }
|
|
25
|
+
#
|
|
26
|
+
# @since 0.1.0
|
|
27
|
+
class Mirrorfile
|
|
28
|
+
# Creates a new Mirrorfile instance.
|
|
29
|
+
#
|
|
30
|
+
# @return [Mirrorfile] a new instance with no entries or source
|
|
31
|
+
def initialize
|
|
32
|
+
@entries = []
|
|
33
|
+
@source = nil
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Sets the base URL for subsequent mirror declarations.
|
|
37
|
+
#
|
|
38
|
+
# The source URL is prepended to repository paths in following
|
|
39
|
+
# {#mirror} calls until a new source is declared. This allows
|
|
40
|
+
# grouping repositories by host without repeating the full URL.
|
|
41
|
+
#
|
|
42
|
+
# @param url [String] the base URL for repositories (e.g., "https://github.com")
|
|
43
|
+
# @return [String] the normalized source URL (trailing slash removed)
|
|
44
|
+
#
|
|
45
|
+
# @example Setting a GitHub source
|
|
46
|
+
# source "https://github.com"
|
|
47
|
+
# mirror "rails/rails" # clones from https://github.com/rails/rails
|
|
48
|
+
#
|
|
49
|
+
# @example Multiple sources
|
|
50
|
+
# source "https://github.com"
|
|
51
|
+
# mirror "user/repo1"
|
|
52
|
+
#
|
|
53
|
+
# source "https://gitlab.com"
|
|
54
|
+
# mirror "user/repo2" # clones from https://gitlab.com/user/repo2
|
|
55
|
+
def source(url)
|
|
56
|
+
@source = url.chomp("/")
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Declares a repository to be mirrored.
|
|
60
|
+
#
|
|
61
|
+
# If a {#source} has been set, the path is appended to it to form
|
|
62
|
+
# the full URL. Otherwise, the path is treated as a complete URL.
|
|
63
|
+
#
|
|
64
|
+
# @param path [String] the repository path or full URL
|
|
65
|
+
# @param as [String] the local directory name (defaults to repo name)
|
|
66
|
+
# @return [Entry] the newly created entry
|
|
67
|
+
#
|
|
68
|
+
# @example With source set
|
|
69
|
+
# source "https://github.com"
|
|
70
|
+
# mirror "rails/rails" # uses source + path
|
|
71
|
+
# mirror "hotwired/turbo", as: "turbo" # custom local name
|
|
72
|
+
#
|
|
73
|
+
# @example Without source (full URL)
|
|
74
|
+
# mirror "https://github.com/rails/rails"
|
|
75
|
+
# mirror "git@github.com:rails/rails.git", as: "rails"
|
|
76
|
+
#
|
|
77
|
+
# @see #source
|
|
78
|
+
def mirror(path, as: File.basename(path, ".git"))
|
|
79
|
+
url = @source ? "#{@source}/#{path}" : path
|
|
80
|
+
Entry.new(url:, name: as).tap { @entries << _1 }
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Returns a lazy enumerator of all declared entries.
|
|
84
|
+
#
|
|
85
|
+
# Using a lazy enumerator allows for efficient iteration over
|
|
86
|
+
# entries without loading them all into memory at once, and
|
|
87
|
+
# enables chaining with other enumerable methods.
|
|
88
|
+
#
|
|
89
|
+
# @return [Enumerator::Lazy<Entry>] lazy enumerator of Entry objects
|
|
90
|
+
#
|
|
91
|
+
# @example Iterating over entries
|
|
92
|
+
# mirrorfile.entries.each { |entry| entry.install(base_dir) }
|
|
93
|
+
#
|
|
94
|
+
# @example Filtering entries
|
|
95
|
+
# mirrorfile.entries
|
|
96
|
+
# .select { |e| e.name.start_with?("rails") }
|
|
97
|
+
# .each { |e| e.update(base_dir) }
|
|
98
|
+
def entries
|
|
99
|
+
@entries.lazy
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Returns the number of declared entries.
|
|
103
|
+
#
|
|
104
|
+
# @return [Integer] the count of mirror entries
|
|
105
|
+
def size
|
|
106
|
+
@entries.size
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Loads and evaluates a Mirrorfile from disk.
|
|
110
|
+
#
|
|
111
|
+
# @param path [Pathname, String] path to the Mirrorfile
|
|
112
|
+
# @return [Mirrorfile] a new instance with entries from the file
|
|
113
|
+
# @raise [MirrorfileNotFound] if the file doesn't exist
|
|
114
|
+
# @raise [SyntaxError] if the file contains invalid Ruby
|
|
115
|
+
#
|
|
116
|
+
# @example
|
|
117
|
+
# mirrorfile = Mirrorfile::Mirrorfile.load("Mirrorfile")
|
|
118
|
+
# mirrorfile.entries.each { |e| puts e.url }
|
|
119
|
+
def self.load(path)
|
|
120
|
+
path = Pathname.new(path)
|
|
121
|
+
raise MirrorfileNotFound, "Mirrorfile not found at #{path}" unless path.exist?
|
|
122
|
+
|
|
123
|
+
new.tap { _1.instance_eval(path.read, path.to_s) }
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
data/lib/mirrorfile.rb
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pathname"
|
|
4
|
+
|
|
5
|
+
require_relative "mirrorfile/version"
|
|
6
|
+
require_relative "mirrorfile/entry"
|
|
7
|
+
require_relative "mirrorfile/mirrorfile"
|
|
8
|
+
require_relative "mirrorfile/mirror"
|
|
9
|
+
require_relative "mirrorfile/cli"
|
|
10
|
+
|
|
11
|
+
# Mirrorfile is a gem for managing local mirrors of git repositories.
|
|
12
|
+
#
|
|
13
|
+
# It provides a DSL similar to Bundler's Gemfile for specifying repositories
|
|
14
|
+
# to clone and keep updated locally. This is useful for vendoring dependencies,
|
|
15
|
+
# referencing source code, or maintaining offline copies of repositories.
|
|
16
|
+
#
|
|
17
|
+
# @example Basic usage with a Mirrorfile
|
|
18
|
+
# # Mirrorfile
|
|
19
|
+
# source "https://github.com"
|
|
20
|
+
#
|
|
21
|
+
# mirror "rails/rails", as: "rails-source"
|
|
22
|
+
# mirror "hotwired/turbo-rails"
|
|
23
|
+
#
|
|
24
|
+
# @example Command line usage
|
|
25
|
+
# $ bin/mirror init # Initialize project with Mirrorfile
|
|
26
|
+
# $ bin/mirror install # Clone all repositories
|
|
27
|
+
# $ bin/mirror update # Pull latest changes
|
|
28
|
+
#
|
|
29
|
+
# @author Your Name
|
|
30
|
+
# @since 0.1.0
|
|
31
|
+
module Mirrorfile
|
|
32
|
+
class Error < StandardError; end
|
|
33
|
+
|
|
34
|
+
# Error raised when Mirrorfile is not found
|
|
35
|
+
class MirrorfileNotFound < Error; end
|
|
36
|
+
|
|
37
|
+
# Error raised when a git operation fails
|
|
38
|
+
class GitOperationError < Error; end
|
|
39
|
+
|
|
40
|
+
class << self
|
|
41
|
+
# Returns the root path for mirror operations
|
|
42
|
+
#
|
|
43
|
+
# @return [Pathname] the current working directory as a Pathname
|
|
44
|
+
def root
|
|
45
|
+
Pathname.new(Dir.pwd)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Returns the path to the mirrors directory
|
|
49
|
+
#
|
|
50
|
+
# @return [Pathname] path to the mirrors directory
|
|
51
|
+
def mirrors_dir
|
|
52
|
+
root.join("mirrors")
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Returns the path to the Mirrorfile
|
|
56
|
+
#
|
|
57
|
+
# @return [Pathname] path to the Mirrorfile
|
|
58
|
+
def mirrorfile_path
|
|
59
|
+
root.join("Mirrorfile")
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
data/mirrorfile.gemspec
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "lib/mirrorfile/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "mirrorfile"
|
|
7
|
+
spec.version = Mirrorfile::VERSION
|
|
8
|
+
spec.authors = ["Nathan Kidd"]
|
|
9
|
+
spec.email = ["nathankidd@hey.com"]
|
|
10
|
+
|
|
11
|
+
spec.summary = "Manage local mirrors of git repositories"
|
|
12
|
+
|
|
13
|
+
spec.description = <<~DESC
|
|
14
|
+
Mirrorfile provides a DSL similar to Bundler's Gemfile for managing local
|
|
15
|
+
mirrors of git repositories. Clone and keep repositories updated with
|
|
16
|
+
simple commands. Includes Rails/Zeitwerk integration for autoloading
|
|
17
|
+
mirrored code.
|
|
18
|
+
DESC
|
|
19
|
+
|
|
20
|
+
spec.homepage = "https://github.com/n-at-han-k/mirrorfile"
|
|
21
|
+
spec.license = "MIT"
|
|
22
|
+
spec.required_ruby_version = ">= 3.2.0"
|
|
23
|
+
|
|
24
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
25
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
|
26
|
+
spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
|
|
27
|
+
spec.metadata["documentation_uri"] = spec.homepage
|
|
28
|
+
spec.metadata["rubygems_mfa_required"] = "true"
|
|
29
|
+
|
|
30
|
+
spec.files = Dir.chdir(__dir__) do
|
|
31
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
|
32
|
+
(File.expand_path(f) == __FILE__) ||
|
|
33
|
+
f.start_with?(*%w[bin/ test/ spec/ features/ .git .github appveyor Gemfile])
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
spec.bindir = "exe"
|
|
38
|
+
spec.executables = ["mirror"]
|
|
39
|
+
spec.require_paths = ["lib"]
|
|
40
|
+
|
|
41
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
|
42
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
|
43
|
+
spec.add_development_dependency "rubocop", "~> 1.21"
|
|
44
|
+
spec.add_development_dependency "yard", "~> 0.9"
|
|
45
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: mirrorfile
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Nathan Kidd
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: minitest
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '5.0'
|
|
19
|
+
type: :development
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '5.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: rake
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '13.0'
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '13.0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: rubocop
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '1.21'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '1.21'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: yard
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0.9'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '0.9'
|
|
68
|
+
description: |
|
|
69
|
+
Mirrorfile provides a DSL similar to Bundler's Gemfile for managing local
|
|
70
|
+
mirrors of git repositories. Clone and keep repositories updated with
|
|
71
|
+
simple commands. Includes Rails/Zeitwerk integration for autoloading
|
|
72
|
+
mirrored code.
|
|
73
|
+
email:
|
|
74
|
+
- nathankidd@hey.com
|
|
75
|
+
executables:
|
|
76
|
+
- mirror
|
|
77
|
+
extensions: []
|
|
78
|
+
extra_rdoc_files: []
|
|
79
|
+
files:
|
|
80
|
+
- ".yardopts"
|
|
81
|
+
- CHANGELOG.md
|
|
82
|
+
- LICENSE.txt
|
|
83
|
+
- README.md
|
|
84
|
+
- Rakefile
|
|
85
|
+
- exe/mirror
|
|
86
|
+
- lib/mirrorfile.rb
|
|
87
|
+
- lib/mirrorfile/cli.rb
|
|
88
|
+
- lib/mirrorfile/entry.rb
|
|
89
|
+
- lib/mirrorfile/mirror.rb
|
|
90
|
+
- lib/mirrorfile/mirrorfile.rb
|
|
91
|
+
- lib/mirrorfile/version.rb
|
|
92
|
+
- mirrorfile.gemspec
|
|
93
|
+
homepage: https://github.com/n-at-han-k/mirrorfile
|
|
94
|
+
licenses:
|
|
95
|
+
- MIT
|
|
96
|
+
metadata:
|
|
97
|
+
homepage_uri: https://github.com/n-at-han-k/mirrorfile
|
|
98
|
+
source_code_uri: https://github.com/n-at-han-k/mirrorfile
|
|
99
|
+
changelog_uri: https://github.com/n-at-han-k/mirrorfile/blob/main/CHANGELOG.md
|
|
100
|
+
documentation_uri: https://github.com/n-at-han-k/mirrorfile
|
|
101
|
+
rubygems_mfa_required: 'true'
|
|
102
|
+
rdoc_options: []
|
|
103
|
+
require_paths:
|
|
104
|
+
- lib
|
|
105
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - ">="
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: 3.2.0
|
|
110
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
111
|
+
requirements:
|
|
112
|
+
- - ">="
|
|
113
|
+
- !ruby/object:Gem::Version
|
|
114
|
+
version: '0'
|
|
115
|
+
requirements: []
|
|
116
|
+
rubygems_version: 3.6.7
|
|
117
|
+
specification_version: 4
|
|
118
|
+
summary: Manage local mirrors of git repositories
|
|
119
|
+
test_files: []
|