From aaff7b36c4cecfc40d55cfec696a5a818d896d56 Mon Sep 17 00:00:00 2001 From: Timothy Sutton Date: Mon, 10 Nov 2025 06:37:58 -0500 Subject: [PATCH] Add --sparse-paths CLI option to allow sparse checkouts --- .rubocop.yml | 6 +++++ README.md | 21 ++++++++++----- lib/git-fastclone.rb | 52 +++++++++++++++++++++++++++++++++--- lib/git-fastclone/version.rb | 2 +- 4 files changed, 69 insertions(+), 12 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 2a10a07..7e6c783 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -24,3 +24,9 @@ Metrics/MethodLength: Metrics/BlockLength: Exclude: - 'spec/**/*' + +Metrics/CyclomaticComplexity: + Max: 15 + +Metrics/PerceivedComplexity: + Max: 15 diff --git a/README.md b/README.md index d2d33a3..f18b884 100644 --- a/README.md +++ b/README.md @@ -14,13 +14,13 @@ Why fastclone? Doing lots of repeated checkouts on a specific machine? | Repository | 1st Fastclone | 2nd Fastclone | git clone | cp -R | -| -----------|---------------|---------------|-----------|-------| -| angular.js | 8s | 3s | 6s | 0.5s | -| bootstrap | 26s | 3s | 11s | 0.2s | -| gradle | 25s | 9s | 19s | 6.2s | -| linux | 4m 53s | 1m 6s | 3m 51s | 29s | -| react.js | 18s | 3s | 8s | 0.5s | -| tensorflow | 19s | 4s | 8s | 1.5s | +| ---------- | ------------- | ------------- | --------- | ----- | +| angular.js | 8s | 3s | 6s | 0.5s | +| bootstrap | 26s | 3s | 11s | 0.2s | +| gradle | 25s | 9s | 19s | 6.2s | +| linux | 4m 53s | 1m 6s | 3m 51s | 29s | +| react.js | 18s | 3s | 8s | 0.5s | +| tensorflow | 19s | 4s | 8s | 1.5s | Above times captured using `time` without verbose mode. @@ -51,6 +51,8 @@ Usage --lock-timeout N Timeout in seconds to acquire a lock on any reference repo. Default is 0 which waits indefinitely. --pre-clone-hook command An optional command that should be invoked before cloning mirror repo + --sparse-paths PATHS Comma-separated list of paths for sparse checkout. + Enables sparse checkout mode using git sparse-checkout. Change the default `REFERENCE_REPO_DIR` environment variable if necessary. @@ -66,6 +68,11 @@ The hook is invoked with given arguments: 1. path to the repo mirror location 1. attempt number, 0-indexed +Sparse checkout support +----------------------- + +In passing `--sparse-paths`, git-fastclone will instead perform a sparse checkout, where the passed list of paths will be set up as patterns. This can be useful if you're interested only in a subset of paths in the repository. + How to test? ------------ Manual testing: diff --git a/lib/git-fastclone.rb b/lib/git-fastclone.rb index b45a0a9..876f53a 100644 --- a/lib/git-fastclone.rb +++ b/lib/git-fastclone.rb @@ -85,7 +85,7 @@ class Runner attr_accessor :reference_dir, :prefetch_submodules, :reference_updated, :reference_mutex, :options, :abs_clone_path, :using_local_repo, :verbose, :print_git_errors, :color, - :flock_timeout_secs + :flock_timeout_secs, :sparse_paths def initialize # Prefetch reference repos for submodules we've seen before @@ -115,6 +115,8 @@ def initialize self.color = false self.flock_timeout_secs = 0 + + self.sparse_paths = nil end def run @@ -172,6 +174,12 @@ def parse_options 'No-op when a file is missing') do |script_file| options[:pre_clone_hook] = script_file end + + opts.on('--sparse-paths PATHS', + 'Comma-separated list of paths for sparse checkout.', + 'Enables sparse checkout mode using git sparse-checkout.') do |paths| + self.sparse_paths = paths.split(',').map(&:strip) + end end.parse! end @@ -199,6 +207,16 @@ def parse_inputs raise msg end + # Validate that --branch is specified when using --sparse-paths + if sparse_paths && !options[:branch] + msg = "Error: --branch is required when using --sparse-paths\n" \ + "Sparse checkouts need an explicit branch/revision to checkout.\n" \ + 'Usage: git-fastclone --sparse-paths --branch ' + raise msg.red if color + + raise msg + end + self.reference_dir = ENV['REFERENCE_REPO_DIR'] || DEFAULT_REFERENCE_REPO_DIR FileUtils.mkdir_p(reference_dir) @@ -234,13 +252,23 @@ def clone(url, rev, src_dir, config) clear_clone_dest_if_needed(attempt_number, clone_dest) clone_commands = ['git', 'clone', verbose ? '--verbose' : '--quiet'] - clone_commands << '--reference' << mirror.to_s << url.to_s << clone_dest + # For sparse checkouts, clone directly from the local mirror and skip the actual checkout process + # For normal clones, use --reference and clone from the remote URL + if sparse_paths + clone_commands.push('--no-checkout') + clone_commands << mirror.to_s << clone_dest + else + clone_commands << '--reference' << mirror.to_s << url.to_s << clone_dest + end clone_commands << '--config' << config.to_s unless config.nil? fail_on_error(*clone_commands, quiet: !verbose, print_on_failure: print_git_errors) + + # Configure sparse checkout if enabled + perform_sparse_checkout(clone_dest, rev) if sparse_paths end - # Only checkout if we're changing branches to a non-default branch - if rev + # Only checkout if we're changing branches to a non-default branch (for non-sparse clones) + if !sparse_paths && rev fail_on_error('git', 'checkout', '--quiet', rev.to_s, quiet: !verbose, print_on_failure: print_git_errors, chdir: File.join(abs_clone_path, src_dir)) @@ -258,6 +286,22 @@ def clone(url, rev, src_dir, config) end end + def perform_sparse_checkout(clone_dest, rev) + puts 'Configuring sparse checkout...' if verbose + + # Initialize sparse checkout with cone mode + fail_on_error('git', 'sparse-checkout', 'init', '--cone', + quiet: !verbose, print_on_failure: print_git_errors, chdir: clone_dest) + + # Set the sparse paths + fail_on_error('git', 'sparse-checkout', 'set', *sparse_paths, + quiet: !verbose, print_on_failure: print_git_errors, chdir: clone_dest) + + # Checkout the specified branch/revision + fail_on_error('git', 'checkout', '--quiet', rev.to_s, + quiet: !verbose, print_on_failure: print_git_errors, chdir: clone_dest) + end + def update_submodules(pwd, url) return unless File.exist?(File.join(abs_clone_path, pwd, '.gitmodules')) diff --git a/lib/git-fastclone/version.rb b/lib/git-fastclone/version.rb index 5f10e66..eaba986 100644 --- a/lib/git-fastclone/version.rb +++ b/lib/git-fastclone/version.rb @@ -2,5 +2,5 @@ # Version string for git-fastclone module GitFastCloneVersion - VERSION = '1.5.1' + VERSION = '1.6.0' end