mirrorfile 0.1.1 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: de77b25cb4e899eb5e29e28c56864a7f592a917721333e7846f27143ce4e7fd1
4
- data.tar.gz: e67318033257885299216cb91234ca51f53204224e6fd2a17d7e3cb86170b832
3
+ metadata.gz: 513175ce2e6de6e4e0a0a810d844ce5a280fd3e899cf490b289249054a8c37f9
4
+ data.tar.gz: 9a8ca82bdd368ef020c97fbf156aba1e5d3cad6860e5eef4e02226165c167277
5
5
  SHA512:
6
- metadata.gz: 9f78685072b0bc631479c96d980a78a52f45e00d8ed9109937f9ead3a2de2e0fc1a48e5f85556630c7e7a22b5e160e1949b70c42d0513cbc91320c4626a33e16
7
- data.tar.gz: f0c17c1fe5cfa8ac281bf61c0b0b7e0f1f9c518476444a49184087845ea355664f5f7f0613176710698455e34751212b3d6fc210fa84fd00dce9df74fba499e3
6
+ metadata.gz: 0046b1bde5286137d682244bf597e8f72a0d43a206c6013b4dc470b08b9dead8ebbe0d7aa390c63437330b9ccf15c93dbefc99ca8e5a0bd346606166232f38b9
7
+ data.tar.gz: fa70c541f5cdaa6e17c2a654bf07a094cb2037a405409a9fda00fe8f7d725331cb6b66737915fe1df8b39a2f7c8bdbe4bcd887a1a536fd7c1420fa8bcbbdd971
@@ -21,12 +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 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
24
+ COMMANDS = %w[init install update list git help].freeze
30
25
 
31
26
  # Creates a new CLI instance.
32
27
  #
@@ -53,16 +48,31 @@ module Mirrorfile
53
48
  def call(args)
54
49
  command = args.first
55
50
 
56
- @stderr.puts DEPRECATION_WARNING unless command == 'migrate-to-v1'
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
57
67
 
58
68
  case command
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
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
66
76
  when '-v', '--version' then version
67
77
  else usage
68
78
  end
@@ -79,6 +89,19 @@ module Mirrorfile
79
89
 
80
90
  private
81
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
+
82
105
  # Executes the init command.
83
106
  #
84
107
  # @return [void]
@@ -117,12 +140,16 @@ module Mirrorfile
117
140
  entries.empty? ? @stdout.puts('No mirrors defined.') : entries.each { @stdout.puts _1 }
118
141
  end
119
142
 
120
- # Executes the migrate-to-v1 command.
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.
121
147
  #
148
+ # @param args [Array<String>] arguments to pass to git
122
149
  # @return [void]
123
150
  # @api private
124
- def migrate_to_v1
125
- Mirror.new.migrate_to_v1
151
+ def git(args)
152
+ system('git', '--git-dir=.git.mirror', *args)
126
153
  end
127
154
 
128
155
  # Displays help information.
@@ -136,13 +163,13 @@ module Mirrorfile
136
163
  Usage: mirror <command>
137
164
 
138
165
  Commands:
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
166
+ init Initialize project with Mirrorfile, .gitignore entry,
167
+ and Zeitwerk initializer for Rails projects
168
+ install Clone repositories that don't exist locally
169
+ update Pull latest changes for existing repositories
170
+ list Show all defined mirrors
171
+ git Run git commands against a .git.mirror directory
172
+ help Show this help message
146
173
 
147
174
  Options:
148
175
  -h, --help Show this help message
@@ -152,6 +179,7 @@ module Mirrorfile
152
179
  $ mirror init
153
180
  $ mirror install
154
181
  $ mirror update
182
+ $ cd mirrors/rails && mirror git log --oneline
155
183
 
156
184
  Mirrorfile syntax:
157
185
  source "https://github.com"
@@ -184,12 +212,12 @@ module Mirrorfile
184
212
  Usage: mirror <command>
185
213
 
186
214
  Commands:
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
215
+ init Create Mirrorfile, .gitignore entry, and Zeitwerk initializer (Rails only)
216
+ install Clone repositories that don't exist locally
217
+ update Pull latest changes for existing repositories
218
+ list Show all defined mirrors
219
+ git Run git commands against a .git.mirror directory
220
+ help Show detailed help
193
221
 
194
222
  Run 'mirror help' for more information.
195
223
  USAGE
@@ -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
@@ -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
- system("git", "clone", url, dir.to_s) unless dir.exist?
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
- system("git", "-C", dir.to_s, "pull", "--ff-only") if dir.exist?
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.
@@ -68,10 +68,11 @@ module Mirrorfile
68
68
  # mirror.install
69
69
  #
70
70
  # @see Entry#install
71
- def install
71
+ def install(legacy: false)
72
72
  ensure_mirrorfile!
73
73
  mirrors_dir.mkpath
74
- @mirrorfile.entries.each { _1.install(mirrors_dir) }
74
+ git_dir = legacy ? '.git' : '.git.mirror'
75
+ @mirrorfile.entries.each { _1.install(mirrors_dir, git_dir: git_dir) }
75
76
  end
76
77
 
77
78
  # Updates all existing local repositories.
@@ -88,9 +89,10 @@ module Mirrorfile
88
89
  # mirror.update
89
90
  #
90
91
  # @see Entry#update
91
- def update
92
+ def update(legacy: false)
92
93
  ensure_mirrorfile!
93
- @mirrorfile.entries.each { _1.update(mirrors_dir) }
94
+ git_dir = legacy ? '.git' : '.git.mirror'
95
+ @mirrorfile.entries.each { _1.update(mirrors_dir, git_dir: git_dir) }
94
96
  end
95
97
 
96
98
  # Initializes a new project with mirror support.
@@ -117,6 +119,7 @@ module Mirrorfile
117
119
  def init
118
120
  create_mirrorfile
119
121
  setup_gitignore
122
+ setup_templates
120
123
  setup_zeitwerk
121
124
  puts "Initialized mirrors in #{root}"
122
125
  end
@@ -133,21 +136,18 @@ module Mirrorfile
133
136
  @mirrorfile.entries.to_a
134
137
  end
135
138
 
136
- # Migrates the project to mirrorfile v1.0.
139
+ # Returns whether any mirrors use legacy .git directories.
137
140
  #
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
141
+ # A project is considered legacy if any subdirectory of mirrors/
142
+ # contains a .git directory without a .git.mirror sibling.
143
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.'
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
151
  end
152
152
 
153
153
  private
@@ -253,87 +253,24 @@ module Mirrorfile
253
253
  RUBY
254
254
  end
255
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
256
  # Copies template files into the mirrors directory.
293
257
  #
294
- # Creates the mirrors directory if needed, then copies
295
- # envrc.template and README.md.template. Existing files
296
- # are not overwritten.
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.
297
261
  #
298
262
  # @return [void]
299
263
  # @api private
300
- def install_templates
264
+ def setup_templates
301
265
  mirrors_dir.mkpath
302
266
 
303
267
  templates_dir = Pathname.new(__dir__).join('../../templates')
304
268
 
305
269
  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
270
+ envrc_dest.exist? || FileUtils.cp(templates_dir.join('envrc.template'), envrc_dest)
310
271
 
311
272
  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
273
+ readme_dest.exist? || FileUtils.cp(templates_dir.join('README.md.template'), readme_dest)
337
274
  end
338
275
 
339
276
  # Determines if the current project is a Rails application by checking
@@ -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.1'
6
+ VERSION = '1.0.0'
7
7
  end
data/lib/mirrorfile.rb CHANGED
@@ -1,12 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "pathname"
3
+ require 'pathname'
4
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"
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("mirrors")
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("Mirrorfile")
60
+ root.join('Mirrorfile')
60
61
  end
61
62
  end
62
63
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mirrorfile
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nathan Kidd
@@ -88,6 +88,7 @@ files:
88
88
  - flake.nix
89
89
  - lib/mirrorfile.rb
90
90
  - lib/mirrorfile/cli.rb
91
+ - lib/mirrorfile/cli_legacy.rb
91
92
  - lib/mirrorfile/entry.rb
92
93
  - lib/mirrorfile/mirror.rb
93
94
  - lib/mirrorfile/mirrorfile.rb