Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix bug of git shallow cloning and better performance when cloning #122

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 42 additions & 20 deletions lib/cocoapods-downloader/git.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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

Expand Down
109 changes: 109 additions & 0 deletions spec/git_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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/')
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down