diff --git a/lib/cocoapods-downloader/git.rb b/lib/cocoapods-downloader/git.rb index 58feb6a..5108524 100644 --- a/lib/cocoapods-downloader/git.rb +++ b/lib/cocoapods-downloader/git.rb @@ -21,22 +21,35 @@ def checkout_options end def self.preprocess_options(options) + return options if options[:commit] || options[:tag] return options unless options[:branch] - command = ['ls-remote', - options[:git], - options[:branch]] - output = Git.execute_command('git', command) - match = commit_from_ls_remote output, options[:branch] - - return options if match.nil? - - options[:commit] = match - options.delete(:branch) - + commit = commit_from_remote_ref(options[:git], options[:branch]) + return options if commit.nil? + options[:commit] = commit options end + # Matches a commit from branch or tag by git remote url. + # + # @note When there is a branch and tag with the same name, it will match + # the branch, since `refs/heads` is sorted before `refs/tags`. + # + # @param [String] url + # The remote url of the git repository. + # + # @param [String] ref + # The desired branch or tag to match a commit to. + # + # @return [String] commit hash string, or nil if no match found + def self.commit_from_remote_ref(url, ref) + return nil if url.nil? || ref.nil? + command = ['ls-remote', url, ref] + output = Git.execute_command('git', command) + match = commit_from_ls_remote(output, ref) + match + end + # Matches a commit from the branches reported by git ls-remote. # # @note When there is a branch and tag with the same name, it will match @@ -52,7 +65,7 @@ def self.preprocess_options(options) # def self.commit_from_ls_remote(output, branch_name) return nil if branch_name.nil? - encoded_branch_name = branch_name.dup.force_encoding(Encoding::ASCII_8BIT) + encoded_branch_name = branch_name.dup.force_encoding(Encoding::UTF_8) match = %r{([a-z0-9]*)\trefs\/(heads|tags)\/#{Regexp.quote(encoded_branch_name)}}.match(output) match[1] unless match.nil? end @@ -129,17 +142,26 @@ def update_submodules # def clone_arguments(force_head, shallow_clone) command = ['clone', url, target_path, '--template='] - - if shallow_clone && !options[:commit] - command += %w(--single-branch --depth 1) + if force_head + if shallow_clone && !options[:commit] + command += %w(--single-branch --depth 1) + end + return command end - - unless force_head - if tag_or_branch = options[:tag] || options[:branch] - command += ['--branch', tag_or_branch] + if shallow_clone + if (Git.options & options.keys).empty? + command += %w(--single-branch --depth 1) + elsif ref = options[:branch] || options[:tag] + command += %w(--single-branch) + commit = Git.commit_from_remote_ref(url, ref) + if !options[:commit] || (commit == options[:commit]) + command += %w(--depth 1) + end end end - + if tag_or_branch = options[:tag] || options[:branch] + command += ['--branch', tag_or_branch] + end command end diff --git a/spec/git_spec.rb b/spec/git_spec.rb index bafea00..fb2282c 100644 --- a/spec/git_spec.rb +++ b/spec/git_spec.rb @@ -116,6 +116,18 @@ def ensure_only_one_ref(folder) end end + def ensure_only_one_branch(folder) + Dir.chdir(folder) do + `git branch| wc -l`.strip.should == '1' + end + end + + def ensure_only_one_tag(folder) + Dir.chdir(folder) do + `git tag| wc -l`.strip.should == '1' + end + end + before do FileUtils.rm_rf('/tmp/git-submodule-repo') FileUtils.cp_r(fixture('git-submodule-repo'), '/tmp/') @@ -156,6 +168,59 @@ def ensure_only_one_ref(folder) `git rev-list HEAD`.chomp.should.include '407e385' end end + + # If 'preprocess_options' remove the options[:branch], it will fail here. + it 'clones a specific branch and preprocess options' do + options = { :git => fixture_url('git-repo'), :branch => 'topic_branch' } + options = Downloader.preprocess_options(options) + downloader = Downloader.for_target(tmp_folder, options) + downloader.download + + tmp_folder('README').read.strip.should == 'topic_branch' + ensure_only_one_ref(tmp_folder) + end + + it 'clones a specific branch and pointing commit' do + options = { :git => fixture_url('git-repo'), :branch => 'topic_branch', :commit => '4bd5b396b46445d897fd2eab4a56e868b9eb5fc4' } # rubocop:disable Metrics/LineLength + downloader = Downloader.for_target(tmp_folder, options) + downloader.download + + tmp_folder('README').read.strip.should == 'topic_branch' + ensure_only_one_ref(tmp_folder) + end + + it 'clones a specific tag and pointing commit' do + options = { :git => fixture_url('git-repo'), :tag => 'v1.0', :commit => '407e385d69d49a691eb6853f643317c61e417c83' } # rubocop:disable Metrics/LineLength + downloader = Downloader.for_target(tmp_folder, options) + downloader.download + + tmp_folder('README').read.strip.should == 'v1.0' + ensure_only_one_ref(tmp_folder) + end + + it 'clones a specific branch and non-pointing commit' do + options = { :git => fixture_url('git-repo'), :branch => 'topic_branch', :commit => '7ad3a6ccca379b6bd7e4ff9477448670d799c661' } # rubocop:disable Metrics/LineLength + downloader = Downloader.for_target(tmp_folder, options) + downloader.download + Dir.chdir(tmp_folder) do + `git rev-list HEAD`.chomp.should.include '7ad3a6ccca379b6bd7e4ff9477448670d799c661' + `git checkout topic_branch` + end + tmp_folder('README').read.strip.should == 'topic_branch' + ensure_only_one_branch(tmp_folder) + end + + it 'clones a specific tag and non-pointing commit' do + options = { :git => fixture_url('git-repo'), :tag => 'v1.0', :commit => '7ad3a6ccca379b6bd7e4ff9477448670d799c661' } # rubocop:disable Metrics/LineLength + downloader = Downloader.for_target(tmp_folder, options) + downloader.download + Dir.chdir(tmp_folder) do + `git rev-list HEAD`.chomp.should.include '7ad3a6ccca379b6bd7e4ff9477448670d799c661' + `git checkout v1.0` + end + tmp_folder('README').read.strip.should == 'v1.0' + ensure_only_one_tag(tmp_folder) + end end describe 'Robustness' do @@ -269,6 +334,15 @@ def ensure_only_one_ref(folder) Git.send(:commit_from_ls_remote, nil, 'test_branch').should.nil? Git.send(:commit_from_ls_remote, "123\trefs/heads/abc", nil).should.nil? end + + # If use Encoding::ASCII, it will fail here. + it 'finds commit for a branch with forced encoding' do + test_commit = '8edcd2fc53176ccb5fa7327f33b7dd733dbd3a06' + test_branch = '中文分支' + test_output = "#{test_commit}\trefs/heads/#{test_branch}" + result = Git.send(:commit_from_ls_remote, test_output, test_branch) + result.should == test_commit + end end describe '::preprocess_options' do @@ -289,6 +363,41 @@ def ensure_only_one_ref(folder) new_options = Downloader.preprocess_options(options) new_options[:branch].should == 'aaaa' end + + # If 'preprocess_options' remove the `return options if options[:commit] || options[:tag]`, it will fail here. + it 'don`t override commit' do + options = { :git => fixture_url('git-repo'), :branch => 'master', :commit => '7ad3a6c' } + new_options = Downloader.preprocess_options(options) + new_options[:commit].should.include '7ad3a6c' + end + end + + describe ':commit_from_remote_ref' do + it 'match a commit for a branch' do + commit = Git.send(:commit_from_remote_ref, fixture_url('git-repo'), 'topic_branch') + commit.should == '4bd5b396b46445d897fd2eab4a56e868b9eb5fc4' + end + + it 'match a commit for a tag' do + commit = Git.send(:commit_from_remote_ref, fixture_url('git-repo'), 'v1.0') + commit.should == '407e385d69d49a691eb6853f643317c61e417c83' + end + + it 'match a commit for invalid branch' do + commit = Git.send(:commit_from_remote_ref, fixture_url('git-repo'), 'abcde') + commit.should.nil? + end + + it 'match a commit for invalid tag' do + commit = Git.send(:commit_from_remote_ref, fixture_url('git-repo'), 'v1.abcde') + commit.should.nil? + end + + it 'handles nil inputs' do + Git.send(:commit_from_remote_ref, nil, 'test_branch').should.nil? + Git.send(:commit_from_remote_ref, fixture_url('git-repo'), nil).should.nil? + Git.send(:commit_from_remote_ref, nil, nil).should.nil? + end end end end