Commit 0b5a2eef authored by Lin Jen-Shin's avatar Lin Jen-Shin

Add `source_branch` option for various git operations

If `source_branch` option is passed, and target branch cannot be found,
`Repository#update_branch_with_hooks` would try to create a new branch
from `source_branch`.

This way, we could make changes in the new branch while only firing
the hooks once for the changes. Previously, we can only create a new
branch first then make changes to the new branch, firing hooks twice.
This behaviour is bad for CI.

Fixes #7237
parent 3128641f
...@@ -786,8 +786,12 @@ class Repository ...@@ -786,8 +786,12 @@ class Repository
@root_ref ||= cache.fetch(:root_ref) { raw_repository.root_ref } @root_ref ||= cache.fetch(:root_ref) { raw_repository.root_ref }
end end
def commit_dir(user, path, message, branch, author_email: nil, author_name: nil) def commit_dir(user, path, message, branch,
update_branch_with_hooks(user, branch) do |ref| author_email: nil, author_name: nil, source_branch: nil)
update_branch_with_hooks(
user,
branch,
source_branch: source_branch) do |ref|
options = { options = {
commit: { commit: {
branch: ref, branch: ref,
...@@ -802,8 +806,12 @@ class Repository ...@@ -802,8 +806,12 @@ class Repository
end end
end end
def commit_file(user, path, content, message, branch, update, author_email: nil, author_name: nil) def commit_file(user, path, content, message, branch, update,
update_branch_with_hooks(user, branch) do |ref| author_email: nil, author_name: nil, source_branch: nil)
update_branch_with_hooks(
user,
branch,
source_branch: source_branch) do |ref|
options = { options = {
commit: { commit: {
branch: ref, branch: ref,
...@@ -823,8 +831,13 @@ class Repository ...@@ -823,8 +831,13 @@ class Repository
end end
end end
def update_file(user, path, content, branch:, previous_path:, message:, author_email: nil, author_name: nil) def update_file(user, path, content,
update_branch_with_hooks(user, branch) do |ref| branch:, previous_path:, message:,
author_email: nil, author_name: nil, source_branch: nil)
update_branch_with_hooks(
user,
branch,
source_branch: source_branch) do |ref|
options = { options = {
commit: { commit: {
branch: ref, branch: ref,
...@@ -849,8 +862,12 @@ class Repository ...@@ -849,8 +862,12 @@ class Repository
end end
end end
def remove_file(user, path, message, branch, author_email: nil, author_name: nil) def remove_file(user, path, message, branch,
update_branch_with_hooks(user, branch) do |ref| author_email: nil, author_name: nil, source_branch: nil)
update_branch_with_hooks(
user,
branch,
source_branch: source_branch) do |ref|
options = { options = {
commit: { commit: {
branch: ref, branch: ref,
...@@ -868,17 +885,18 @@ class Repository ...@@ -868,17 +885,18 @@ class Repository
end end
end end
def multi_action(user:, branch:, message:, actions:, author_email: nil, author_name: nil) def multi_action(user:, branch:, message:, actions:,
update_branch_with_hooks(user, branch) do |ref| author_email: nil, author_name: nil, source_branch: nil)
update_branch_with_hooks(
user,
branch,
source_branch: source_branch) do |ref|
index = rugged.index index = rugged.index
parents = [] parents = []
branch = find_branch(ref)
if branch last_commit = find_branch(ref).dereferenced_target
last_commit = branch.dereferenced_target
index.read_tree(last_commit.raw_commit.tree) index.read_tree(last_commit.raw_commit.tree)
parents = [last_commit.sha] parents = [last_commit.sha]
end
actions.each do |action| actions.each do |action|
case action[:action] case action[:action]
...@@ -967,7 +985,10 @@ class Repository ...@@ -967,7 +985,10 @@ class Repository
return false unless revert_tree_id return false unless revert_tree_id
update_branch_with_hooks(user, base_branch) do update_branch_with_hooks(
user,
base_branch,
source_branch: revert_tree_id) do
committer = user_to_committer(user) committer = user_to_committer(user)
source_sha = Rugged::Commit.create(rugged, source_sha = Rugged::Commit.create(rugged,
message: commit.revert_message, message: commit.revert_message,
...@@ -984,7 +1005,10 @@ class Repository ...@@ -984,7 +1005,10 @@ class Repository
return false unless cherry_pick_tree_id return false unless cherry_pick_tree_id
update_branch_with_hooks(user, base_branch) do update_branch_with_hooks(
user,
base_branch,
source_branch: cherry_pick_tree_id) do
committer = user_to_committer(user) committer = user_to_committer(user)
source_sha = Rugged::Commit.create(rugged, source_sha = Rugged::Commit.create(rugged,
message: commit.message, message: commit.message,
...@@ -1082,11 +1106,11 @@ class Repository ...@@ -1082,11 +1106,11 @@ class Repository
fetch_ref(path_to_repo, ref, ref_path) fetch_ref(path_to_repo, ref, ref_path)
end end
def update_branch_with_hooks(current_user, branch) def update_branch_with_hooks(current_user, branch, source_branch: nil)
update_autocrlf_option update_autocrlf_option
ref = Gitlab::Git::BRANCH_REF_PREFIX + branch ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
target_branch = find_branch(branch) target_branch, new_branch_added = raw_ensure_branch(branch, source_branch)
was_empty = empty? was_empty = empty?
# Make commit # Make commit
...@@ -1096,7 +1120,7 @@ class Repository ...@@ -1096,7 +1120,7 @@ class Repository
raise CommitError.new('Failed to create commit') raise CommitError.new('Failed to create commit')
end end
if rugged.lookup(newrev).parent_ids.empty? || target_branch.nil? if rugged.lookup(newrev).parent_ids.empty?
oldrev = Gitlab::Git::BLANK_SHA oldrev = Gitlab::Git::BLANK_SHA
else else
oldrev = rugged.merge_base(newrev, target_branch.dereferenced_target.sha) oldrev = rugged.merge_base(newrev, target_branch.dereferenced_target.sha)
...@@ -1105,11 +1129,9 @@ class Repository ...@@ -1105,11 +1129,9 @@ class Repository
GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do
update_ref!(ref, newrev, oldrev) update_ref!(ref, newrev, oldrev)
if was_empty || !target_branch
# If repo was empty expire cache # If repo was empty expire cache
after_create if was_empty after_create if was_empty
after_create_branch after_create_branch if was_empty || new_branch_added
end
end end
newrev newrev
...@@ -1169,4 +1191,28 @@ class Repository ...@@ -1169,4 +1191,28 @@ class Repository
def repository_event(event, tags = {}) def repository_event(event, tags = {})
Gitlab::Metrics.add_event(event, { path: path_with_namespace }.merge(tags)) Gitlab::Metrics.add_event(event, { path: path_with_namespace }.merge(tags))
end end
def raw_ensure_branch(branch_name, source_branch)
old_branch = find_branch(branch_name)
if old_branch
[old_branch, false]
elsif source_branch
oldrev = Gitlab::Git::BLANK_SHA
ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
target = commit(source_branch).try(:id)
unless target
raise CommitError.new(
"Cannot find branch #{branch_name} nor #{source_branch}")
end
update_ref!(ref, target, oldrev)
[find_branch(branch_name), true]
else
raise CommitError.new(
"Cannot find branch #{branch_name} and source_branch is not set")
end
end
end end
...@@ -29,7 +29,7 @@ module Commits ...@@ -29,7 +29,7 @@ module Commits
tree_id = repository.public_send("check_#{action}_content", @commit, @target_branch) tree_id = repository.public_send("check_#{action}_content", @commit, @target_branch)
if tree_id if tree_id
create_target_branch(into) if @create_merge_request validate_target_branch(into) if @create_merge_request
repository.public_send(action, current_user, @commit, into, tree_id) repository.public_send(action, current_user, @commit, into, tree_id)
success success
...@@ -50,12 +50,12 @@ module Commits ...@@ -50,12 +50,12 @@ module Commits
true true
end end
def create_target_branch(new_branch) def validate_target_branch(new_branch)
# Temporary branch exists and contains the change commit # Temporary branch exists and contains the change commit
return success if repository.find_branch(new_branch) return if repository.find_branch(new_branch)
result = CreateBranchService.new(@project, current_user) result = ValidateNewBranchService.new(@project, current_user).
.execute(new_branch, @target_branch, source_project: @source_project) execute(new_branch)
if result[:status] == :error if result[:status] == :error
raise ChangeError, "There was an error creating the source branch: #{result[:message]}" raise ChangeError, "There was an error creating the source branch: #{result[:message]}"
......
...@@ -2,18 +2,9 @@ require_relative 'base_service' ...@@ -2,18 +2,9 @@ require_relative 'base_service'
class CreateBranchService < BaseService class CreateBranchService < BaseService
def execute(branch_name, ref, source_project: @project) def execute(branch_name, ref, source_project: @project)
valid_branch = Gitlab::GitRefValidator.validate(branch_name) failure = validate_new_branch(branch_name)
unless valid_branch return failure if failure
return error('Branch name is invalid')
end
repository = project.repository
existing_branch = repository.find_branch(branch_name)
if existing_branch
return error('Branch already exists')
end
new_branch = if source_project != @project new_branch = if source_project != @project
repository.fetch_ref( repository.fetch_ref(
...@@ -41,4 +32,13 @@ class CreateBranchService < BaseService ...@@ -41,4 +32,13 @@ class CreateBranchService < BaseService
def success(branch) def success(branch)
super().merge(branch: branch) super().merge(branch: branch)
end end
private
def validate_new_branch(branch_name)
result = ValidateNewBranchService.new(project, current_user).
execute(branch_name)
error(result[:message]) if result[:status] == :error
end
end end
...@@ -23,9 +23,7 @@ module Files ...@@ -23,9 +23,7 @@ module Files
validate validate
# Create new branch if it different from source_branch # Create new branch if it different from source_branch
if different_branch? validate_target_branch if different_branch?
create_target_branch
end
result = commit result = commit
if result if result
...@@ -73,10 +71,11 @@ module Files ...@@ -73,10 +71,11 @@ module Files
end end
end end
def create_target_branch def validate_target_branch
result = CreateBranchService.new(project, current_user).execute(@target_branch, @source_branch, source_project: @source_project) result = ValidateNewBranchService.new(project, current_user).
execute(@target_branch)
unless result[:status] == :success if result[:status] == :error
raise_error("Something went wrong when we tried to create #{@target_branch} for you: #{result[:message]}") raise_error("Something went wrong when we tried to create #{@target_branch} for you: #{result[:message]}")
end end
end end
......
...@@ -3,7 +3,14 @@ require_relative "base_service" ...@@ -3,7 +3,14 @@ require_relative "base_service"
module Files module Files
class CreateDirService < Files::BaseService class CreateDirService < Files::BaseService
def commit def commit
repository.commit_dir(current_user, @file_path, @commit_message, @target_branch, author_email: @author_email, author_name: @author_name) repository.commit_dir(
current_user,
@file_path,
@commit_message,
@target_branch,
author_email: @author_email,
author_name: @author_name,
source_branch: @source_branch)
end end
def validate def validate
......
...@@ -3,7 +3,16 @@ require_relative "base_service" ...@@ -3,7 +3,16 @@ require_relative "base_service"
module Files module Files
class CreateService < Files::BaseService class CreateService < Files::BaseService
def commit def commit
repository.commit_file(current_user, @file_path, @file_content, @commit_message, @target_branch, false, author_email: @author_email, author_name: @author_name) repository.commit_file(
current_user,
@file_path,
@file_content,
@commit_message,
@target_branch,
false,
author_email: @author_email,
author_name: @author_name,
source_branch: @source_branch)
end end
def validate def validate
......
...@@ -3,7 +3,14 @@ require_relative "base_service" ...@@ -3,7 +3,14 @@ require_relative "base_service"
module Files module Files
class DeleteService < Files::BaseService class DeleteService < Files::BaseService
def commit def commit
repository.remove_file(current_user, @file_path, @commit_message, @target_branch, author_email: @author_email, author_name: @author_name) repository.remove_file(
current_user,
@file_path,
@commit_message,
@target_branch,
author_email: @author_email,
author_name: @author_name,
source_branch: @source_branch)
end end
end end
end end
...@@ -11,7 +11,8 @@ module Files ...@@ -11,7 +11,8 @@ module Files
message: @commit_message, message: @commit_message,
actions: params[:actions], actions: params[:actions],
author_email: @author_email, author_email: @author_email,
author_name: @author_name author_name: @author_name,
source_branch: @source_branch
) )
end end
......
...@@ -10,7 +10,8 @@ module Files ...@@ -10,7 +10,8 @@ module Files
previous_path: @previous_path, previous_path: @previous_path,
message: @commit_message, message: @commit_message,
author_email: @author_email, author_email: @author_email,
author_name: @author_name) author_name: @author_name,
source_branch: @source_branch)
end end
private private
......
require_relative 'base_service'
class ValidateNewBranchService < BaseService
def execute(branch_name)
valid_branch = Gitlab::GitRefValidator.validate(branch_name)
unless valid_branch
return error('Branch name is invalid')
end
repository = project.repository
existing_branch = repository.find_branch(branch_name)
if existing_branch
return error('Branch already exists')
end
success
rescue GitHooksService::PreReceiveError => ex
error(ex.message)
end
end
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