From 888821f9ffb56c6fdf762f28dd42cf3b7226652f Mon Sep 17 00:00:00 2001
From: Kamil Trzcinski <ayufan@ayufan.eu>
Date: Thu, 19 Nov 2015 19:22:46 +0100
Subject: [PATCH] Add support for batch download operation

---
 lib/gitlab/lfs/response.rb             | 105 ++++++++++++++++++-------
 lib/gitlab/lfs/router.rb               |   2 +-
 spec/lib/gitlab/lfs/lfs_router_spec.rb |  25 ++++--
 3 files changed, 96 insertions(+), 36 deletions(-)

diff --git a/lib/gitlab/lfs/response.rb b/lib/gitlab/lfs/response.rb
index 4202c786466..ddadc07ebba 100644
--- a/lib/gitlab/lfs/response.rb
+++ b/lib/gitlab/lfs/response.rb
@@ -26,7 +26,7 @@ module Gitlab
 
       def render_download_object_response(oid)
         render_response_to_download do
-          if check_download_sendfile_header? && check_download_accept_header?
+          if check_download_sendfile_header?
             render_lfs_sendfile(oid)
           else
             render_not_found
@@ -34,20 +34,15 @@ module Gitlab
         end
       end
 
-      def render_lfs_api_auth
-        render_response_to_push do
-          request_body = JSON.parse(@request.body.read)
-          return render_not_found if request_body.empty? || request_body['objects'].empty?
-
-          response = build_response(request_body['objects'])
-          [
-            200,
-            {
-              "Content-Type" => "application/json; charset=utf-8",
-              "Cache-Control" => "private",
-            },
-            [JSON.dump(response)]
-          ]
+      def render_batch_operation_response
+        request_body = JSON.parse(@request.body.read)
+        case request_body["operation"]
+        when "download"
+          render_batch_download(request_body)
+        when "upload"
+          render_batch_upload(request_body)
+        else
+          render_forbidden
         end
       end
 
@@ -142,6 +137,38 @@ module Gitlab
         end
       end
 
+      def render_batch_upload(body)
+        return render_not_found if body.empty? || body['objects'].nil?
+
+        render_response_to_push do
+          response = build_upload_batch_response(body['objects'])
+          [
+            200,
+            {
+              "Content-Type" => "application/json; charset=utf-8",
+              "Cache-Control" => "private",
+            },
+            [JSON.dump(response)]
+          ]
+        end
+      end
+
+      def render_batch_download(body)
+        return render_not_found if body.empty? || body['objects'].nil?
+
+        render_response_to_download do
+          response = build_download_batch_response(body['objects'])
+          [
+            200,
+            {
+              "Content-Type" => "application/json; charset=utf-8",
+              "Cache-Control" => "private",
+            },
+            [JSON.dump(response)]
+          ]
+        end
+      end
+
       def render_lfs_download_hypermedia(oid)
         return render_not_found unless oid.present?
 
@@ -266,10 +293,16 @@ module Gitlab
         @project.lfs_objects.where(oid: objects_oids).pluck(:oid).to_set
       end
 
-      def build_response(objects)
+      def build_upload_batch_response(objects)
         selected_objects = select_existing_objects(objects)
 
-        upload_hypermedia(objects, selected_objects)
+        upload_hypermedia_links(objects, selected_objects)
+      end
+
+      def build_download_batch_response(objects)
+        selected_objects = select_existing_objects(objects)
+
+        download_hypermedia_links(objects, selected_objects)
       end
 
       def download_hypermedia(oid)
@@ -279,7 +312,6 @@ module Gitlab
              {
               'href' => "#{@origin_project.http_url_to_repo}/gitlab-lfs/objects/#{oid}",
               'header' => {
-                'Accept' => "application/vnd.git-lfs+json; charset=utf-8",
                 'Authorization' => @env['HTTP_AUTHORIZATION']
               }.compact
             }
@@ -287,21 +319,40 @@ module Gitlab
         }
       end
 
-      def upload_hypermedia(all_objects, existing_objects)
+      def download_hypermedia_links(all_objects, existing_objects)
         all_objects.each do |object|
-          object['_links'] = hypermedia_links(object) unless existing_objects.include?(object['oid'])
+          # generate links only for existing objects
+          next unless existing_objects.include?(object['oid'])
+
+          object['_links'] = {
+            'download' => {
+              'href' => "#{@origin_project.http_url_to_repo}/gitlab-lfs/objects/#{object['oid']}",
+              'header' => {
+                'Authorization' => @env['HTTP_AUTHORIZATION']
+              }.compact
+            }
+          }
         end
 
         { 'objects' => all_objects }
       end
 
-      def hypermedia_links(object)
-        {
-          "upload" => {
-            'href' => "#{@origin_project.http_url_to_repo}/gitlab-lfs/objects/#{object['oid']}/#{object['size']}",
-            'header' => { 'Authorization' => @env['HTTP_AUTHORIZATION'] }
-          }.compact
-        }
+      def upload_hypermedia_links(all_objects, existing_objects)
+        all_objects.each do |object|
+          # generate links only for non-existing objects
+          next if existing_objects.include?(object['oid'])
+
+          object['_links'] = {
+            'upload' => {
+              'href' => "#{@origin_project.http_url_to_repo}/gitlab-lfs/objects/#{object['oid']}/#{object['size']}",
+              'header' => {
+                'Authorization' => @env['HTTP_AUTHORIZATION']
+              }.compact
+            }
+          }
+        end
+
+        { 'objects' => all_objects }
       end
     end
   end
