Merge branch '8784-dir-listings'
[arvados.git] / services / api / app / models / commit.rb
index 9e1176b9b37e8640184be2eb62178e9520744cc7..1d9a821d131a4721bf461ff613531748c38d95fb 100644 (file)
@@ -1,4 +1,10 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
 class Commit < ActiveRecord::Base
+  extend CurrentApiClient
+
   class GitError < StandardError
     def http_status
       422
@@ -29,7 +35,7 @@ class Commit < ActiveRecord::Base
   # repository can be the name of a locally hosted repository or a git
   # URL (see git-fetch(1)). Currently http, https, and git schemes are
   # supported.
-  def self.find_commit_range(current_user, repository, minimum, maximum, exclude)
+  def self.find_commit_range repository, minimum, maximum, exclude
     if minimum and minimum.empty?
       minimum = nil
     end
@@ -56,12 +62,22 @@ class Commit < ActiveRecord::Base
 
     # Get the commit hash for the upper bound
     max_hash = nil
-    IO.foreach("|git rev-list --max-count=1 #{maximum.shellescape} --") do |line|
+    git_max_hash_cmd = "git rev-list --max-count=1 #{maximum.shellescape} --"
+    IO.foreach("|#{git_max_hash_cmd}") do |line|
       max_hash = line.strip
     end
 
-    # If not found or string is invalid, nothing else to do
-    return [] if !max_hash or !git_check_ref_format(max_hash)
+    # If not found, nothing else to do
+    if !max_hash
+      logger.warn "no refs found looking for max_hash: `GIT_DIR=#{gitdir} #{git_max_hash_cmd}` returned no output"
+      return []
+    end
+
+    # If string is invalid, nothing else to do
+    if !git_check_ref_format(max_hash)
+      logger.warn "ref returned by `GIT_DIR=#{gitdir} #{git_max_hash_cmd}` was invalid for max_hash: #{max_hash}"
+      return []
+    end
 
     resolved_exclude = nil
     if exclude
@@ -81,12 +97,22 @@ class Commit < ActiveRecord::Base
     if minimum
       # Get the commit hash for the lower bound
       min_hash = nil
-      IO.foreach("|git rev-list --max-count=1 #{minimum.shellescape} --") do |line|
+      git_min_hash_cmd = "git rev-list --max-count=1 #{minimum.shellescape} --"
+      IO.foreach("|#{git_min_hash_cmd}") do |line|
         min_hash = line.strip
       end
 
-      # If not found or string is invalid, nothing else to do
-      return [] if !min_hash or !git_check_ref_format(min_hash)
+      # If not found, nothing else to do
+      if !min_hash
+        logger.warn "no refs found looking for min_hash: `GIT_DIR=#{gitdir} #{git_min_hash_cmd}` returned no output"
+        return []
+      end
+
+      # If string is invalid, nothing else to do
+      if !git_check_ref_format(min_hash)
+        logger.warn "ref returned by `GIT_DIR=#{gitdir} #{git_min_hash_cmd}` was invalid for min_hash: #{min_hash}"
+        return []
+      end
 
       # Now find all commits between them
       IO.foreach("|git rev-list #{min_hash.shellescape}..#{max_hash.shellescape} --") do |line|
@@ -109,14 +135,17 @@ class Commit < ActiveRecord::Base
   # The repo can be a remote url, but in this case sha1 must already
   # be present in our local cache for that repo: e.g., sha1 was just
   # returned by find_commit_range.
-  def self.tag_in_internal_repository repo, sha1, tag
+  def self.tag_in_internal_repository repo_name, sha1, tag
     unless git_check_ref_format tag
       raise ArgumentError.new "invalid tag #{tag}"
     end
     unless /^[0-9a-f]{40}$/ =~ sha1
       raise ArgumentError.new "invalid sha1 #{sha1}"
     end
-    src_gitdir, _ = git_dir_for repo
+    src_gitdir, _ = git_dir_for repo_name
+    unless src_gitdir
+      raise ArgumentError.new "no local repository for #{repo_name}"
+    end
     dst_gitdir = Rails.configuration.git_internal_dir
     must_pipe("echo #{sha1.shellescape}",
               "git --git-dir #{src_gitdir.shellescape} pack-objects -q --revs --stdout",
@@ -127,8 +156,8 @@ class Commit < ActiveRecord::Base
 
   protected
 
-  def self.remote_url? repository
-    /^(https?|git):\/\// =~ repository
+  def self.remote_url? repo_name
+    /^(https?|git):\/\// =~ repo_name
   end
 
   # Return [local_git_dir, is_remote]. If is_remote, caller must use
@@ -152,8 +181,11 @@ class Commit < ActiveRecord::Base
   end
 
   def self.cache_dir_for git_url
-    Rails.root.join('tmp', 'git', Digest::SHA1.hexdigest(git_url) + ".git").
-      to_s
+    File.join(cache_dir_base, Digest::SHA1.hexdigest(git_url) + ".git").to_s
+  end
+
+  def self.cache_dir_base
+    Rails.root.join 'tmp', 'git'
   end
 
   def self.fetch_remote_repository gitdir, git_url
@@ -164,9 +196,16 @@ class Commit < ActiveRecord::Base
     unless /^[a-z]+:\/\// =~ git_url
       raise ArgumentError.new "invalid git url #{git_url}"
     end
-    FileUtils.mkdir_p gitdir
+    begin
+      must_git gitdir, "branch"
+    rescue GitError => e
+      raise unless /Not a git repository/ =~ e.to_s
+      # OK, this just means we need to create a blank cache repository
+      # before fetching.
+      FileUtils.mkdir_p gitdir
+      must_git gitdir, "init"
+    end
     must_git(gitdir,
-             "init",
              "fetch --no-progress --tags --prune --force --update-head-ok #{git_url.shellescape} 'refs/heads/*:refs/heads/*'")
   end