From 0284f01716cfdcbe8d9e7a0281e551414b4c0239 Mon Sep 17 00:00:00 2001
From: Ahmad Sherif <me@ahmadsherif.com>
Date: Mon, 12 Jun 2017 20:55:28 +0200
Subject: [PATCH] Migrate Gitlab::Git::Blob.find to Gitaly

---
 GITALY_SERVER_VERSION              |  2 +-
 Gemfile                            |  2 +-
 Gemfile.lock                       |  4 +--
 lib/gitlab/git/blob.rb             | 45 ++++++++++++++++++++++++++++++
 lib/gitlab/gitaly_client/commit.rb | 20 +++++++++++++
 spec/lib/gitlab/git/blob_spec.rb   | 12 +++++++-
 spec/lib/gitlab/workhorse_spec.rb  |  1 -
 7 files changed, 80 insertions(+), 6 deletions(-)

diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index bc859cbd6d9..ac454c6a1fc 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-0.11.2
+0.12.0
diff --git a/Gemfile b/Gemfile
index 61c70ce6b8a..b1790b23dcd 100644
--- a/Gemfile
+++ b/Gemfile
@@ -384,7 +384,7 @@ gem 'vmstat', '~> 2.3.0'
 gem 'sys-filesystem', '~> 1.1.6'
 
 # Gitaly GRPC client
-gem 'gitaly', '~> 0.8.0'
+gem 'gitaly', '~> 0.9.0'
 
 gem 'toml-rb', '~> 0.3.15', require: false
 
diff --git a/Gemfile.lock b/Gemfile.lock
index 7ca330b6a59..bfd0498db35 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -277,7 +277,7 @@ GEM
       po_to_json (>= 1.0.0)
       rails (>= 3.2.0)
     gherkin-ruby (0.3.2)
-    gitaly (0.8.0)
+    gitaly (0.9.0)
       google-protobuf (~> 3.1)
       grpc (~> 1.0)
     github-linguist (4.7.6)
@@ -977,7 +977,7 @@ DEPENDENCIES
   gettext (~> 3.2.2)
   gettext_i18n_rails (~> 1.8.0)
   gettext_i18n_rails_js (~> 1.2.0)
-  gitaly (~> 0.8.0)
+  gitaly (~> 0.9.0)
   github-linguist (~> 4.7.0)
   gitlab-flowdock-git-hook (~> 1.0.1)
   gitlab-markup (~> 1.5.1)
diff --git a/lib/gitlab/git/blob.rb b/lib/gitlab/git/blob.rb
index 33a7624e303..a7aceab4c14 100644
--- a/lib/gitlab/git/blob.rb
+++ b/lib/gitlab/git/blob.rb
@@ -14,6 +14,51 @@ module Gitlab
 
       class << self
         def find(repository, sha, path)
+          Gitlab::GitalyClient.migrate(:project_raw_show) do |is_enabled|
+            if is_enabled
+              find_by_gitaly(repository, sha, path)
+            else
+              find_by_rugged(repository, sha, path)
+            end
+          end
+        end
+
+        def find_by_gitaly(repository, sha, path)
+          path = path.sub(/\A\/*/, '')
+          path = '/' if path.empty?
+          name = File.basename(path)
+          entry = Gitlab::GitalyClient::Commit.new(repository).tree_entry(sha, path, MAX_DATA_DISPLAY_SIZE)
+          return unless entry
+
+          case entry.type
+          when :COMMIT
+            new(
+              id: entry.oid,
+              name: name,
+              size: 0,
+              data: '',
+              path: path,
+              commit_id: sha
+            )
+          when :BLOB
+            # EncodingDetector checks the first 1024 * 1024 bytes for NUL byte, libgit2 checks
+            # only the first 8000 (https://github.com/libgit2/libgit2/blob/2ed855a9e8f9af211e7274021c2264e600c0f86b/src/filter.h#L15),
+            # which is what we use below to keep a consistent behavior.
+            detect = CharlockHolmes::EncodingDetector.new(8000).detect(entry.data)
+            new(
+              id: entry.oid,
+              name: name,
+              size: entry.size,
+              data: entry.data.dup,
+              mode: entry.mode.to_s(8),
+              path: path,
+              commit_id: sha,
+              binary: detect && detect[:type] == :binary
+            )
+          end
+        end
+
+        def find_by_rugged(repository, sha, path)
           commit = repository.lookup(sha)
           root_tree = commit.tree
 
diff --git a/lib/gitlab/gitaly_client/commit.rb b/lib/gitlab/gitaly_client/commit.rb
index 73c1848c95f..b8877619797 100644
--- a/lib/gitlab/gitaly_client/commit.rb
+++ b/lib/gitlab/gitaly_client/commit.rb
@@ -36,6 +36,26 @@ module Gitlab
         end
       end
 
+      def tree_entry(ref, path, limit = nil)
+        request = Gitaly::TreeEntryRequest.new(
+          repository: @gitaly_repo,
+          revision: ref,
+          path: path.dup.force_encoding(Encoding::ASCII_8BIT),
+          limit: limit.to_i
+        )
+
+        response = GitalyClient.call(@repository.storage, :commit, :tree_entry, request)
+        entry = response.first
+        return unless entry.oid.present?
+
+        if entry.type == :BLOB
+          rest_of_data = response.reduce("") { |memo, msg| memo << msg.data }
+          entry.data += rest_of_data
+        end
+
+        entry
+      end
+
       private
 
       def commit_diff_request_params(commit, options = {})
diff --git a/spec/lib/gitlab/git/blob_spec.rb b/spec/lib/gitlab/git/blob_spec.rb
index e6a07a58d73..610a08b3400 100644
--- a/spec/lib/gitlab/git/blob_spec.rb
+++ b/spec/lib/gitlab/git/blob_spec.rb
@@ -15,7 +15,7 @@ describe Gitlab::Git::Blob, seed_helper: true do
     end
   end
 
-  describe '.find' do
+  shared_examples 'finding blobs' do
     context 'file in subdir' do
       let(:blob) { Gitlab::Git::Blob.find(repository, SeedRepo::Commit::ID, "files/ruby/popen.rb") }
 
@@ -101,6 +101,16 @@ describe Gitlab::Git::Blob, seed_helper: true do
     end
   end
 
+  describe '.find' do
+    context 'when project_raw_show Gitaly feature is enabled' do
+      it_behaves_like 'finding blobs'
+    end
+
+    context 'when project_raw_show Gitaly feature is disabled', skip_gitaly_mock: true do
+      it_behaves_like 'finding blobs'
+    end
+  end
+
   describe '.raw' do
     let(:raw_blob) { Gitlab::Git::Blob.raw(repository, SeedRepo::RubyBlob::ID) }
     it { expect(raw_blob.id).to eq(SeedRepo::RubyBlob::ID) }
diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb
index a3e8166cb70..493ff3bb5fb 100644
--- a/spec/lib/gitlab/workhorse_spec.rb
+++ b/spec/lib/gitlab/workhorse_spec.rb
@@ -216,7 +216,6 @@ describe Gitlab::Workhorse, lib: true do
 
       it 'includes a Repository param' do
         repo_param = { Repository: {
-          path: '', # deprecated field; grpc automatically creates it anyway
           storage_name: 'default',
           relative_path: project.full_path + '.git'
         } }
-- 
GitLab