mirrorfile 0.1.0 → 0.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2b29787fe3ec7d699137a8bc8f02dbfafd0ac95693dece15bf7199c977d3aa0e
4
- data.tar.gz: df9541b78b01255029986e18f3144294f4e60b436b8d928c7e8ff8937177cadc
3
+ metadata.gz: de77b25cb4e899eb5e29e28c56864a7f592a917721333e7846f27143ce4e7fd1
4
+ data.tar.gz: e67318033257885299216cb91234ca51f53204224e6fd2a17d7e3cb86170b832
5
5
  SHA512:
6
- metadata.gz: b3fd0d4747c159d0cf957012a7ba997b0dfc062278ef5d3b0b0bd823e23e96df11d78ebd863a1dbfe91beabac0a8e1a207e84cdbca662c0d2fa70a2ef5706172
7
- data.tar.gz: b0fea1083d5ec020119d35d775d19545662223110306e0ac6ce87598c80bc190a952dcb4113ed7f056845b0074651097b17add026c7743f90cbdc23ac1b1895c
6
+ metadata.gz: 9f78685072b0bc631479c96d980a78a52f45e00d8ed9109937f9ead3a2de2e0fc1a48e5f85556630c7e7a22b5e160e1949b70c42d0513cbc91320c4626a33e16
7
+ data.tar.gz: f0c17c1fe5cfa8ac281bf61c0b0b7e0f1f9c518476444a49184087845ea355664f5f7f0613176710698455e34751212b3d6fc210fa84fd00dce9df74fba499e3
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
+ }
@@ -21,7 +21,12 @@ 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 migrate-to-v1 help].freeze
25
+
26
+ # Deprecation warning shown on every command except migrate-to-v1
27
+ DEPRECATION_WARNING = <<~WARN.freeze
28
+ [mirrorfile] WARNING: v#{VERSION} is deprecated. Run `mirror migrate-to-v1` to upgrade to v1.0.
29
+ WARN
25
30
 
26
31
  # Creates a new CLI instance.
27
32
  #
@@ -48,15 +53,18 @@ module Mirrorfile
48
53
  def call(args)
49
54
  command = args.first
50
55
 
56
+ @stderr.puts DEPRECATION_WARNING unless command == 'migrate-to-v1'
57
+
51
58
  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
59
+ when 'init' then init
60
+ when 'install' then install
61
+ when 'update' then update
62
+ when 'list' then list
63
+ when 'migrate-to-v1' then migrate_to_v1
64
+ when 'help' then help
65
+ when '-h', '--help' then help
66
+ when '-v', '--version' then version
67
+ else usage
60
68
  end
61
69
 
62
70
  0
@@ -65,7 +73,7 @@ module Mirrorfile
65
73
  1
66
74
  rescue StandardError => e
67
75
  @stderr.puts "Error: #{e.message}"
68
- @stderr.puts e.backtrace.first(5).map { " #{_1}" } if ENV["DEBUG"]
76
+ @stderr.puts(e.backtrace.first(5).map { " #{_1}" }) if ENV['DEBUG']
69
77
  1
70
78
  end
71
79
 
@@ -84,9 +92,9 @@ module Mirrorfile
84
92
  # @return [void]
85
93
  # @api private
86
94
  def install
87
- @stdout.puts "Installing mirrors..."
95
+ @stdout.puts 'Installing mirrors...'
88
96
  Mirror.new.install
89
- @stdout.puts "Done."
97
+ @stdout.puts 'Done.'
90
98
  end
91
99
 
92
100
  # Executes the update command.
@@ -94,9 +102,9 @@ module Mirrorfile
94
102
  # @return [void]
95
103
  # @api private
96
104
  def update
97
- @stdout.puts "Updating mirrors..."
105
+ @stdout.puts 'Updating mirrors...'
98
106
  Mirror.new.update
99
- @stdout.puts "Done."
107
+ @stdout.puts 'Done.'
100
108
  end
101
109
 
102
110
  # Executes the list command.
@@ -106,7 +114,15 @@ module Mirrorfile
106
114
  def list
107
115
  entries = Mirror.new.list
108
116
 
