diff --git a/CHANGELOG b/CHANGELOG
index aa14f49bc8e8307a6c95307a3f925b975c07eca2..27eef858d787ac6af4e8ccb0aa427d92ab24b788 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
 v 10.3.0 (unreleased)
   - Remove Repository#format_patch
   - Remove Repository#add_tag
+  - Rename a repository file
 
 v 10.2.3
   - Handle nil blob data
diff --git a/Gemfile b/Gemfile
index 038a3e8df8c476479335eecc4d2dcc3053b07a25..394be07f7df19a7dc1878bb149d3970e1ff61b3b 100644
--- a/Gemfile
+++ b/Gemfile
@@ -4,6 +4,7 @@ gemspec
 
 group :development do
   gem 'rubocop'
+  gem 'rubocop-rspec', '~> 1.5.0'
   gem 'coveralls', require: false
   gem 'rspec', '~> 3.0'
   gem 'rspec-mocks'
diff --git a/lib/gitlab_git/blob.rb b/lib/gitlab_git/blob.rb
index 94c0307c5835984a212b88b9d8b89c2667a7f422..52d1138e4ed2a1964a1326a046db3947aba52a21 100644
--- a/lib/gitlab_git/blob.rb
+++ b/lib/gitlab_git/blob.rb
@@ -150,6 +150,12 @@ module Gitlab
           if action == :remove
             index.remove(filename)
           else
+            if action == :rename
+              old_path_name = PathHelper.normalize_path(file[:previous_path])
+              old_filename = old_path_name.to_s
+              index.remove(old_filename)
+            end
+
             mode = 0o100644
             file_entry = index.get(filename)
 
@@ -207,6 +213,35 @@ module Gitlab
         def remove(repository, options)
           commit(repository, options, :remove)
         end
+
+
+        # Rename file from repository and return commit sha
+        #
+        # options should contain next structure:
+        #   file: {
+        #     previous_path: 'documents/old_story.txt'
+        #     path: 'documents/story.txt'
+        #     content: 'Lorem ipsum...',
+        #     update: true
+        #   },
+        #   author: {
+        #     email: 'user@example.com',
+        #     name: 'Test User',
+        #     time: Time.now
+        #   },
+        #   committer: {
+        #     email: 'user@example.com',
+        #     name: 'Test User',
+        #     time: Time.now
+        #   },
+        #   commit: {
+        #     message: 'Rename FILENAME',
+        #     branch: 'master'
+        #   }
+        #
+        def rename(repository, options)
+          commit(repository, options, :rename)
+        end
       end
 
       def initialize(options)
diff --git a/spec/blob_spec.rb b/spec/blob_spec.rb
index 6998ea37baf6c88c124d883f09c81775ef274cfd..0af42c9f0386b25c77acbf66523a729d1e0665fb 100644
--- a/spec/blob_spec.rb
+++ b/spec/blob_spec.rb
@@ -270,6 +270,57 @@ describe Gitlab::Git::Blob do
     end
   end
 
+  describe :rename do
+    let(:repository) { Gitlab::Git::Repository.new(TEST_NORMAL_REPO_PATH) }
+
+    let(:commit_options) do
+      options = {
+        file: {
+          path: 'NEWREADME.md',
+          previous_path: 'README.md',
+          content: 'Lorem ipsum...',
+          update: true
+        },
+        author: {
+          email: 'user@example.com',
+          name: 'Test User',
+          time: Time.now
+        },
+        committer: {
+          email: 'user@example.com',
+          name: 'Test User',
+          time: Time.now
+        },
+        commit: {
+          message: 'Rename readme',
+          branch: 'master'
+        }
+      }
+    end
+
+    let!(:ref) { commit_options[:commit][:branch] }
+    let!(:prev_commit_count) { repository.commit_count(ref) }
+    let(:commit_sha) { Gitlab::Git::Blob.rename(repository, commit_options) }
+    let(:commit_count) { repository.commit_count(ref) }
+    let(:commit) { repository.lookup(commit_sha) }
+    let(:blob) { Gitlab::Git::Blob.find(repository, commit_sha, "NEWREADME.md") }
+    let(:removed_blob) { Gitlab::Git::Blob.find(repository, commit_sha, "README.md") }
+
+    it 'should rename the file with commit' do
+      # Commit message valid
+      expect(commit.message).to eq('Rename readme')
+
+      # Only one commit was made
+      expect(commit_count).to eq(prev_commit_count + 1)
+
+      # Previous file was removed
+      expect(removed_blob).to be_nil
+
+      # File was renamed
+      expect(blob).not_to be_nil
+    end
+  end
+
   describe :remove do
     let(:repository) { Gitlab::Git::Repository.new(TEST_REPO_PATH) }
 
@@ -295,17 +346,16 @@ describe Gitlab::Git::Blob do
       }
     end
 
-    let!(:commit_sha) { Gitlab::Git::Blob.remove(repository, commit_options) }
-    let!(:commit) { repository.lookup(commit_sha) }
+    let(:commit_sha) { Gitlab::Git::Blob.remove(repository, commit_options) }
+    let(:commit) { repository.lookup(commit_sha) }
+    let(:blob) { Gitlab::Git::Blob.find(repository, commit_sha, "README.md") }
 
     it 'should remove file with commit' do
       # Commit message valid
       expect(commit.message).to eq('Remove readme')
 
       # File was removed
-      expect(commit.tree.to_a.any? do |tree|
-        tree[:name] == 'README.md'
-      end).to be_falsey
+      expect(blob).to be_nil
     end
   end