13111: Merge branch 'master' into 13111-webdav-projects
[arvados.git] / services / api / app / models / commit.rb
index 2baf1d6f8fc58dc9bdc2c840e88b822578162b91..921c690cd00f78f6fc2b46bbace23fff89992db8 100644 (file)
@@ -2,10 +2,12 @@
 #
 # SPDX-License-Identifier: AGPL-3.0
 
+require 'request_error'
+
 class Commit < ActiveRecord::Base
   extend CurrentApiClient
 
-  class GitError < StandardError
+  class GitError < RequestError
     def http_status
       422
     end
@@ -149,16 +151,42 @@ class Commit < ActiveRecord::Base
     dst_gitdir = Rails.configuration.git_internal_dir
 
     begin
-      commit_in_dst = must_git(dst_gitdir, "rev-parse --verify #{sha1.shellescape}^{commit}").strip
+      commit_in_dst = must_git(dst_gitdir, "log -n1 --format=%H #{sha1.shellescape}^{commit}").strip
     rescue GitError
       commit_in_dst = false
     end
 
+    tag_cmd = "tag --force #{tag.shellescape} #{sha1.shellescape}^{commit}"
     if commit_in_dst == sha1
-      must_git(dst_gitdir, "tag --force #{tag.shellescape} #{sha1.shellescape}")
+      must_git(dst_gitdir, tag_cmd)
     else
-      refspec = "#{sha1}:refs/tags/#{tag}"
-      must_git(dst_gitdir, "fetch --no-tags file://#{src_gitdir.shellescape} #{refspec.shellescape}")
+      # git-fetch is faster than pack-objects|unpack-objects, but
+      # git-fetch can't fetch by sha1. So we first try to fetch a
+      # branch that has the desired commit, and if that fails (there
+      # is no such branch, or the branch we choose changes under us in
+      # race), we fall back to pack|unpack.
+      begin
+        branches = must_git(src_gitdir,
+                            "branch --contains #{sha1.shellescape}")
+        m = branches.match(/^. (\w+)\n/)
+        if !m
+          raise GitError.new "commit is not on any branch"
+        end
+        branch = m[1]
+        must_git(dst_gitdir,
+                 "fetch file://#{src_gitdir.shellescape} #{branch.shellescape}")
+        # Even if all of the above steps succeeded, we might still not
+        # have the right commit due to a race, in which case tag_cmd
+        # will fail, and we'll need to fall back to pack|unpack. So
+        # don't be tempted to condense this tag_cmd and the one in the
+        # rescue block into a single attempt.
+        must_git(dst_gitdir, tag_cmd)
+      rescue GitError
+        must_pipe("echo #{sha1.shellescape}",
+                  "git --git-dir #{src_gitdir.shellescape} pack-objects -q --revs --stdout",
+                  "git --git-dir #{dst_gitdir.shellescape} unpack-objects -q")
+        must_git(dst_gitdir, tag_cmd)
+      end
     end
   end
 
@@ -193,7 +221,7 @@ class Commit < ActiveRecord::Base
   end
 
   def self.cache_dir_base
-    Rails.root.join 'tmp', 'git'
+    Rails.root.join 'tmp', 'git-cache'
   end
 
   def self.fetch_remote_repository gitdir, git_url