109
- entries.empty? ? @stdout.puts("No mirrors defined.") : entries.each { @stdout.puts _1 }
117
+ entries.empty? ? @stdout.puts('No mirrors defined.') : entries.each { @stdout.puts _1 }
118
+ end
119
+
120
+ # Executes the migrate-to-v1 command.
121
+ #
122
+ # @return [void]
123
+ # @api private
124
+ def migrate_to_v1
125
+ Mirror.new.migrate_to_v1
110
126
  end
111
127
 
112
128
  # Displays help information.
@@ -120,12 +136,13 @@ module Mirrorfile
120
136
  Usage: mirror <command>
121
137
 
122
138
  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
139
+ init Initialize project with Mirrorfile, .gitignore entry,
140
+ and Zeitwerk initializer for Rails projects
141
+ install Clone repositories that don't exist locally
142
+ update Pull latest changes for existing repositories
143
+ list Show all defined mirrors
144
+ migrate-to-v1 Upgrade to mirrorfile v1.0
145
+ help Show this help message
129
146
 
130
147
  Options:
131
148
  -h, --help Show this help message
@@ -155,7 +172,7 @@ module Mirrorfile
155
172
  # @return [void]
156
173
  # @api private
157
174
  def version
158
- @stdout.puts "mirrorfile #{Mirrorfile::VERSION}"
175
+ @stdout.puts "mirrorfile #{::Mirrorfile::VERSION}"
159
176
  end
160
177
 
161
178
  # Displays usage information for unknown commands.
@@ -167,11 +184,12 @@ module Mirrorfile
167
184
  Usage: mirror <command>
168
185
 
169
186
  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
187
+ init Create Mirrorfile, .gitignore entry, and Zeitwerk initializer (Rails only)
188
+ install Clone repositories that don't exist locally
189
+ update Pull latest changes for existing repositories
190
+ list Show all defined mirrors
191
+ migrate-to-v1 Upgrade to mirrorfile v1.0
192
+ help Show detailed help
175
193
 
176
194
  Run 'mirror help' for more information.
177
195
  USAGE
@@ -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("mirrors")
50
- @gitignore_path = @root.join(".gitignore")
51
- @mirrorfile_path = @root.join("Mirrorfile")
52
- @initializer_path = @root.join("config/initializers/mirrors.rb")
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
 
@@ -98,7 +100,7 @@ module Mirrorfile
98
100
  #
99
101
  # - Creates a Mirrorfile with example syntax
100
102
  # - Adds /mirrors to .gitignore (creates file if needed)
101
- # - Creates a Rails initializer for Zeitwerk autoloading
103
+ # - Creates a Rails initializer for Zeitwerk autoloading (Rails projects only)
102
104
  #
103
105
  # Existing files are not overwritten.
104
106
  #
@@ -131,6 +133,23 @@ module Mirrorfile
131
133
  @mirrorfile.entries.to_a
132
134
  end
133
135
 
136
+ # Migrates the project to mirrorfile v1.0.
137
+ #
138
+ # Performs the following steps:
139
+ # - Updates Gemfile version constraint to ~> 1.0 (if Gemfile exists)
140
+ # - Updates the system gem if installed
141
+ # - Installs template files (.envrc, README.md) into mirrors/
142
+ # - Renames .git directories to .git.mirror in existing mirrors
143
+ #
144
+ # @return [void]
145
+ def migrate_to_v1
146
+ update_gemfile
147
+ update_system_gem
148
+ install_templates
149
+ rename_git_dirs
150
+ puts 'Migration to v1.0 complete.'
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?("/mirrors") || gitignore_path.write([*lines, "/mirrors"].join("\n") + "\n")
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,97 @@ module Mirrorfile
231
252
  end
232
253
  RUBY
233
254
  end