diff --git a/lib/gitlab/lfs/router.rb b/lib/gitlab/lfs/router.rb
index 4809e834984..2b3f2e8e958 100644
--- a/lib/gitlab/lfs/router.rb
+++ b/lib/gitlab/lfs/router.rb
@@ -48,7 +48,7 @@ module Gitlab
 
         # Check for Batch API
         if post_path[0].ends_with?("/info/lfs/objects/batch")
-          lfs.render_lfs_api_auth
+          lfs.render_batch_operation_response
         else
           nil
         end
diff --git a/spec/lib/gitlab/lfs/lfs_router_spec.rb b/spec/lib/gitlab/lfs/lfs_router_spec.rb
index cebcb5bc887..5eafaad79c9 100644
--- a/spec/lib/gitlab/lfs/lfs_router_spec.rb
+++ b/spec/lib/gitlab/lfs/lfs_router_spec.rb
@@ -66,7 +66,7 @@ describe Gitlab::Lfs::Router do
           json_response = ActiveSupport::JSON.decode(lfs_router_auth.try_call.last.first)
 
           expect(json_response['_links']['download']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}")
-          expect(json_response['_links']['download']['header']).to eq("Authorization" => @auth, "Accept" => "application/vnd.git-lfs+json; charset=utf-8")
+          expect(json_response['_links']['download']['header']).to eq("Authorization" => @auth)
         end
       end
 
@@ -107,7 +107,7 @@ describe Gitlab::Lfs::Router do
             json_response = ActiveSupport::JSON.decode(lfs_router_public_auth.try_call.last.first)
 
             expect(json_response['_links']['download']['href']).to eq("#{Gitlab.config.gitlab.url}/#{public_project.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}")
-            expect(json_response['_links']['download']['header']).to eq("Accept" => "application/vnd.git-lfs+json; charset=utf-8")
+            expect(json_response['_links']['download']['header']).to eq({})
           end
         end
 
@@ -117,7 +117,7 @@ describe Gitlab::Lfs::Router do
             json_response = ActiveSupport::JSON.decode(lfs_router_public_noauth.try_call.last.first)
 
             expect(json_response['_links']['download']['href']).to eq("#{Gitlab.config.gitlab.url}/#{public_project.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}")
-            expect(json_response['_links']['download']['header']).to eq("Accept" => "application/vnd.git-lfs+json; charset=utf-8")
+            expect(json_response['_links']['download']['header']).to eq({})
           end
         end
       end
@@ -191,7 +191,7 @@ describe Gitlab::Lfs::Router do
             json_response = ActiveSupport::JSON.decode(lfs_router_forked_auth.try_call.last.first)
 
             expect(json_response['_links']['download']['href']).to eq("#{Gitlab.config.gitlab.url}/#{forked_project.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}")
-            expect(json_response['_links']['download']['header']).to eq("Accept" => "application/vnd.git-lfs+json; charset=utf-8")
+            expect(json_response['_links']['download']['header']).to eq({})
           end
         end
 
@@ -219,7 +219,7 @@ describe Gitlab::Lfs::Router do
 
             json_response = ActiveSupport::JSON.decode(lfs_router_forked_auth.try_call.last.first)
             expect(json_response['_links']['download']['href']).to eq("#{Gitlab.config.gitlab.url}/#{forked_project.path_with_namespace}.git/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897")
-            expect(json_response['_links']['download']['header']).to eq("Accept" => "application/vnd.git-lfs+json; charset=utf-8", "Authorization" => @auth)
+            expect(json_response['_links']['download']['header']).to eq("Authorization" => @auth)
           end
         end
 
@@ -250,7 +250,8 @@ describe Gitlab::Lfs::Router do
         body = { 'objects' => [{
                    'oid' => sample_oid,
                    'size' => sample_size
-                  }]
+                  }],
+                  'operation' => 'upload'
                 }.to_json
         env['rack.input'] = StringIO.new(body)
       end
@@ -286,7 +287,8 @@ describe Gitlab::Lfs::Router do
               'objects' => [{
                 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
                 'size' => 1575078
-                }]
+                }],
+              'operation' => 'upload'
                 }.to_json
             env['rack.input'] = StringIO.new(body)
           end
@@ -315,7 +317,8 @@ describe Gitlab::Lfs::Router do
                 { 'oid' => sample_oid,
                   'size' => sample_size
                 }
-              ]
+              ],
+              'operation' => 'upload'
             }.to_json
             env['rack.input'] = StringIO.new(body)
             public_project.lfs_objects << lfs_object
@@ -351,6 +354,12 @@ describe Gitlab::Lfs::Router do
     end
 
     context 'when user is not authenticated' do
+      before do
+        env['rack.input'] = StringIO.new(
+          { 'objects' => [], 'operation' => 'upload' }.to_json
+        )
+      end
+
       context 'when user has push access' do
         before do
           project.team << [user, :master]
-- 
2.30.9