mirrorfile 0.1.1 → 1.0.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: de77b25cb4e899eb5e29e28c56864a7f592a917721333e7846f27143ce4e7fd1
4
- data.tar.gz: e67318033257885299216cb91234ca51f53204224e6fd2a17d7e3cb86170b832
3
+ metadata.gz: d8ab3635e4795e7250a284a158d0122062460a19db75d4eb00cd8754e0cedbb5
4
+ data.tar.gz: aec727ba372fea9a0cf78fcb38e321db66845f6e8d6e63618a62b6d2ba6c780b
5
5
  SHA512:
6
- metadata.gz: 9f78685072b0bc631479c96d980a78a52f45e00d8ed9109937f9ead3a2de2e0fc1a48e5f85556630c7e7a22b5e160e1949b70c42d0513cbc91320c4626a33e16
7
- data.tar.gz: f0c17c1fe5cfa8ac281bf61c0b0b7e0f1f9c518476444a49184087845ea355664f5f7f0613176710698455e34751212b3d6fc210fa84fd00dce9df74fba499e3
6
+ metadata.gz: 8deb4362ac6f4d89ba6dc3d8c7f5d2dc3887bf1e433ca2dba9151ffcd0905edfaddf62757f3600fae74f6eb53982c7f1ba7b35a4e8e55e1e62bb03d84f54e43c
7
+ data.tar.gz: 3c190b0887928dfedd7cd5cfae45fa1858f248417461e7e05411a63c93cd827844a18ef3c0462e1b3fe7120ad3487b9e0c77ab7fc61fa9bfb2fb37ea0f39654b
@@ -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 migrate-to-v1 help].freeze
30
25
 
31
26
  # Creates a new CLI instance.
32
27
  #
@@ -53,16 +48,32 @@ 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
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))
63
74
  when 'migrate-to-v1' then migrate_to_v1
64
75
  when 'help' then help
65
- when '-h', '--help' then help
76
+ when '-h', '--help' then help
66
77
  when '-v', '--version' then version
67
78
  else usage
68
79
  end
@@ -79,6 +90,19 @@ module Mirrorfile
79
90
 
80
91
  private
81
92
 
93
+ # Returns whether the given command should use legacy mode
94
+ # when legacy mirrors are detected.
95
+ #
96
+ # Commands like init, help, and version flags are not affected
97
+ # by legacy state and always use the standard CLI.
98
+ #
99
+ # @param command [String, nil] the command name
100
+ # @return [Boolean]
101
+ # @api private
102
+ def legacy_command?(command)
103
+ %w[install update list git].include?(command)
104
+ end
105
+
82
106
  # Executes the init command.
83
107
  #
84
108
  # @return [void]
@@ -117,6 +141,18 @@ module Mirrorfile
117
141
  entries.empty? ? @stdout.puts('No mirrors defined.') : entries.each { @stdout.puts _1 }
118
142
  end
119
143
 
144
+ # Executes a git command against a .git.mirror directory.
145
+ #
146
+ # Runs git with --git-dir=.git.mirror in the current working
147
+ # directory, passing through all additional arguments.
148
+ #
149
+ # @param args [Array<String>] arguments to pass to git
150
+ # @return [void]
151
+ # @api private
152
+ def git(args)
153
+ system('git', '--git-dir=.git.mirror', *args)
154
+ end
155
+
120
156
  # Executes the migrate-to-v1 command.
121
157
  #
122
158
  # @return [void]
@@ -141,7 +177,8 @@ module Mirrorfile
141
177
  install Clone repositories that don't exist locally
142
178
  update Pull latest changes for existing repositories
143
179
  list Show all defined mirrors
144
- migrate-to-v1 Upgrade to mirrorfile v1.0
180
+ git Run git commands against a .git.mirror directory
181
+ migrate-to-v1 Upgrade legacy mirrors to v1.0 format
145
182
  help Show this help message
146
183
 
147
184
  Options:
@@ -152,6 +189,7 @@ module Mirrorfile
152
189
  $ mirror init
153
190
  $ mirror install
154
191
  $ mirror update
192
+ $ cd mirrors/rails && mirror git log --oneline
155
193
 
156
194
  Mirrorfile syntax:
157
195
  source "https://github.com"
@@ -188,7 +226,8 @@ module Mirrorfile
188
226
  install Clone repositories that don't exist locally
189
227
  update Pull latest changes for existing repositories
190
228
  list Show all defined mirrors
191
- migrate-to-v1 Upgrade to mirrorfile v1.0
229
+ git Run git commands against a .git.mirror directory
230
+ migrate-to-v1 Upgrade legacy mirrors to v1.0 format
192
231
  help Show detailed help
193
232
 
194
233
  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
@@ -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
@@ -145,11 +148,25 @@ module Mirrorfile
145
148
  def migrate_to_v1
146
149
  update_gemfile
147
150
  update_system_gem
148
- install_templates
151
+ setup_templates
149
152
  rename_git_dirs
150
153
  puts 'Migration to v1.0 complete.'
151
154
  end
152
155
 
156
+ # Returns whether any mirrors use legacy .git directories.
157
+ #
158
+ # A project is considered legacy if any subdirectory of mirrors/
159
+ # contains a .git directory without a .git.mirror sibling.
160
+ #
161
+ # @return [Boolean] true if legacy mirrors are detected
162
+ def legacy?
163
+ return false unless mirrors_dir.exist?
164
+
165
+ mirrors_dir.children.select(&:directory?).any? do |path|
166
+ path.join('.git').exist? && !path.join('.git.mirror').exist?
167
+ end
168
+ end
169
+
153
170
  private
154
171
 
155
172
  # Loads and parses the Mirrorfile.
@@ -253,6 +270,26 @@ module Mirrorfile
253
270
  RUBY
254
271
  end
255
272
 
273
+ # Copies template files into the mirrors directory.
274
+ #
275
+ # Creates the mirrors directory if it doesn't exist, then copies
276
+ # envrc.template and README.md.template from the gem's
277
+ # templates directory. Existing files are not overwritten.
278
+ #
279
+ # @return [void]
280
+ # @api private
281
+ def setup_templates
282
+ mirrors_dir.mkpath
283
+
284
+ templates_dir = Pathname.new(__dir__).join('../../templates')
285
+
286
+ envrc_dest = mirrors_dir.join('.envrc')
287
+ envrc_dest.exist? || FileUtils.cp(templates_dir.join('envrc.template'), envrc_dest)
288
+
289
+ readme_dest = mirrors_dir.join('README.md')
290
+ readme_dest.exist? || FileUtils.cp(templates_dir.join('README.md.template'), readme_dest)
291
+ end
292
+
256
293
  # Updates the Gemfile to use mirrorfile ~> 1.0.
257
294
  #
258
295
  # Replaces any existing mirrorfile gem declaration with one
@@ -289,32 +326,6 @@ module Mirrorfile
289
326
  system('gem', 'update', 'mirrorfile')
290
327
  end
291
328
 
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
329
  # Renames .git directories to .git.mirror in existing mirrors.
319
330
  #
320
331
  # Scans all subdirectories of mirrors/ for .git directories
@@ -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.1'
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.1
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