mirrorfile 0.1.0 → 1.0.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 +4 -4
- data/.envrc +1 -0
- data/README.md +1 -1
- data/flake.lock +61 -0
- data/flake.nix +34 -0
- data/lib/mirrorfile/cli.rb +63 -17
- data/lib/mirrorfile/cli_legacy.rb +74 -0
- data/lib/mirrorfile/entry.rb +21 -4
- data/lib/mirrorfile/mirror.rb +61 -11
- data/lib/mirrorfile/version.rb +1 -1
- data/lib/mirrorfile.rb +9 -8
- data/templates/README.md.template +21 -0
- data/templates/envrc.template +24 -0
- metadata +9 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 513175ce2e6de6e4e0a0a810d844ce5a280fd3e899cf490b289249054a8c37f9
|
|
4
|
+
data.tar.gz: 9a8ca82bdd368ef020c97fbf156aba1e5d3cad6860e5eef4e02226165c167277
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0046b1bde5286137d682244bf597e8f72a0d43a206c6013b4dc470b08b9dead8ebbe0d7aa390c63437330b9ccf15c93dbefc99ca8e5a0bd346606166232f38b9
|
|
7
|
+
data.tar.gz: fa70c541f5cdaa6e17c2a654bf07a094cb2037a405409a9fda00fe8f7d725331cb6b66737915fe1df8b39a2f7c8bdbe4bcd887a1a536fd7c1420fa8bcbbdd971
|
data/.envrc
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
use flake
|
data/README.md
CHANGED
|
@@ -22,7 +22,7 @@ gem install mirrorfile
|
|
|
22
22
|
|
|
23
23
|
## Usage
|
|
24
24
|
```bash
|
|
25
|
-
mirror init # creates Mirrorfile, .gitignore entry, zeitwerk initializer
|
|
25
|
+
mirror init # creates Mirrorfile, .gitignore entry, zeitwerk initializer (Rails only)
|
|
26
26
|
mirror install # clones missing repos
|
|
27
27
|
mirror update # pulls existing repos
|
|
28
28
|
mirror list # shows defined mirrors
|
data/flake.lock
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"nodes": {
|
|
3
|
+
"flake-utils": {
|
|
4
|
+
"inputs": {
|
|
5
|
+
"systems": "systems"
|
|
6
|
+
},
|
|
7
|
+
"locked": {
|
|
8
|
+
"lastModified": 1731533236,
|
|
9
|
+
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
|
10
|
+
"owner": "numtide",
|
|
11
|
+
"repo": "flake-utils",
|
|
12
|
+
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
|
13
|
+
"type": "github"
|
|
14
|
+
},
|
|
15
|
+
"original": {
|
|
16
|
+
"owner": "numtide",
|
|
17
|
+
"repo": "flake-utils",
|
|
18
|
+
"type": "github"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"nixpkgs": {
|
|
22
|
+
"locked": {
|
|
23
|
+
"lastModified": 1772773019,
|
|
24
|
+
"narHash": "sha256-E1bxHxNKfDoQUuvriG71+f+s/NT0qWkImXsYZNFFfCs=",
|
|
25
|
+
"owner": "nixos",
|
|
26
|
+
"repo": "nixpkgs",
|
|
27
|
+
"rev": "aca4d95fce4914b3892661bcb80b8087293536c6",
|
|
28
|
+
"type": "github"
|
|
29
|
+
},
|
|
30
|
+
"original": {
|
|
31
|
+
"owner": "nixos",
|
|
32
|
+
"ref": "nixos-unstable",
|
|
33
|
+
"repo": "nixpkgs",
|
|
34
|
+
"type": "github"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"root": {
|
|
38
|
+
"inputs": {
|
|
39
|
+
"flake-utils": "flake-utils",
|
|
40
|
+
"nixpkgs": "nixpkgs"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"systems": {
|
|
44
|
+
"locked": {
|
|
45
|
+
"lastModified": 1681028828,
|
|
46
|
+
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
|
47
|
+
"owner": "nix-systems",
|
|
48
|
+
"repo": "default",
|
|
49
|
+
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
|
50
|
+
"type": "github"
|
|
51
|
+
},
|
|
52
|
+
"original": {
|
|
53
|
+
"owner": "nix-systems",
|
|
54
|
+
"repo": "default",
|
|
55
|
+
"type": "github"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"root": "root",
|
|
60
|
+
"version": 7
|
|
61
|
+
}
|
data/flake.nix
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
inputs = {
|
|
3
|
+
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
|
4
|
+
|
|
5
|
+
flake-utils.url = "github:numtide/flake-utils";
|
|
6
|
+
flake-utils.inputs.nixpkgs.follows = "nixpkgs";
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
outputs = { self, nixpkgs, flake-utils }:
|
|
10
|
+
flake-utils.lib.eachDefaultSystem (system:
|
|
11
|
+
let
|
|
12
|
+
pkgs = nixpkgs.legacyPackages.${system};
|
|
13
|
+
in
|
|
14
|
+
{
|
|
15
|
+
devShells.default = pkgs.mkShell {
|
|
16
|
+
packages = with pkgs; [
|
|
17
|
+
ruby_3_4
|
|
18
|
+
bundler
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
shellHook = ''
|
|
22
|
+
export BUNDLE_PATH=".bundler"
|
|
23
|
+
export GEM_PATH=".bundler/ruby/3.4.0"
|
|
24
|
+
|
|
25
|
+
export PATH="$PWD/bin:$PATH"
|
|
26
|
+
export PATH=".bundler/ruby/3.4.0/bin:$PATH"
|
|
27
|
+
'';
|
|
28
|
+
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
packages.default = pkgs.hello;
|
|
32
|
+
}
|
|
33
|
+
);
|
|
34
|
+
}
|
data/lib/mirrorfile/cli.rb
CHANGED
|
@@ -21,7 +21,7 @@ module Mirrorfile
|
|
|
21
21
|
class CLI
|
|
22
22
|
# Available CLI commands
|
|
23
23
|
# @return [Array<String>] list of valid commands
|
|
24
|
-
COMMANDS = %w[init install update list help].freeze
|
|
24
|
+
COMMANDS = %w[init install update list git help].freeze
|
|
25
25
|
|
|
26
26
|
# Creates a new CLI instance.
|
|
27
27
|
#
|
|
@@ -48,15 +48,33 @@ module Mirrorfile
|
|
|
48
48
|
def call(args)
|
|
49
49
|
command = args.first
|
|
50
50
|
|
|
51
|
+
if legacy_command?(command) && Mirror.new.legacy?
|
|
52
|
+
return CLILegacy.new(stdout: @stdout, stderr: @stderr).call(args)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
dispatch(args)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Dispatches args to the appropriate command method.
|
|
59
|
+
#
|
|
60
|
+
# Separated from {#call} so that {CLILegacy} can dispatch
|
|
61
|
+
# without re-checking legacy state.
|
|
62
|
+
#
|
|
63
|
+
# @param args [Array<String>] command-line arguments
|
|
64
|
+
# @return [Integer] exit status code
|
|
65
|
+
def dispatch(args)
|
|
66
|
+
command = args.first
|
|
67
|
+
|
|
51
68
|
case command
|
|
52
|
-
when
|
|
53
|
-
when
|
|
54
|
-
when
|
|
55
|
-
when
|
|
56
|
-
when
|
|
57
|
-
when
|
|
58
|
-
when
|
|
59
|
-
|
|
69
|
+
when 'init' then init
|
|
70
|
+
when 'install' then install
|
|
71
|
+
when 'update' then update
|
|
72
|
+
when 'list' then list
|
|
73
|
+
when 'git' then git(args.drop(1))
|
|
74
|
+
when 'help' then help
|
|
75
|
+
when '-h', '--help' then help
|
|
76
|
+
when '-v', '--version' then version
|
|
77
|
+
else usage
|
|
60
78
|
end
|
|
61
79
|
|
|
62
80
|
0
|
|
@@ -65,12 +83,25 @@ module Mirrorfile
|
|
|
65
83
|
1
|
|
66
84
|
rescue StandardError => e
|
|
67
85
|
@stderr.puts "Error: #{e.message}"
|
|
68
|
-
@stderr.puts
|
|
86
|
+
@stderr.puts(e.backtrace.first(5).map { " #{_1}" }) if ENV['DEBUG']
|
|
69
87
|
1
|
|
70
88
|
end
|
|
71
89
|
|
|
72
90
|
private
|
|
73
91
|
|
|
92
|
+
# Returns whether the given command should use legacy mode
|
|
93
|
+
# when legacy mirrors are detected.
|
|
94
|
+
#
|
|
95
|
+
# Commands like init, help, and version flags are not affected
|
|
96
|
+
# by legacy state and always use the standard CLI.
|
|
97
|
+
#
|
|
98
|
+
# @param command [String, nil] the command name
|
|
99
|
+
# @return [Boolean]
|
|
100
|
+
# @api private
|
|
101
|
+
def legacy_command?(command)
|
|
102
|
+
%w[install update list git].include?(command)
|
|
103
|
+
end
|
|
104
|
+
|
|
74
105
|
# Executes the init command.
|
|
75
106
|
#
|
|
76
107
|
# @return [void]
|
|
@@ -84,9 +115,9 @@ module Mirrorfile
|
|
|
84
115
|
# @return [void]
|
|
85
116
|
# @api private
|
|
86
117
|
def install
|
|
87
|
-
@stdout.puts
|
|
118
|
+
@stdout.puts 'Installing mirrors...'
|
|
88
119
|
Mirror.new.install
|
|
89
|
-
@stdout.puts
|
|
120
|
+
@stdout.puts 'Done.'
|
|
90
121
|
end
|
|
91
122
|
|
|
92
123
|
# Executes the update command.
|
|
@@ -94,9 +125,9 @@ module Mirrorfile
|
|
|
94
125
|
# @return [void]
|
|
95
126
|
# @api private
|
|
96
127
|
def update
|
|
97
|
-
@stdout.puts
|
|
128
|
+
@stdout.puts 'Updating mirrors...'
|
|
98
129
|
Mirror.new.update
|
|
99
|
-
@stdout.puts
|
|
130
|
+
@stdout.puts 'Done.'
|
|
100
131
|
end
|
|
101
132
|
|
|
102
133
|
# Executes the list command.
|
|
@@ -106,7 +137,19 @@ module Mirrorfile
|
|
|
106
137
|
def list
|
|
107
138
|
entries = Mirror.new.list
|
|
108
139
|
|
|
109
|
-
entries.empty? ? @stdout.puts(
|
|
140
|
+
entries.empty? ? @stdout.puts('No mirrors defined.') : entries.each { @stdout.puts _1 }
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Executes a git command against a .git.mirror directory.
|
|
144
|
+
#
|
|
145
|
+
# Runs git with --git-dir=.git.mirror in the current working
|
|
146
|
+
# directory, passing through all additional arguments.
|
|
147
|
+
#
|
|
148
|
+
# @param args [Array<String>] arguments to pass to git
|
|
149
|
+
# @return [void]
|
|
150
|
+
# @api private
|
|
151
|
+
def git(args)
|
|
152
|
+
system('git', '--git-dir=.git.mirror', *args)
|
|
110
153
|
end
|
|
111
154
|
|
|
112
155
|
# Displays help information.
|
|
@@ -125,6 +168,7 @@ module Mirrorfile
|
|
|
125
168
|
install Clone repositories that don't exist locally
|
|
126
169
|
update Pull latest changes for existing repositories
|
|
127
170
|
list Show all defined mirrors
|
|
171
|
+
git Run git commands against a .git.mirror directory
|
|
128
172
|
help Show this help message
|
|
129
173
|
|
|
130
174
|
Options:
|
|
@@ -135,6 +179,7 @@ module Mirrorfile
|
|
|
135
179
|
$ mirror init
|
|
136
180
|
$ mirror install
|
|
137
181
|
$ mirror update
|
|
182
|
+
$ cd mirrors/rails && mirror git log --oneline
|
|
138
183
|
|
|
139
184
|
Mirrorfile syntax:
|
|
140
185
|
source "https://github.com"
|
|
@@ -155,7 +200,7 @@ module Mirrorfile
|
|
|
155
200
|
# @return [void]
|
|
156
201
|
# @api private
|
|
157
202
|
def version
|
|
158
|
-
@stdout.puts "mirrorfile #{Mirrorfile::VERSION}"
|
|
203
|
+
@stdout.puts "mirrorfile #{::Mirrorfile::VERSION}"
|
|
159
204
|
end
|
|
160
205
|
|
|
161
206
|
# Displays usage information for unknown commands.
|
|
@@ -167,10 +212,11 @@ module Mirrorfile
|
|
|
167
212
|
Usage: mirror <command>
|
|
168
213
|
|
|
169
214
|
Commands:
|
|
170
|
-
init Create Mirrorfile, .gitignore entry, and Zeitwerk initializer
|
|
215
|
+
init Create Mirrorfile, .gitignore entry, and Zeitwerk initializer (Rails only)
|
|
171
216
|
install Clone repositories that don't exist locally
|
|
172
217
|
update Pull latest changes for existing repositories
|
|
173
218
|
list Show all defined mirrors
|
|
219
|
+
git Run git commands against a .git.mirror directory
|
|
174
220
|
help Show detailed help
|
|
175
221
|
|
|
176
222
|
Run 'mirror help' for more information.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mirrorfile
|
|
4
|
+
# Legacy CLI for projects with old-style .git directories.
|
|
5
|
+
#
|
|
6
|
+
# This CLI is used automatically when mirrorfile detects that the
|
|
7
|
+
# mirrors directory contains repositories with .git instead of
|
|
8
|
+
# .git.mirror. It prints a deprecation warning and operates using
|
|
9
|
+
# the legacy .git directory name.
|
|
10
|
+
#
|
|
11
|
+
# @see CLI
|
|
12
|
+
# @since 1.0.0
|
|
13
|
+
class CLILegacy < CLI
|
|
14
|
+
LEGACY_WARNING = <<~WARN
|
|
15
|
+
[mirrorfile] WARNING: Your mirrors use legacy .git directories.
|
|
16
|
+
Upgrade with mirrorfile v0.1.1 (`mirror migrate-to-v1`), or remove
|
|
17
|
+
the mirrors/ directory and re-run `mirror install`.
|
|
18
|
+
WARN
|
|
19
|
+
|
|
20
|
+
# Overrides CLI#call to skip legacy detection and dispatch directly.
|
|
21
|
+
#
|
|
22
|
+
# @param args [Array<String>] command-line arguments
|
|
23
|
+
# @return [Integer] exit status code
|
|
24
|
+
def call(args)
|
|
25
|
+
dispatch(args)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
# Executes the install command in legacy mode.
|
|
31
|
+
#
|
|
32
|
+
# @return [void]
|
|
33
|
+
# @api private
|
|
34
|
+
def install
|
|
35
|
+
@stderr.puts LEGACY_WARNING
|
|
36
|
+
@stdout.puts 'Installing mirrors (legacy mode)...'
|
|
37
|
+
Mirror.new.install(legacy: true)
|
|
38
|
+
@stdout.puts 'Done.'
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Executes the update command in legacy mode.
|
|
42
|
+
#
|
|
43
|
+
# @return [void]
|
|
44
|
+
# @api private
|
|
45
|
+
def update
|
|
46
|
+
@stderr.puts LEGACY_WARNING
|
|
47
|
+
@stdout.puts 'Updating mirrors (legacy mode)...'
|
|
48
|
+
Mirror.new.update(legacy: true)
|
|
49
|
+
@stdout.puts 'Done.'
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Executes the list command with a legacy warning.
|
|
53
|
+
#
|
|
54
|
+
# @return [void]
|
|
55
|
+
# @api private
|
|
56
|
+
def list
|
|
57
|
+
@stderr.puts LEGACY_WARNING
|
|
58
|
+
super
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Executes a git command using the default .git directory.
|
|
62
|
+
#
|
|
63
|
+
# In legacy mode, mirrors have .git (not .git.mirror), so
|
|
64
|
+
# no --git-dir flag is needed.
|
|
65
|
+
#
|
|
66
|
+
# @param args [Array<String>] arguments to pass to git
|
|
67
|
+
# @return [void]
|
|
68
|
+
# @api private
|
|
69
|
+
def git(args)
|
|
70
|
+
@stderr.puts LEGACY_WARNING
|
|
71
|
+
system('git', *args)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
data/lib/mirrorfile/entry.rb
CHANGED
|
@@ -41,7 +41,13 @@ module Mirrorfile
|
|
|
41
41
|
# clone the repository once. If the local directory already exists,
|
|
42
42
|
# no action is taken.
|
|
43
43
|
#
|
|
44
|
+
# After cloning, the .git directory is renamed to the configured
|
|
45
|
+
# git_dir name (default: .git.mirror) so that the host project's
|
|
46
|
+
# git does not treat it as a nested repository.
|
|
47
|
+
#
|
|
44
48
|
# @param base_dir [Pathname] the base directory to clone into
|
|
49
|
+
# @param git_dir [String] the git directory name to use
|
|
50
|
+
# (default: ".git.mirror", use ".git" for legacy mode)
|
|
45
51
|
# @return [Boolean, nil] true if clone succeeded, false if failed,
|
|
46
52
|
# nil if already exists
|
|
47
53
|
#
|
|
@@ -49,9 +55,15 @@ module Mirrorfile
|
|
|
49
55
|
# entry.install(Pathname.new("mirrors"))
|
|
50
56
|
#
|
|
51
57
|
# @see #update
|
|
52
|
-
def install(base_dir)
|
|
58
|
+
def install(base_dir, git_dir: '.git.mirror')
|
|
53
59
|
dir = local_path(base_dir)
|
|
54
|
-
|
|
60
|
+
return if dir.exist?
|
|
61
|
+
|
|
62
|
+
return unless system('git', 'clone', url, dir.to_s)
|
|
63
|
+
|
|
64
|
+
return unless git_dir != '.git'
|
|
65
|
+
|
|
66
|
+
File.rename(dir.join('.git').to_s, dir.join(git_dir).to_s)
|
|
55
67
|
end
|
|
56
68
|
|
|
57
69
|
# Updates an existing repository by pulling the latest changes.
|
|
@@ -60,6 +72,8 @@ module Mirrorfile
|
|
|
60
72
|
# If the local directory doesn't exist, no action is taken.
|
|
61
73
|
#
|
|
62
74
|
# @param base_dir [Pathname] the base directory containing the clone
|
|
75
|
+
# @param git_dir [String] the git directory name to use
|
|
76
|
+
# (default: ".git.mirror", use ".git" for legacy mode)
|
|
63
77
|
# @return [Boolean, nil] true if pull succeeded, false if failed,
|
|
64
78
|
# nil if directory doesn't exist
|
|
65
79
|
#
|
|
@@ -67,9 +81,12 @@ module Mirrorfile
|
|
|
67
81
|
# entry.update(Pathname.new("mirrors"))
|
|
68
82
|
#
|
|
69
83
|
# @see #install
|
|
70
|
-
def update(base_dir)
|
|
84
|
+
def update(base_dir, git_dir: '.git.mirror')
|
|
71
85
|
dir = local_path(base_dir)
|
|
72
|
-
|
|
86
|
+
return unless dir.exist?
|
|
87
|
+
|
|
88
|
+
system('git', '--git-dir', dir.join(git_dir).to_s,
|
|
89
|
+
'--work-tree', dir.to_s, 'pull', '--ff-only')
|
|
73
90
|
end
|
|
74
91
|
|
|
75
92
|
# Returns a human-readable representation of the entry.
|
data/lib/mirrorfile/mirror.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
|
|
3
5
|
module Mirrorfile
|
|
4
6
|
# Orchestrates mirror operations: init, install, and update.
|
|
5
7
|
#
|
|
@@ -46,10 +48,10 @@ module Mirrorfile
|
|
|
46
48
|
# mirror = Mirrorfile::Mirror.new(root: "/path/to/project")
|
|
47
49
|
def initialize(root: Dir.pwd)
|
|
48
50
|
@root = Pathname.new(root)
|
|
49
|
-
@mirrors_dir = @root.join(
|
|
50
|
-
@gitignore_path = @root.join(
|
|
51
|
-
@mirrorfile_path = @root.join(
|
|
52
|
-
@initializer_path = @root.join(
|
|
51
|
+
@mirrors_dir = @root.join('mirrors')
|
|
52
|
+
@gitignore_path = @root.join('.gitignore')
|
|
53
|
+
@mirrorfile_path = @root.join('Mirrorfile')
|
|
54
|
+
@initializer_path = @root.join('config/initializers/mirrors.rb')
|
|
53
55
|
@mirrorfile = load_mirrorfile if @mirrorfile_path.exist?
|
|
54
56
|
end
|
|
55
57
|
|
|
@@ -66,10 +68,11 @@ module Mirrorfile
|
|
|
66
68
|
# mirror.install
|
|
67
69
|
#
|
|
68
70
|
# @see Entry#install
|
|
69
|
-
def install
|
|
71
|
+
def install(legacy: false)
|
|
70
72
|
ensure_mirrorfile!
|
|
71
73
|
mirrors_dir.mkpath
|
|
72
|
-
|
|
74
|
+
git_dir = legacy ? '.git' : '.git.mirror'
|
|
75
|
+
@mirrorfile.entries.each { _1.install(mirrors_dir, git_dir: git_dir) }
|
|
73
76
|
end
|
|
74
77
|
|
|
75
78
|
# Updates all existing local repositories.
|
|
@@ -86,9 +89,10 @@ module Mirrorfile
|
|
|
86
89
|
# mirror.update
|
|
87
90
|
#
|
|
88
91
|
# @see Entry#update
|
|
89
|
-
def update
|
|
92
|
+
def update(legacy: false)
|
|
90
93
|
ensure_mirrorfile!
|
|
91
|
-
|
|
94
|
+
git_dir = legacy ? '.git' : '.git.mirror'
|
|
95
|
+
@mirrorfile.entries.each { _1.update(mirrors_dir, git_dir: git_dir) }
|
|
92
96
|
end
|
|
93
97
|
|
|
94
98
|
# Initializes a new project with mirror support.
|
|
@@ -98,7 +102,7 @@ module Mirrorfile
|
|
|
98
102
|
#
|
|
99
103
|
# - Creates a Mirrorfile with example syntax
|
|
100
104
|
# - Adds /mirrors to .gitignore (creates file if needed)
|
|
101
|
-
# - Creates a Rails initializer for Zeitwerk autoloading
|
|
105
|
+
# - Creates a Rails initializer for Zeitwerk autoloading (Rails projects only)
|
|
102
106
|
#
|
|
103
107
|
# Existing files are not overwritten.
|
|
104
108
|
#
|
|
@@ -115,6 +119,7 @@ module Mirrorfile
|
|
|
115
119
|
def init
|
|
116
120
|
create_mirrorfile
|
|
117
121
|
setup_gitignore
|
|
122
|
+
setup_templates
|
|
118
123
|
setup_zeitwerk
|
|
119
124
|
puts "Initialized mirrors in #{root}"
|
|
120
125
|
end
|
|
@@ -131,6 +136,20 @@ module Mirrorfile
|
|
|
131
136
|
@mirrorfile.entries.to_a
|
|
132
137
|
end
|
|
133
138
|
|
|
139
|
+
# Returns whether any mirrors use legacy .git directories.
|
|
140
|
+
#
|
|
141
|
+
# A project is considered legacy if any subdirectory of mirrors/
|
|
142
|
+
# contains a .git directory without a .git.mirror sibling.
|
|
143
|
+
#
|
|
144
|
+
# @return [Boolean] true if legacy mirrors are detected
|
|
145
|
+
def legacy?
|
|
146
|
+
return false unless mirrors_dir.exist?
|
|
147
|
+
|
|
148
|
+
mirrors_dir.children.select(&:directory?).any? do |path|
|
|
149
|
+
path.join('.git').exist? && !path.join('.git.mirror').exist?
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
134
153
|
private
|
|
135
154
|
|
|
136
155
|
# Loads and parses the Mirrorfile.
|
|
@@ -195,10 +214,10 @@ module Mirrorfile
|
|
|
195
214
|
# @return [Integer, nil] bytes written or nil if already ignored
|
|
196
215
|
# @api private
|
|
197
216
|
def setup_gitignore
|
|
198
|
-
gitignore_path.exist? || gitignore_path.write(
|
|
217
|
+
gitignore_path.exist? || gitignore_path.write('')
|
|
199
218
|
|
|
200
219
|
lines = gitignore_path.readlines.map(&:chomp)
|
|
201
|
-
lines.include?(
|
|
220
|
+
lines.include?('/mirrors') || gitignore_path.write([*lines, '/mirrors'].join("\n") + "\n")
|
|
202
221
|
end
|
|
203
222
|
|
|
204
223
|
# Creates a Rails initializer for Zeitwerk autoloading.
|
|
@@ -211,6 +230,8 @@ module Mirrorfile
|
|
|
211
230
|
# @return [Integer, nil] bytes written or nil if file exists
|
|
212
231
|
# @api private
|
|
213
232
|
def setup_zeitwerk
|
|
233
|
+
return unless rails_project?
|
|
234
|
+
|
|
214
235
|
initializer_path.dirname.mkpath
|
|
215
236
|
initializer_path.exist? || initializer_path.write(<<~RUBY)
|
|
216
237
|
# frozen_string_literal: true
|
|
@@ -231,5 +252,34 @@ module Mirrorfile
|
|
|
231
252
|
end
|
|
232
253
|
RUBY
|
|
233
254
|
end
|
|
255
|
+
|
|
256
|
+
# Copies template files into the mirrors directory.
|
|
257
|
+
#
|
|
258
|
+
# Creates the mirrors directory if it doesn't exist, then copies
|
|
259
|
+
# envrc.template and README.md.template from the gem's
|
|
260
|
+
# templates directory. Existing files are not overwritten.
|
|
261
|
+
#
|
|
262
|
+
# @return [void]
|
|
263
|
+
# @api private
|
|
264
|
+
def setup_templates
|
|
265
|
+
mirrors_dir.mkpath
|
|
266
|
+
|
|
267
|
+
templates_dir = Pathname.new(__dir__).join('../../templates')
|
|
268
|
+
|
|
269
|
+
envrc_dest = mirrors_dir.join('.envrc')
|
|
270
|
+
envrc_dest.exist? || FileUtils.cp(templates_dir.join('envrc.template'), envrc_dest)
|
|
271
|
+
|
|
272
|
+
readme_dest = mirrors_dir.join('README.md')
|
|
273
|
+
readme_dest.exist? || FileUtils.cp(templates_dir.join('README.md.template'), readme_dest)
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# Determines if the current project is a Rails application by checking
|
|
277
|
+
# for the standard Rails application entrypoint.
|
|
278
|
+
#
|
|
279
|
+
# @return [Boolean] true if Rails project structure is detected
|
|
280
|
+
# @api private
|
|
281
|
+
def rails_project?
|
|
282
|
+
root.join('config/application.rb').exist?
|
|
283
|
+
end
|
|
234
284
|
end
|
|
235
285
|
end
|
data/lib/mirrorfile/version.rb
CHANGED
data/lib/mirrorfile.rb
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require 'pathname'
|
|
4
4
|
|
|
5
|
-
require_relative
|
|
6
|
-
require_relative
|
|
7
|
-
require_relative
|
|
8
|
-
require_relative
|
|
9
|
-
require_relative
|
|
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
|
+
require_relative 'mirrorfile/cli_legacy'
|
|
10
11
|
|
|
11
12
|
# Mirrorfile is a gem for managing local mirrors of git repositories.
|
|
12
13
|
#
|
|
@@ -49,14 +50,14 @@ module Mirrorfile
|
|
|
49
50
|
#
|
|
50
51
|
# @return [Pathname] path to the mirrors directory
|
|
51
52
|
def mirrors_dir
|
|
52
|
-
root.join(
|
|
53
|
+
root.join('mirrors')
|
|
53
54
|
end
|
|
54
55
|
|
|
55
56
|
# Returns the path to the Mirrorfile
|
|
56
57
|
#
|
|
57
58
|
# @return [Pathname] path to the Mirrorfile
|
|
58
59
|
def mirrorfile_path
|
|
59
|
-
root.join(
|
|
60
|
+
root.join('Mirrorfile')
|
|
60
61
|
end
|
|
61
62
|
end
|
|
62
63
|
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Mirrors
|
|
2
|
+
|
|
3
|
+
This directory contains mirrored git repositories managed by mirrorfile.
|
|
4
|
+
|
|
5
|
+
## Working with git in mirrored repos
|
|
6
|
+
|
|
7
|
+
The `.git` directory in each mirrored repository is renamed to `.git.mirror`
|
|
8
|
+
to prevent the parent project from detecting them as submodules.
|
|
9
|
+
|
|
10
|
+
To run git commands within a mirror, use:
|
|
11
|
+
|
|
12
|
+
mirror git <command>
|
|
13
|
+
|
|
14
|
+
For example:
|
|
15
|
+
|
|
16
|
+
mirror git status
|
|
17
|
+
mirror git log --oneline
|
|
18
|
+
|
|
19
|
+
Alternatively, if you have [direnv](https://direnv.net/) installed, `cd` into
|
|
20
|
+
a mirror directory and the `.envrc` file will automatically set
|
|
21
|
+
`GIT_DIR=.git.mirror`, allowing you to use `git` commands directly.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# vim: set filetype=bash :
|
|
2
|
+
#
|
|
3
|
+
# This file can be utilised if `direnv` is installed.
|
|
4
|
+
# Alternatively, run the following helper:
|
|
5
|
+
#
|
|
6
|
+
# $ mirror git <any-git-command>
|
|
7
|
+
#
|
|
8
|
+
# $ mirror git status
|
|
9
|
+
# $ mirror git add .
|
|
10
|
+
# $ mirror git commit -m "example commit message"
|
|
11
|
+
#
|
|
12
|
+
# Direnv related resources:
|
|
13
|
+
# Docs - https://direnv.net/
|
|
14
|
+
# Packages - https://github.com/direnv
|
|
15
|
+
# Git - https://github.com/direnv/direnv
|
|
16
|
+
#
|
|
17
|
+
# This is needed because mirrorfile renames the `.git` directory to `.git.mirror`.
|
|
18
|
+
# This is essential to avoid issues with nested git repos that .gitignore can't solve.
|
|
19
|
+
# An example would be wanting to run `git add --force ./mirrors/example-project/flake.nix`.
|
|
20
|
+
# Unfortunately this doesn't work without removing the directory from .gitignore, which then means
|
|
21
|
+
# you have a submodule in your repo.
|
|
22
|
+
# By renaming the `.git` directory you avoid the parent repo detecting the folder as a submodule.
|
|
23
|
+
|
|
24
|
+
export GIT_DIR=.git.mirror
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mirrorfile
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Nathan Kidd
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 1980-01-
|
|
10
|
+
date: 1980-01-01 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: minitest
|
|
@@ -77,19 +77,25 @@ executables:
|
|
|
77
77
|
extensions: []
|
|
78
78
|
extra_rdoc_files: []
|
|
79
79
|
files:
|
|
80
|
+
- ".envrc"
|
|
80
81
|
- ".yardopts"
|
|
81
82
|
- CHANGELOG.md
|
|
82
83
|
- LICENSE.txt
|
|
83
84
|
- README.md
|
|
84
85
|
- Rakefile
|
|
85
86
|
- exe/mirror
|
|
87
|
+
- flake.lock
|
|
88
|
+
- flake.nix
|
|
86
89
|
- lib/mirrorfile.rb
|
|
87
90
|
- lib/mirrorfile/cli.rb
|
|
91
|
+
- lib/mirrorfile/cli_legacy.rb
|
|
88
92
|
- lib/mirrorfile/entry.rb
|
|
89
93
|
- lib/mirrorfile/mirror.rb
|
|
90
94
|
- lib/mirrorfile/mirrorfile.rb
|
|
91
95
|
- lib/mirrorfile/version.rb
|
|
92
96
|
- mirrorfile.gemspec
|
|
97
|
+
- templates/README.md.template
|
|
98
|
+
- templates/envrc.template
|
|
93
99
|
homepage: https://github.com/n-at-han-k/mirrorfile
|
|
94
100
|
licenses:
|
|
95
101
|
- MIT
|
|
@@ -113,7 +119,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
113
119
|
- !ruby/object:Gem::Version
|
|
114
120
|
version: '0'
|
|
115
121
|
requirements: []
|
|
116
|
-
rubygems_version: 3.
|
|
122
|
+
rubygems_version: 3.7.2
|
|
117
123
|
specification_version: 4
|
|
118
124
|
summary: Manage local mirrors of git repositories
|
|
119
125
|
test_files: []
|