Commit 6fca7f0a authored by Rémy Coutable's avatar Rémy Coutable

Merge branch 'squash-default' into 'master'

Squash default

See merge request !1272
parents d4437eee f728dd24
...@@ -23,13 +23,15 @@ module MergeRequests ...@@ -23,13 +23,15 @@ module MergeRequests
end end
run_git_command( run_git_command(
%W(worktree add #{tree_path} #{merge_request.target_branch} --detach), %W(worktree add --no-checkout --detach #{tree_path}),
repository.path_to_repo, repository.path_to_repo,
git_env, git_env,
'add worktree for squash' 'add worktree for squash'
) )
diff = git_command(%W(diff --binary #{merge_request.diff_start_sha}...#{merge_request.diff_head_sha})) configure_sparse_checkout
diff = git_command(%W(diff --binary #{diff_range}))
apply = git_command(%w(apply --index)) apply = git_command(%w(apply --index))
run_command( run_command(
...@@ -40,9 +42,12 @@ module MergeRequests ...@@ -40,9 +42,12 @@ module MergeRequests
) )
run_git_command( run_git_command(
%W(commit -C #{merge_request.diff_head_sha}), %W(commit --no-verify -m #{merge_request.title}),
tree_path, tree_path,
git_env.merge('GIT_COMMITTER_NAME' => current_user.name, 'GIT_COMMITTER_EMAIL' => current_user.email), git_env.merge('GIT_COMMITTER_NAME' => current_user.name,
'GIT_COMMITTER_EMAIL' => current_user.email,
'GIT_AUTHOR_NAME' => merge_request.author.name,
'GIT_AUTHOR_EMAIL' => merge_request.author.email),
'commit squashed changes' 'commit squashed changes'
) )
...@@ -62,10 +67,64 @@ module MergeRequests ...@@ -62,10 +67,64 @@ module MergeRequests
false false
ensure ensure
clean_dir clean_dir
clean_worktree
end end
def tree_path def tree_path
@tree_path ||= merge_request.squash_dir_path @tree_path ||= merge_request.squash_dir_path
end end
def diff_range
@diff_range ||= "#{merge_request.diff_start_sha}...#{merge_request.diff_head_sha}"
end
def worktree_path
@worktree_path ||= File.join(repository.path_to_repo, 'worktrees', merge_request.id.to_s)
end
def clean_worktree
FileUtils.rm_rf(worktree_path) if File.exist?(worktree_path)
end
# Adding a worktree means checking out the repository. For large repos, this
# can be very expensive, so set up sparse checkout for the worktree to only
# check out the files we're interested in.
#
def configure_sparse_checkout
run_git_command(
%w(config core.sparseCheckout true),
repository.path_to_repo,
git_env,
'configure sparse checkout'
)
# Get the same diff we'll apply, excluding added files. (We can't check
# out files on the target branch if they don't exist yet!)
#
diff_files = run_git_command(
%W(diff --name-only --diff-filter=a --binary #{diff_range}),
repository.path_to_repo,
git_env,
'get files in diff'
)
# If only new files are introduced by this MR, then our sparse checkout
# doesn't need to have any files at all.
#
unless diff_files.empty?
worktree_info = File.join(worktree_path, 'info')
FileUtils.mkdir_p(worktree_info) unless File.directory?(worktree_info)
File.write(File.join(worktree_info, 'sparse-checkout'), diff_files)
end
run_git_command(
%W(checkout --detach #{merge_request.target_branch}),
tree_path,
git_env,
'check out target branch'
)
end
end end
end end
...@@ -41,7 +41,7 @@ module MergeRequests ...@@ -41,7 +41,7 @@ module MergeRequests
end end
def log_error(message) def log_error(message)
Gitlab::GitLogger.error(message) Gitlab::GitLogger.error("#{self.class.name} error (#{merge_request.to_reference(full: true)}): #{message}")
end end
def clean_dir def clean_dir
......
...@@ -35,8 +35,8 @@ This can then be overridden at the time of accepting the merge request: ...@@ -35,8 +35,8 @@ This can then be overridden at the time of accepting the merge request:
The squashed commit has the following metadata: The squashed commit has the following metadata:
* Message: taken from the last commit in the source branch. * Message: the title of the merge request.
* Author: taken from the last commit in the source branch. * Author: the author of the merge request.
* Committer: the user who initiated the squash. * Committer: the user who initiated the squash.
## Squashing and [fast-forward merge][ff-merge] ## Squashing and [fast-forward merge][ff-merge]
......
...@@ -12,11 +12,10 @@ feature 'Squashing merge requests', js: true, feature: true do ...@@ -12,11 +12,10 @@ feature 'Squashing merge requests', js: true, feature: true do
shared_examples 'squash' do shared_examples 'squash' do
it 'squashes the commits into a single commit, and adds a merge commit' do it 'squashes the commits into a single commit, and adds a merge commit' do
latest_master_commits = project.repository.commits_between(original_head.sha, 'master').map(&:raw) latest_master_commits = project.repository.commits_between(original_head.sha, 'master').map(&:raw)
last_mr_commit = project.repository.commit(source_branch)
squash_commit = an_object_having_attributes(sha: a_string_matching(/\h{40}/), squash_commit = an_object_having_attributes(sha: a_string_matching(/\h{40}/),
message: "#{last_mr_commit.message}\n", message: "Csv\n",
author_name: last_mr_commit.author_name, author_name: user.name,
committer_name: user.name) committer_name: user.name)
merge_commit = an_object_having_attributes(sha: a_string_matching(/\h{40}/), merge_commit = an_object_having_attributes(sha: a_string_matching(/\h{40}/),
......
...@@ -5,34 +5,25 @@ describe MergeRequests::SquashService do ...@@ -5,34 +5,25 @@ describe MergeRequests::SquashService do
let(:user) { project.owner } let(:user) { project.owner }
let(:project) { create(:project) } let(:project) { create(:project) }
let(:merge_request) do
create(:merge_request,
source_branch: 'video', source_project: project,
target_branch: 'master', target_project: project)
end
let(:merge_request_with_one_commit) do let(:merge_request_with_one_commit) do
create(:merge_request, create(:merge_request,
source_branch: 'feature.custom-highlighting', source_project: project, source_branch: 'feature.custom-highlighting', source_project: project,
target_branch: 'master', target_project: project) target_branch: 'master', target_project: project)
end end
describe '#execute' do let(:merge_request_with_only_new_files) do
context 'when there is only one commit in the merge request' do create(:merge_request,
it 'returns that commit SHA' do source_branch: 'video', source_project: project,
result = service.execute(merge_request_with_one_commit) target_branch: 'master', target_project: project)
expect(result).to match(status: :success, squash_sha: merge_request_with_one_commit.diff_head_sha)
end end
it 'does not perform any git actions' do let(:merge_request_with_large_files) do
expect(service).not_to receive(:run_git_command) create(:merge_request,
source_branch: 'squash-large-files', source_project: project,
service.execute(merge_request_with_one_commit) target_branch: 'master', target_project: project)
end
end end
context 'when the squash succeeds' do shared_examples 'the squash succeeds' do
it 'returns the squashed commit SHA' do it 'returns the squashed commit SHA' do
result = service.execute(merge_request) result = service.execute(merge_request)
...@@ -54,14 +45,12 @@ describe MergeRequests::SquashService do ...@@ -54,14 +45,12 @@ describe MergeRequests::SquashService do
let(:squash_sha) { service.execute(merge_request)[:squash_sha] } let(:squash_sha) { service.execute(merge_request)[:squash_sha] }
let(:squash_commit) { project.repository.commit(squash_sha) } let(:squash_commit) { project.repository.commit(squash_sha) }
it 'copies the author info and message from the last commit in the source branch' do it 'copies the author info and message from the merge request' do
diff_head_commit = merge_request.diff_head_commit expect(squash_commit.author_name).to eq(merge_request.author.name)
expect(squash_commit.author_email).to eq(merge_request.author.email)
expect(squash_commit.author_name).to eq(diff_head_commit.author_name)
expect(squash_commit.author_email).to eq(diff_head_commit.author_email)
# The commit message on the 'real' commit doesn't have a trailing newline # Commit messages have a trailing newline, but titles don't.
expect(squash_commit.message.chomp).to eq(diff_head_commit.message) expect(squash_commit.message.chomp).to eq(merge_request.title)
end end
it 'sets the current user as the committer' do it 'sets the current user as the committer' do
...@@ -74,14 +63,44 @@ describe MergeRequests::SquashService do ...@@ -74,14 +63,44 @@ describe MergeRequests::SquashService do
mr_diff = rugged.diff(merge_request.diff_base_sha, merge_request.diff_head_sha) mr_diff = rugged.diff(merge_request.diff_base_sha, merge_request.diff_head_sha)
squash_diff = rugged.diff(merge_request.diff_start_sha, squash_sha) squash_diff = rugged.diff(merge_request.diff_start_sha, squash_sha)
expect(squash_diff.patch).to eq(mr_diff.patch) expect(squash_diff.patch.length).to eq(mr_diff.patch.length)
expect(squash_commit.sha).not_to eq(merge_request.diff_head_sha) expect(squash_commit.sha).not_to eq(merge_request.diff_head_sha)
end end
end end
end end
describe '#execute' do
context 'when there is only one commit in the merge request' do
it 'returns that commit SHA' do
result = service.execute(merge_request_with_one_commit)
expect(result).to match(status: :success, squash_sha: merge_request_with_one_commit.diff_head_sha)
end
it 'does not perform any git actions' do
expect(service).not_to receive(:run_git_command)
service.execute(merge_request_with_one_commit)
end
end
context 'when squashing only new files' do
let(:merge_request) { merge_request_with_only_new_files }
include_examples 'the squash succeeds'
end
context 'when squashing with files too large to display' do
let(:merge_request) { merge_request_with_large_files }
include_examples 'the squash succeeds'
end
stages = { stages = {
'add worktree for squash' => 'worktree', 'add worktree for squash' => 'worktree',
'configure sparse checkout' => 'config',
'get files in diff' => 'diff --name-only',
'check out target branch' => 'checkout',
'apply patch' => 'diff --binary', 'apply patch' => 'diff --binary',
'commit squashed changes' => 'commit', 'commit squashed changes' => 'commit',
'get SHA of squashed commit' => 'rev-parse' 'get SHA of squashed commit' => 'rev-parse'
...@@ -89,13 +108,14 @@ describe MergeRequests::SquashService do ...@@ -89,13 +108,14 @@ describe MergeRequests::SquashService do
stages.each do |stage, command| stages.each do |stage, command|
context "when the #{stage} stage fails" do context "when the #{stage} stage fails" do
let(:merge_request) { merge_request_with_only_new_files }
let(:error) { 'A test error' } let(:error) { 'A test error' }
before do before do
git_command = a_collection_containing_exactly( git_command = a_collection_containing_exactly(
a_string_starting_with("#{Gitlab.config.git.bin_path} #{command}") a_string_starting_with("#{Gitlab.config.git.bin_path} #{command}")
).or( ).or(
a_collection_starting_with([Gitlab.config.git.bin_path, command]) a_collection_starting_with([Gitlab.config.git.bin_path] + command.split)
) )
allow(service).to receive(:popen).and_return(['', 0]) allow(service).to receive(:popen).and_return(['', 0])
...@@ -123,6 +143,7 @@ describe MergeRequests::SquashService do ...@@ -123,6 +143,7 @@ describe MergeRequests::SquashService do
end end
context 'when any other exception is thrown' do context 'when any other exception is thrown' do
let(:merge_request) { merge_request_with_only_new_files }
let(:error) { 'A test error' } let(:error) { 'A test error' }
before do before do
......
...@@ -38,7 +38,8 @@ module TestEnv ...@@ -38,7 +38,8 @@ module TestEnv
'conflict-too-large' => '39fa04f', 'conflict-too-large' => '39fa04f',
'deleted-image-test' => '6c17798', 'deleted-image-test' => '6c17798',
'wip' => 'b9238ee', 'wip' => 'b9238ee',
'csv' => '3dd0896' 'csv' => '3dd0896',
'squash-large-files' => '54cec52'
} }
# gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily # gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment