From c38e4ccde4272ef572b531d478f9803d1e7e4234 Mon Sep 17 00:00:00 2001
From: Jacques <jerasmus@gitlab.com>
Date: Mon, 22 Nov 2021 15:35:42 +0100
Subject: [PATCH] Add canCurrentUserPushToBranch permission

Added canCurrentUserPushToBranch to graphQL

Changelog: added
---
 .../components/blob_button_group.vue          |  5 ++++
 .../components/blob_content_viewer.vue        |  2 ++
 .../components/delete_blob_modal.vue          | 13 +++++++---
 .../queries/blob_info.query.graphql           |  1 +
 app/graphql/types/repository/blob_type.rb     |  3 +++
 app/presenters/blob_presenter.rb              |  6 +++++
 doc/api/graphql/reference/index.md            |  1 +
 .../components/blob_button_group_spec.js      |  1 +
 .../components/blob_button_group_spec.js      |  1 +
 .../components/delete_blob_modal_spec.js      | 26 +++++++++++--------
 spec/frontend/repository/mock_data.js         |  1 +
 .../types/repository/blob_type_spec.rb        |  1 +
 spec/presenters/blob_presenter_spec.rb        | 22 ++++++++++++++++
 13 files changed, 69 insertions(+), 14 deletions(-)

diff --git a/app/assets/javascripts/repository/components/blob_button_group.vue b/app/assets/javascripts/repository/components/blob_button_group.vue
index e2ba5cea6ccdc..de6156d48dc64 100644
--- a/app/assets/javascripts/repository/components/blob_button_group.vue
+++ b/app/assets/javascripts/repository/components/blob_button_group.vue
@@ -53,6 +53,10 @@ export default {
       type: Boolean,
       required: true,
     },
+    canPushToBranch: {
+      type: Boolean,
+      required: true,
+    },
     emptyRepo: {
       type: Boolean,
       required: true,
@@ -126,6 +130,7 @@ export default {
       :target-branch="targetBranch || ref"
       :original-branch="originalBranch || ref"
       :can-push-code="canPushCode"
+      :can-push-to-branch="canPushToBranch"
       :empty-repo="emptyRepo"
     />
   </div>
diff --git a/app/assets/javascripts/repository/components/blob_content_viewer.vue b/app/assets/javascripts/repository/components/blob_content_viewer.vue
index 2cc5a8a79d23b..cea95645fa4bf 100644
--- a/app/assets/javascripts/repository/components/blob_content_viewer.vue
+++ b/app/assets/javascripts/repository/components/blob_content_viewer.vue
@@ -106,6 +106,7 @@ export default {
                 ideForkAndEditPath: '',
                 storedExternally: false,
                 canModifyBlob: false,
+                canCurrentUserPushToBranch: false,
                 rawPath: '',
                 externalStorageUrl: '',
                 replacePath: '',
@@ -266,6 +267,7 @@ export default {
             :replace-path="blobInfo.replacePath"
             :delete-path="blobInfo.webPath"
             :can-push-code="project.userPermissions.pushCode"
+            :can-push-to-branch="blobInfo.canCurrentUserPushToBranch"
             :empty-repo="project.repository.empty"
             :project-path="projectPath"
             :is-locked="isLocked"
diff --git a/app/assets/javascripts/repository/components/delete_blob_modal.vue b/app/assets/javascripts/repository/components/delete_blob_modal.vue
index e15f9b01c6278..0d3dc06c2c8ec 100644
--- a/app/assets/javascripts/repository/components/delete_blob_modal.vue
+++ b/app/assets/javascripts/repository/components/delete_blob_modal.vue
@@ -71,6 +71,10 @@ export default {
       type: Boolean,
       required: true,
     },
+    canPushToBranch: {
+      type: Boolean,
+      required: true,
+    },
     emptyRepo: {
       type: Boolean,
       required: true,
@@ -176,9 +180,12 @@ export default {
       </template>
       <template v-else>
         <input type="hidden" name="original_branch" :value="originalBranch" />
-        <!-- Once "push to branch" permission is made available, will need to add to conditional
-          Follow-up issue: https://gitlab.com/gitlab-org/gitlab/-/issues/335462 -->
-        <input v-if="createNewMr" type="hidden" name="create_merge_request" value="1" />
+        <input
+          v-if="createNewMr || !canPushToBranch"
+          type="hidden"
+          name="create_merge_request"
+          value="1"
+        />
         <gl-form-group
           :label="$options.i18n.COMMIT_LABEL"
           label-for="commit_message"
diff --git a/app/assets/javascripts/repository/queries/blob_info.query.graphql b/app/assets/javascripts/repository/queries/blob_info.query.graphql
index cf3892802fdec..539719175e35f 100644
--- a/app/assets/javascripts/repository/queries/blob_info.query.graphql
+++ b/app/assets/javascripts/repository/queries/blob_info.query.graphql
@@ -28,6 +28,7 @@ query getBlobInfo($projectPath: ID!, $filePath: String!, $ref: String!) {
           forkAndEditPath
           ideForkAndEditPath
           canModifyBlob
+          canCurrentUserPushToBranch
           storedExternally
           rawPath
           replacePath
diff --git a/app/graphql/types/repository/blob_type.rb b/app/graphql/types/repository/blob_type.rb
index 104171e6772d3..cd4993ea86ddd 100644
--- a/app/graphql/types/repository/blob_type.rb
+++ b/app/graphql/types/repository/blob_type.rb
@@ -91,6 +91,9 @@ class BlobType < BaseObject
             calls_gitaly: true,
             description: 'Whether the current user can modify the blob.'
 
+      field :can_current_user_push_to_branch, GraphQL::Types::Boolean, null: true, method: :can_current_user_push_to_branch?,
+            description: 'Whether the current user can push to the branch.'
+
       def raw_text_blob
         object.data unless object.binary?
       end
diff --git a/app/presenters/blob_presenter.rb b/app/presenters/blob_presenter.rb
index 5835a77d0b90d..3555c6c3d0cbf 100644
--- a/app/presenters/blob_presenter.rb
+++ b/app/presenters/blob_presenter.rb
@@ -78,6 +78,12 @@ def can_modify_blob?
     super(blob, project, blob.commit_id)
   end
 
+  def can_current_user_push_to_branch?
+    return false unless current_user && project.repository.branch_exists?(blob.commit_id)
+
+    user_access(project).can_push_to_branch?(blob.commit_id)
+  end
+
   def ide_edit_path
     super(project, blob.commit_id, blob.path)
   end
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index d49eee5d15c34..c4167e3ecd215 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -14072,6 +14072,7 @@ Returns [`Tree`](#tree).
 
 | Name | Type | Description |
 | ---- | ---- | ----------- |
+| <a id="repositoryblobcancurrentuserpushtobranch"></a>`canCurrentUserPushToBranch` | [`Boolean`](#boolean) | Whether the current user can push to the branch. |
 | <a id="repositoryblobcanmodifyblob"></a>`canModifyBlob` | [`Boolean`](#boolean) | Whether the current user can modify the blob. |
 | <a id="repositoryblobeditblobpath"></a>`editBlobPath` | [`String`](#string) | Web path to edit the blob in the old-style editor. |
 | <a id="repositoryblobexternalstorageurl"></a>`externalStorageUrl` | [`String`](#string) | Web path to download the raw blob via external storage, if enabled. |
diff --git a/ee/spec/frontend/repository/components/blob_button_group_spec.js b/ee/spec/frontend/repository/components/blob_button_group_spec.js
index 3db9e53c65949..8719c5316db56 100644
--- a/ee/spec/frontend/repository/components/blob_button_group_spec.js
+++ b/ee/spec/frontend/repository/components/blob_button_group_spec.js
@@ -6,6 +6,7 @@ const DEFAULT_PROPS = {
   name: 'some name',
   path: 'some/path',
   canPushCode: true,
+  canPushToBranch: true,
   replacePath: 'some/replace/path',
   deletePath: 'some/delete/path',
   emptyRepo: false,
diff --git a/spec/frontend/repository/components/blob_button_group_spec.js b/spec/frontend/repository/components/blob_button_group_spec.js
index f2a3354f204c6..9f9d574a8ed37 100644
--- a/spec/frontend/repository/components/blob_button_group_spec.js
+++ b/spec/frontend/repository/components/blob_button_group_spec.js
@@ -9,6 +9,7 @@ const DEFAULT_PROPS = {
   name: 'some name',
   path: 'some/path',
   canPushCode: true,
+  canPushToBranch: true,
   replacePath: 'some/replace/path',
   deletePath: 'some/delete/path',
   emptyRepo: false,
diff --git a/spec/frontend/repository/components/delete_blob_modal_spec.js b/spec/frontend/repository/components/delete_blob_modal_spec.js
index 2c62868f3916f..785783b2e7588 100644
--- a/spec/frontend/repository/components/delete_blob_modal_spec.js
+++ b/spec/frontend/repository/components/delete_blob_modal_spec.js
@@ -13,6 +13,7 @@ const initialProps = {
   targetBranch: 'some-target-branch',
   originalBranch: 'main',
   canPushCode: true,
+  canPushToBranch: true,
   emptyRepo: false,
 };
 
@@ -103,22 +104,25 @@ describe('DeleteBlobModal', () => {
     );
 
     it.each`
-      input                     | value                          | emptyRepo | canPushCode | exist
-      ${'authenticity_token'}   | ${'mock-csrf-token'}           | ${false}  | ${true}     | ${true}
-      ${'authenticity_token'}   | ${'mock-csrf-token'}           | ${true}   | ${false}    | ${true}
-      ${'_method'}              | ${'delete'}                    | ${false}  | ${true}     | ${true}
-      ${'_method'}              | ${'delete'}                    | ${true}   | ${false}    | ${true}
-      ${'original_branch'}      | ${initialProps.originalBranch} | ${false}  | ${true}     | ${true}
-      ${'original_branch'}      | ${undefined}                   | ${true}   | ${true}     | ${false}
-      ${'create_merge_request'} | ${'1'}                         | ${false}  | ${false}    | ${true}
-      ${'create_merge_request'} | ${'1'}                         | ${false}  | ${true}     | ${true}
-      ${'create_merge_request'} | ${undefined}                   | ${true}   | ${false}    | ${false}
+      input                     | value                          | emptyRepo | canPushCode | canPushToBranch | exist
+      ${'authenticity_token'}   | ${'mock-csrf-token'}           | ${false}  | ${true}     | ${true}         | ${true}
+      ${'authenticity_token'}   | ${'mock-csrf-token'}           | ${true}   | ${false}    | ${true}         | ${true}
+      ${'_method'}              | ${'delete'}                    | ${false}  | ${true}     | ${true}         | ${true}
+      ${'_method'}              | ${'delete'}                    | ${true}   | ${false}    | ${true}         | ${true}
+      ${'original_branch'}      | ${initialProps.originalBranch} | ${false}  | ${true}     | ${true}         | ${true}
+      ${'original_branch'}      | ${undefined}                   | ${true}   | ${true}     | ${true}         | ${false}
+      ${'create_merge_request'} | ${'1'}                         | ${false}  | ${false}    | ${true}         | ${true}
+      ${'create_merge_request'} | ${'1'}                         | ${false}  | ${true}     | ${true}         | ${true}
+      ${'create_merge_request'} | ${'1'}                         | ${false}  | ${false}    | ${false}        | ${true}
+      ${'create_merge_request'} | ${'1'}                         | ${false}  | ${false}    | ${true}         | ${true}
+      ${'create_merge_request'} | ${undefined}                   | ${true}   | ${false}    | ${true}         | ${false}
     `(
       'passes $input as a hidden input with the correct value',
-      ({ input, value, emptyRepo, canPushCode, exist }) => {
+      ({ input, value, emptyRepo, canPushCode, canPushToBranch, exist }) => {
         createComponent({
           emptyRepo,
           canPushCode,
+          canPushToBranch,
         });
 
         const inputMethod = findForm().find(`input[name="${input}"]`);
diff --git a/spec/frontend/repository/mock_data.js b/spec/frontend/repository/mock_data.js
index adf5991ac3c8e..99018de6efd85 100644
--- a/spec/frontend/repository/mock_data.js
+++ b/spec/frontend/repository/mock_data.js
@@ -11,6 +11,7 @@ export const simpleViewerMock = {
   forkAndEditPath: 'some_file.js/fork/edit',
   ideForkAndEditPath: 'some_file.js/fork/ide',
   canModifyBlob: true,
+  canCurrentUserPushToBranch: true,
   storedExternally: false,
   rawPath: 'some_file.js',
   replacePath: 'some_file.js/replace',
diff --git a/spec/graphql/types/repository/blob_type_spec.rb b/spec/graphql/types/repository/blob_type_spec.rb
index 7f37237f355b2..968cb026401a1 100644
--- a/spec/graphql/types/repository/blob_type_spec.rb
+++ b/spec/graphql/types/repository/blob_type_spec.rb
@@ -28,6 +28,7 @@
       :rich_viewer,
       :plain_data,
       :can_modify_blob,
+      :can_current_user_push_to_branch,
       :ide_edit_path,
       :external_storage_url,
       :fork_and_edit_path,
diff --git a/spec/presenters/blob_presenter_spec.rb b/spec/presenters/blob_presenter_spec.rb
index 28e18708eab1d..cd38e74e0eaf3 100644
--- a/spec/presenters/blob_presenter_spec.rb
+++ b/spec/presenters/blob_presenter_spec.rb
@@ -31,6 +31,28 @@
     it { expect(presenter.replace_path).to eq("/#{project.full_path}/-/create/#{blob.commit_id}/#{blob.path}") }
   end
 
+  describe '#can_current_user_push_to_branch' do
+    let(:branch_exists) { true }
+
+    before do
+      allow(project.repository).to receive(:branch_exists?).with(blob.commit_id).and_return(branch_exists)
+    end
+
+    it { expect(presenter.can_current_user_push_to_branch?).to eq(true) }
+
+    context 'current_user is nil' do
+      let(:user) { nil }
+
+      it { expect(presenter.can_current_user_push_to_branch?).to eq(false) }
+    end
+
+    context 'branch does not exist' do
+      let(:branch_exists) { false }
+
+      it { expect(presenter.can_current_user_push_to_branch?).to eq(false) }
+    end
+  end
+
   describe '#pipeline_editor_path' do
     context 'when blob is .gitlab-ci.yml' do
       before do
-- 
GitLab