diff --git a/CHANGELOG b/CHANGELOG
index 99f8c51218b1b0bbba37ee03d779da0b80ea55e9..6eab5fb9f1b32bb7dec881fce359d82522641fac 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,6 @@
 v8.0.0
   - SSH certificate support (!207)
+  - Support Git v2 protocol (!217)
 
 v7.2.0
   - Update gitaly-proto to 0.109.0 (!216)
diff --git a/lib/gitlab_access_status.rb b/lib/gitlab_access_status.rb
index 44225aa4fe1d3da79e7d1fb4c923fc8c6a3318b5..c639462a0e8f151718c99537cbb435cc72805a39 100644
--- a/lib/gitlab_access_status.rb
+++ b/lib/gitlab_access_status.rb
@@ -1,9 +1,9 @@
 require 'json'
 
 class GitAccessStatus
-  attr_reader :message, :gl_repository, :gl_id, :gl_username, :repository_path, :gitaly
+  attr_reader :message, :gl_repository, :gl_id, :gl_username, :repository_path, :gitaly, :git_protocol
 
-  def initialize(status, message, gl_repository:, gl_id:, gl_username:, repository_path:, gitaly:)
+  def initialize(status, message, gl_repository:, gl_id:, gl_username:, repository_path:, gitaly:, git_protocol:)
     @status = status
     @message = message
     @gl_repository = gl_repository
@@ -11,6 +11,7 @@ class GitAccessStatus
     @gl_username = gl_username
     @repository_path = repository_path
     @gitaly = gitaly
+    @git_protocol = git_protocol
   end
 
   def self.create_from_json(json)
@@ -21,7 +22,8 @@ class GitAccessStatus
         gl_id: values["gl_id"],
         gl_username: values["gl_username"],
         repository_path: values["repository_path"],
-        gitaly: values["gitaly"])
+        gitaly: values["gitaly"],
+        git_protocol: values["git_protocol"])
   end
 
   def allowed?
diff --git a/lib/gitlab_net.rb b/lib/gitlab_net.rb
index 7013d799c6a15f758b9f624acdfd3171f2570355..9cb7e5654838d6b57979f9159dec4c202fc7b8aa 100644
--- a/lib/gitlab_net.rb
+++ b/lib/gitlab_net.rb
@@ -44,7 +44,8 @@ class GitlabNet # rubocop:disable Metrics/ClassLength
                           gl_id: nil,
                           gl_username: nil,
                           repository_path: nil,
-                          gitaly: nil)
+                          gitaly: nil,
+                          git_protocol: nil)
     end
   end
 
diff --git a/lib/gitlab_shell.rb b/lib/gitlab_shell.rb
index 9f05004c0841ba4f306d2872b6bc2b35974771cc..78fdfe8524620dac9ed0a40133909cfab233ac4c 100644
--- a/lib/gitlab_shell.rb
+++ b/lib/gitlab_shell.rb
@@ -18,7 +18,7 @@ class GitlabShell # rubocop:disable Metrics/ClassLength
   API_COMMANDS = %w(2fa_recovery_codes).freeze
   GL_PROTOCOL = 'ssh'.freeze
 
-  attr_accessor :gl_id, :gl_repository, :repo_name, :command, :git_access
+  attr_accessor :gl_id, :gl_repository, :repo_name, :command, :git_access, :git_protocol
   attr_reader :repo_path
 
   def initialize(who)
@@ -118,6 +118,7 @@ class GitlabShell # rubocop:disable Metrics/ClassLength
 
     self.repo_path = status.repository_path
     @gl_repository = status.gl_repository
+    @git_protocol = ENV['GIT_PROTOCOL']
     @gitaly = status.gitaly
     @username = status.gl_username
     if defined?(@who)
@@ -150,7 +151,8 @@ class GitlabShell # rubocop:disable Metrics/ClassLength
         'repository' => @gitaly['repository'],
         'gl_repository' => @gl_repository,
         'gl_id' => @gl_id,
-        'gl_username' => @username
+        'gl_username' => @username,
+        'git_protocol' => @git_protocol
       }
 
       args = [gitaly_address, JSON.dump(gitaly_request)]
diff --git a/spec/gitlab_access_spec.rb b/spec/gitlab_access_spec.rb
index d082176aa9f139b114322f0fc386e86ebda1f37e..f9717a7b13f063eae2919bce06929d892848fe46 100644
--- a/spec/gitlab_access_spec.rb
+++ b/spec/gitlab_access_spec.rb
@@ -3,8 +3,8 @@ require 'gitlab_access'
 
 describe GitlabAccess do
   let(:repository_path) { "/home/git/repositories" }