255
+
256
+ # Updates the Gemfile to use mirrorfile ~> 1.0.
257
+ #
258
+ # Replaces any existing mirrorfile gem declaration with one
259
+ # constrained to ~> 1.0. Does nothing if Gemfile doesn't exist
260
+ # or doesn't reference mirrorfile.
261
+ #
262
+ # @return [void]
263
+ # @api private
264
+ def update_gemfile
265
+ gemfile = root.join('Gemfile')
266
+ return unless gemfile.exist?
267
+
268
+ content = gemfile.read
269
+ updated = content.gsub(/^(\s*gem\s+["']mirrorfile["']).*$/, '\1, "~> 1.0"')
270
+
271
+ return unless content != updated
272
+
273
+ gemfile.write(updated)
274
+ puts 'Updated Gemfile to mirrorfile ~> 1.0'
275
+ end
276
+
277
+ # Updates the system-installed mirrorfile gem.
278
+ #
279
+ # Checks if the gem is installed globally and runs gem update
280
+ # if so.
281
+ #
282
+ # @return [void]
283
+ # @api private
284
+ def update_system_gem
285
+ return if ENV['MIRRORFILE_SKIP_GEM_UPDATE']
286
+ return unless system('gem', 'list', '-i', 'mirrorfile', out: File::NULL, err: File::NULL)
287
+
288
+ puts 'Updating system gem...'
289
+ system('gem', 'update', 'mirrorfile')
290
+ end
291
+
292
+ # Copies template files into the mirrors directory.
293
+ #
294
+ # Creates the mirrors directory if needed, then copies
295
+ # envrc.template and README.md.template. Existing files
296
+ # are not overwritten.
297
+ #
298
+ # @return [void]
299
+ # @api private
300
+ def install_templates
301
+ mirrors_dir.mkpath
302
+
303
+ templates_dir = Pathname.new(__dir__).join('../../templates')
304
+
305
+ envrc_dest = mirrors_dir.join('.envrc')
306
+ unless envrc_dest.exist?
307
+ FileUtils.cp(templates_dir.join('envrc.template'), envrc_dest)
308
+ puts 'Created mirrors/.envrc'
309
+ end
310
+
311
+ readme_dest = mirrors_dir.join('README.md')
312
+ return if readme_dest.exist?
313
+
314
+ FileUtils.cp(templates_dir.join('README.md.template'), readme_dest)
315
+ puts 'Created mirrors/README.md'
316
+ end
317
+
318
+ # Renames .git directories to .git.mirror in existing mirrors.
319
+ #
320
+ # Scans all subdirectories of mirrors/ for .git directories
321
+ # and renames them. Skips entries that already have .git.mirror.
322
+ #
323
+ # @return [void]
324
+ # @api private
325
+ def rename_git_dirs
326
+ return unless mirrors_dir.exist?
327
+
328
+ mirrors_dir.children.select(&:directory?).each do |mirror_path|
329
+ git_dir = mirror_path.join('.git')
330
+ git_mirror_dir = mirror_path.join('.git.mirror')
331
+
332
+ if git_dir.exist? && !git_mirror_dir.exist?
333
+ File.rename(git_dir.to_s, git_mirror_dir.to_s)
334
+ puts "Renamed #{mirror_path.basename}/.git to .git.mirror"
335
+ end
336
+ end
337
+ end
338
+
339
+ # Determines if the current project is a Rails application by checking
340
+ # for the standard Rails application entrypoint.
341
+ #
342
+ # @return [Boolean] true if Rails project structure is detected
343
+ # @api private
344
+ def rails_project?
345
+ root.join('config/application.rb').exist?
346
+ end
234
347
  end
235
348
  end
@@ -3,5 +3,5 @@
3
3
  module Mirrorfile
4
4
  # Current version of the Mirrorfile gem
5
5
  # @return [String] the semantic version string
6
- VERSION = "0.1.0"
6
+ VERSION = '0.1.1'
7
7
  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: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nathan Kidd
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
10
+ date: 1980-01-01 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: minitest
@@ -77,12 +77,15 @@ 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
88
91
  - lib/mirrorfile/entry.rb
@@ -90,6 +93,8 @@ files:
90
93
  - lib/mirrorfile/mirrorfile.rb
91
94
  - lib/mirrorfile/version.rb
92
95
  - mirrorfile.gemspec
96
+ - templates/README.md.template
97
+ - templates/envrc.template
93
98
  homepage: https://github.com/n-at-han-k/mirrorfile
94
99
  licenses:
95
100
  - MIT
@@ -113,7 +118,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
113
118
  - !ruby/object:Gem::Version
114
119
  version: '0'
115
120
  requirements: []
116
- rubygems_version: 3.6.7
121
+ rubygems_version: 3.7.2
117
122
  specification_version: 4
118
123
  summary: Manage local mirrors of git repositories
119
124
  test_files: []