From 5ecd0c81af85476c2328d3836cc68b17ebd5a8a6 Mon Sep 17 00:00:00 2001
From: Lin Jen-Shin <godfat@godfat.org>
Date: Wed, 7 Dec 2016 22:37:43 +0800
Subject: [PATCH] Commit outside the hooks if possible:

So we still commit outside the hooks, and only
update ref inside the hooks. There are only two
exceptions:

* Whenever it's adding a tag. We can't add a tag
  without committing, unfortunately. See !7700
* Whenever source project is in another repository.
  We'll need to fetch ref otherwise commits can't be made.

See the whole discussion starting from:
https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7237#note_19210942
---
 app/models/repository.rb              |  16 +++-
 app/services/git_operation_service.rb | 111 ++++++++++++--------------
 spec/models/repository_spec.rb        |   2 +-
 3 files changed, 65 insertions(+), 64 deletions(-)

diff --git a/app/models/repository.rb b/app/models/repository.rb
index 36cbb0d051e..9393d6b461e 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -973,7 +973,8 @@ class Repository
       base_branch,
       source_branch: source_branch, source_project: source_project) do
 
-      source_sha = find_branch(base_branch).dereferenced_target.sha
+      source_sha = source_project.repository.find_source_sha(
+                     source_branch || base_branch)
       committer = user_to_committer(user)
 
       Rugged::Commit.create(rugged,
@@ -996,7 +997,8 @@ class Repository
       base_branch,
       source_branch: source_branch, source_project: source_project) do
 
-      source_sha = find_branch(base_branch).dereferenced_target.sha
+      source_sha = source_project.repository.find_source_sha(
+                     source_branch || base_branch)
       committer = user_to_committer(user)
 
       Rugged::Commit.create(rugged,
@@ -1162,6 +1164,16 @@ class Repository
     end
   end
 
+  protected
+
+  def find_source_sha(branch_name)
+    if branch_exists?(branch_name)
+      find_branch(branch_name).dereferenced_target.sha
+    else
+      Gitlab::Git::BLANK_SHA
+    end
+  end
+
   private
 
   def refs_directory_exists?
diff --git a/app/services/git_operation_service.rb b/app/services/git_operation_service.rb
index c9e2c21737a..3a102a9276b 100644
--- a/app/services/git_operation_service.rb
+++ b/app/services/git_operation_service.rb
@@ -34,43 +34,10 @@ GitOperationService = Struct.new(:user, :repository) do
     source_branch: nil,
     source_project: repository.project)
 
-    ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
-    oldrev = Gitlab::Git::BLANK_SHA
+    check_with_branch_arguments!(branch_name, source_branch, source_project)
 
-    if repository.branch_exists?(branch_name)
-      oldrev = newrev = repository.commit(branch_name).sha
-
-    elsif repository.project != source_project
-      unless source_branch
-        raise ArgumentError,
-          'Should also pass :source_branch if' +
-          ' :source_project is different from current project'
-      end
-
-      newrev = source_project.repository.commit(source_branch).try(:sha)
-
-      unless newrev
-        raise Repository::CommitError.new(
-          "Cannot find branch #{branch_name} nor" \
-          " #{source_branch} from" \
-          " #{source_project.path_with_namespace}")
-      end
-
-    elsif source_branch
-      newrev = repository.commit(source_branch).try(:sha)
-
-      unless newrev
-        raise Repository::CommitError.new(
-          "Cannot find branch #{branch_name} nor" \
-          " #{source_branch} from" \
-          " #{repository.project.path_with_namespace}")
-      end
-
-    else # we want an orphan empty branch
-      newrev = Gitlab::Git::BLANK_SHA
-    end
-
-    commit_with_hooks(ref, oldrev, newrev) do
+    update_branch_with_hooks(
+      branch_name, source_branch, source_project) do |ref|
       if repository.project != source_project
         repository.fetch_ref(
           source_project.repository.path_to_repo,
@@ -85,39 +52,37 @@ GitOperationService = Struct.new(:user, :repository) do
 
   private
 
-  def commit_with_hooks(ref, oldrev, newrev)
-    with_hooks_and_update_ref(ref, oldrev, newrev) do |service|
-      was_empty = repository.empty?
-
-      # Make commit
-      nextrev = yield(ref)
-
-      unless nextrev
-        raise Repository::CommitError.new('Failed to create commit')
-      end
+  def update_branch_with_hooks(branch_name, source_branch, source_project)
+    update_autocrlf_option
 
-      branch =
-        repository.find_branch(ref[Gitlab::Git::BRANCH_REF_PREFIX.size..-1])
+    ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
+    oldrev = Gitlab::Git::BLANK_SHA
+    was_empty = repository.empty?
 
-      prevrev = if branch &&
-                   !repository.rugged.lookup(nextrev).parent_ids.empty?
-                  repository.rugged.merge_base(
-                    nextrev, branch.dereferenced_target.sha)
-                else
-                  newrev
-                end
+    # Make commit
+    newrev = yield(ref)
 
-      update_ref!(ref, nextrev, prevrev)
+    unless newrev
+      raise Repository::CommitError.new('Failed to create commit')
+    end
 
-      service.newrev = nextrev
+    branch = repository.find_branch(branch_name)
+    oldrev = if repository.rugged.lookup(newrev).parent_ids.empty? ||
+                branch.nil?
+               Gitlab::Git::BLANK_SHA
+             else
+               repository.rugged.merge_base(
+                 newrev, branch.dereferenced_target.sha)
+             end
 
+    with_hooks_and_update_ref(ref, oldrev, newrev) do
       # If repo was empty expire cache
       repository.after_create if was_empty
       repository.after_create_branch if was_empty ||
                                         oldrev == Gitlab::Git::BLANK_SHA
-
-      nextrev
     end
+
+    newrev
   end
 
   def with_hooks_and_update_ref(ref, oldrev, newrev)
@@ -129,8 +94,6 @@ GitOperationService = Struct.new(:user, :repository) do
   end
 
   def with_hooks(ref, oldrev, newrev)
-    update_autocrlf_option
-
     result = nil
 
     GitHooksService.new.execute(
@@ -170,4 +133,30 @@ GitOperationService = Struct.new(:user, :repository) do
       repository.raw_repository.autocrlf = :input
     end
   end
+
+  def check_with_branch_arguments!(branch_name, source_branch, source_project)
+    return if repository.branch_exists?(branch_name)
+
+    if repository.project != source_project
+      unless source_branch
+        raise ArgumentError,
+          'Should also pass :source_branch if' +
+          ' :source_project is different from current project'
+      end
+
+      unless source_project.repository.commit(source_branch).try(:sha)
+        raise Repository::CommitError.new(
+          "Cannot find branch #{branch_name} nor" \
+          " #{source_branch} from" \
+          " #{source_project.path_with_namespace}")
+      end
+    elsif source_branch
+      unless repository.commit(source_branch).try(:sha)
+        raise Repository::CommitError.new(
+          "Cannot find branch #{branch_name} nor" \
+          " #{source_branch} from" \
+          " #{repository.project.path_with_namespace}")
+      end
+    end
+  end
 end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index e3ec4c85a74..ebd05c946cc 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -695,7 +695,7 @@ describe Repository, models: true do
             user,
             repository.path_to_repo,
             old_rev,
-            old_rev,
+            new_rev,
             'refs/heads/feature').
           and_yield(service).and_return(true)
       end
-- 
GitLab