-  let(:repo_name)   { 'dzaporozhets/gitlab-ci' }
-  let(:repo_path)  { File.join(repository_path, repo_name) + ".git" }
+  let(:repo_name) { 'dzaporozhets/gitlab-ci' }
+  let(:repo_path) { File.join(repository_path, repo_name) + ".git" }
   let(:api) do
     double(GitlabNet).tap do |api|
       api.stub(check_access: GitAccessStatus.new(true,
@@ -13,7 +13,8 @@ describe GitlabAccess do
                                                  gl_id: 'user-123',
                                                  gl_username: 'testuser',
                                                  repository_path: '/home/git/repositories',
-                                                 gitaly: nil))
+                                                 gitaly: nil,
+                                                 git_protocol: 'version=2'))
     end
   end
   subject do
@@ -35,14 +36,12 @@ describe GitlabAccess do
 
   describe "#exec" do
     context "access is granted" do
-
       it "returns true" do
         expect(subject.exec).to be_truthy
       end
     end
 
     context "access is denied" do
-
       before do
         api.stub(check_access: GitAccessStatus.new(
                   false,
@@ -51,7 +50,8 @@ describe GitlabAccess do
                   gl_id: nil,
                   gl_username: nil,
                   repository_path: nil,
-                  gitaly: nil
+                  gitaly: nil,
+                  git_protocol: nil
                 ))
       end
 
@@ -61,7 +61,6 @@ describe GitlabAccess do
     end
 
     context "API connection fails" do
-
       before do
         api.stub(:check_access).and_raise(GitlabNet::ApiUnreachableError)
       end
diff --git a/spec/gitlab_shell_spec.rb b/spec/gitlab_shell_spec.rb
index 3f7c962c822b4345fb3b30d599844b19df1f58e5..382cad4e3cd0f88203bd2909729d40c83747b8ae 100644
--- a/spec/gitlab_shell_spec.rb
+++ b/spec/gitlab_shell_spec.rb
@@ -27,7 +27,8 @@ describe GitlabShell do
     gl_id: gl_id,
     gl_username: gl_username,
     repository_path: repo_path,
-    gitaly: { 'repository' => { 'relative_path' => repo_name, 'storage_name' => 'default'} , 'address' => 'unix:gitaly.socket' }
+    gitaly: { 'repository' => { 'relative_path' => repo_name, 'storage_name' => 'default'} , 'address' => 'unix:gitaly.socket' },
+    git_protocol: git_protocol
   )
   }
 
@@ -41,11 +42,12 @@ describe GitlabShell do
                 gl_id: gl_id,
                 gl_username: gl_username,
                 repository_path: repo_path,
-                gitaly: nil))
+                gitaly: nil,
+                git_protocol: git_protocol))
       api.stub(two_factor_recovery_codes: {
-        'success' => true,
-        'recovery_codes' => ['f67c514de60c4953', '41278385fc00c1e0']
-      })
+                 'success' => true,
+                 'recovery_codes' => %w[f67c514de60c4953 41278385fc00c1e0]
+               })
     end
   end
 
@@ -58,6 +60,7 @@ describe GitlabShell do
   let(:gl_repository) { 'project-1' }
   let(:gl_id) { 'user-1' }
   let(:gl_username) { 'testuser' }
+  let(:git_protocol) { 'version=2' }
 
   before do
     GitlabConfig.any_instance.stub(audit_usernames: false)
@@ -72,7 +75,7 @@ describe GitlabShell do
   describe :parse_cmd do
     describe 'git' do
       context 'w/o namespace' do
-        let(:ssh_args) { %W(git-upload-pack gitlab-ci.git) }
+        let(:ssh_args) { %w(git-upload-pack gitlab-ci.git) }
 
         before do
           subject.send :parse_cmd, ssh_args
@@ -84,7 +87,7 @@ describe GitlabShell do
 
       context 'namespace' do
         let(:repo_name) { 'dmitriy.zaporozhets/gitlab-ci.git' }
-        let(:ssh_args) { %W(git-upload-pack dmitriy.zaporozhets/gitlab-ci.git) }
+        let(:ssh_args) { %w(git-upload-pack dmitriy.zaporozhets/gitlab-ci.git) }
 
         before do
           subject.send :parse_cmd, ssh_args
@@ -95,7 +98,7 @@ describe GitlabShell do
       end
 
       context 'with an invalid number of arguments' do
-        let(:ssh_args) { %W(foobar) }
+        let(:ssh_args) { %w(foobar) }
 
         it "should raise an DisallowedCommandError" do
           expect { subject.send :parse_cmd, ssh_args }.to raise_error(GitlabShell::DisallowedCommandError)
@@ -123,7 +126,7 @@ describe GitlabShell do
 
     describe 'git-lfs' do
       let(:repo_name) { 'dzaporozhets/gitlab.git' }
-      let(:ssh_args) { %W(git-lfs-authenticate dzaporozhets/gitlab.git download) }
+      let(:ssh_args) { %w(git-lfs-authenticate dzaporozhets/gitlab.git download) }
 
       before do
         subject.send :parse_cmd, ssh_args
@@ -136,7 +139,7 @@ describe GitlabShell do
 
     describe 'git-lfs old clients' do
       let(:repo_name) { 'dzaporozhets/gitlab.git' }
-      let(:ssh_args) { %W(git-lfs-authenticate dzaporozhets/gitlab.git download long_oid) }
+      let(:ssh_args) { %w(git-lfs-authenticate dzaporozhets/gitlab.git download long_oid) }
 
       before do
         subject.send :parse_cmd, ssh_args
@@ -149,14 +152,26 @@ describe GitlabShell do
   end
 
   describe :exec do
-    let(:gitaly_message) { JSON.dump({ 'repository' => { 'relative_path' => repo_name, 'storage_name' => 'default' }, 'gl_repository' => gl_repository, 'gl_id' => gl_id, 'gl_username' => gl_username}) }
+    let(:gitaly_message) do
+      JSON.dump(
+        'repository' => { 'relative_path' => repo_name, 'storage_name' => 'default' },
+        'gl_repository' => gl_repository,
+        'gl_id' => gl_id,
+        'gl_username' => gl_username,
+        'git_protocol' => git_protocol
+      )
+    end
+
+    before do
+      allow(ENV).to receive(:[]).with('GIT_PROTOCOL').and_return(git_protocol)
+    end
 
     shared_examples_for 'upload-pack' do |command|
       let(:ssh_cmd) { "#{command} gitlab-ci.git" }
       after { subject.exec(ssh_cmd) }
 
       it "should process the command" do
-        subject.should_receive(:process_cmd).with(%W(git-upload-pack gitlab-ci.git))
+        subject.should_receive(:process_cmd).with(%w(git-upload-pack gitlab-ci.git))
       end
 
       it "should execute the command" do
@@ -185,13 +200,13 @@ describe GitlabShell do
 
     context 'gitaly-upload-pack' do
       let(:ssh_cmd) { "git-upload-pack gitlab-ci.git" }
-      before {
+      before do
         api.stub(check_access: gitaly_check_access)
-      }
+      end
       after { subject.exec(ssh_cmd) }
 
       it "should process the command" do
-        subject.should_receive(:process_cmd).with(%W(git-upload-pack gitlab-ci.git))
+        subject.should_receive(:process_cmd).with(%w(git-upload-pack gitlab-ci.git))
       end
 
       it "should execute the command" do
@@ -215,7 +230,7 @@ describe GitlabShell do
       after { subject.exec(ssh_cmd) }
 
       it "should process the command" do
-        subject.should_receive(:process_cmd).with(%W(git-receive-pack gitlab-ci.git))
+        subject.should_receive(:process_cmd).with(%w(git-receive-pack gitlab-ci.git))
       end
 
       it "should execute the command" do
@@ -231,13 +246,13 @@ describe GitlabShell do
 
     context 'gitaly-receive-pack' do
       let(:ssh_cmd) { "git-receive-pack gitlab-ci.git" }
-      before {
+      before do
         api.stub(check_access: gitaly_check_access)
-      }
+      end
       after { subject.exec(ssh_cmd) }
 
       it "should process the command" do
-        subject.should_receive(:process_cmd).with(%W(git-receive-pack gitlab-ci.git))
+        subject.should_receive(:process_cmd).with(%w(git-receive-pack gitlab-ci.git))
       end
 
       it "should execute the command" do
@@ -264,7 +279,7 @@ describe GitlabShell do
       after { subject.exec(ssh_cmd) }
 
       it "should process the command" do
-        subject.should_receive(:process_cmd).with(%W(git-upload-archive gitlab-ci.git))
+        subject.should_receive(:process_cmd).with(%w(git-upload-archive gitlab-ci.git))
       end
 
       it "should execute the command" do
@@ -341,9 +356,9 @@ describe GitlabShell do
     context "failed connection" do
       let(:ssh_cmd) { 'git-upload-pack gitlab-ci.git' }
 
-      before {
+      before do
         api.stub(:check_access).and_raise(GitlabNet::ApiUnreachableError)
-      }
+      end
       after { subject.exec(ssh_cmd) }
 
       it "should not process the command" do
@@ -382,9 +397,9 @@ describe GitlabShell do
         context 'when the process is unsuccessful' do
           it 'displays the error to the user' do
             api.stub(two_factor_recovery_codes: {
-              'success' => false,
-              'message' => 'Could not find the given key'
-            })
+                       'success' => false,
+                       'message' => 'Could not find the given key'
+                     })
 
             expect($stdout).to receive(:puts)
               .with(/Could not find the given key/)
@@ -412,7 +427,8 @@ describe GitlabShell do
                   gl_id: nil,
                   gl_username: nil,
                   repository_path: nil,
-                  gitaly: nil))
+                  gitaly: nil,
+                  git_protocol: nil))
         message = 'Access denied'
         user_string = "user with id #{gl_id}"
         $logger.should_receive(:warn).with(message, command: 'git-upload-pack gitlab-ci.git', user: user_string)
@@ -457,6 +473,7 @@ describe GitlabShell do
     before do
       Kernel.stub(:exec)
       shell.gl_repository = gl_repository
+      shell.git_protocol = git_protocol
       shell.instance_variable_set(:@username, gl_username)
     end