Commit 3eb03540 authored by Rémy Coutable's avatar Rémy Coutable

Make Git history follow renames again by performing the --skip in Ruby

This hack is needed because of an issue when --follow and --skip
are passed to git log at the same time.

See https://gitlab.com/gitlab-org/gitlab-ce/issues/23062Signed-off-by: default avatarRémy Coutable <remy@rymai.me>
parent 43fa9c1f
...@@ -109,9 +109,7 @@ class Repository ...@@ -109,9 +109,7 @@ class Repository
offset: offset, offset: offset,
after: after, after: after,
before: before, before: before,
# --follow doesn't play well with --skip. See: follow: path.present?,
# https://gitlab.com/gitlab-org/gitlab-ce/issues/3574#note_3040520
follow: false,
skip_merges: skip_merges skip_merges: skip_merges
} }
......
---
title: Make Git history follow renames again by performing the --skip in Ruby
merge_request:
author:
...@@ -324,24 +324,30 @@ module Gitlab ...@@ -324,24 +324,30 @@ module Gitlab
end end
def log_by_shell(sha, options) def log_by_shell(sha, options)
limit = options[:limit].to_i
offset = options[:offset].to_i
use_follow_flag = options[:follow] && options[:path].present?
# We will perform the offset in Ruby because --follow doesn't play well with --skip.
# See: https://gitlab.com/gitlab-org/gitlab-ce/issues/3574#note_3040520
offset_in_ruby = use_follow_flag && options[:offset].present?
limit += offset if offset_in_ruby
cmd = %W(#{Gitlab.config.git.bin_path} --git-dir=#{path} log) cmd = %W(#{Gitlab.config.git.bin_path} --git-dir=#{path} log)
cmd += %W(-n #{options[:limit].to_i}) cmd += %W(-n #{limit})
cmd += %w(--format=%H) cmd += %w(--format=%H)
cmd += %W(--skip=#{options[:offset].to_i}) cmd += %W(--skip=#{offset}) unless offset_in_ruby
cmd += %w(--follow) if options[:follow] cmd += %w(--follow) if use_follow_flag
cmd += %w(--no-merges) if options[:skip_merges] cmd += %w(--no-merges) if options[:skip_merges]
cmd += %W(--after=#{options[:after].iso8601}) if options[:after] cmd += %W(--after=#{options[:after].iso8601}) if options[:after]
cmd += %W(--before=#{options[:before].iso8601}) if options[:before] cmd += %W(--before=#{options[:before].iso8601}) if options[:before]
cmd += [sha] cmd += [sha]
cmd += %W(-- #{options[:path]}) if options[:path].present? cmd += %W(-- #{options[:path]}) if options[:path].present?
raw_output = IO.popen(cmd) {|io| io.read } raw_output = IO.popen(cmd) { |io| io.read }
lines = offset_in_ruby ? raw_output.lines.drop(offset) : raw_output.lines
log = raw_output.lines.map do |c|
Rugged::Commit.new(rugged, c.strip)
end
log.is_a?(Array) ? log : [] lines.map { |c| Rugged::Commit.new(rugged, c.strip) }
end end
def sha_from_ref(ref) def sha_from_ref(ref)
......
...@@ -546,7 +546,7 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -546,7 +546,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
repository.log(options.merge(path: "encoding")) repository.log(options.merge(path: "encoding"))
end end
it "should not follow renames" do it "does not follow renames" do
expect(log_commits).to include(commit_with_new_name) expect(log_commits).to include(commit_with_new_name)
expect(log_commits).to include(rename_commit) expect(log_commits).to include(rename_commit)
expect(log_commits).not_to include(commit_with_old_name) expect(log_commits).not_to include(commit_with_old_name)
...@@ -554,33 +554,107 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -554,33 +554,107 @@ describe Gitlab::Git::Repository, seed_helper: true do
end end
context "and 'path' is a file that matches the new filename" do context "and 'path' is a file that matches the new filename" do
context 'without offset' do
let(:log_commits) do let(:log_commits) do
repository.log(options.merge(path: "encoding/CHANGELOG")) repository.log(options.merge(path: "encoding/CHANGELOG"))
end end
it "should follow renames" do it "follows renames" do
expect(log_commits).to include(commit_with_new_name) expect(log_commits).to include(commit_with_new_name)
expect(log_commits).to include(rename_commit) expect(log_commits).to include(rename_commit)
expect(log_commits).to include(commit_with_old_name) expect(log_commits).to include(commit_with_old_name)
end end
end end
context "and 'path' is a file that matches the old filename" do context 'with offset=1' do
let(:log_commits) do let(:log_commits) do
repository.log(options.merge(path: "CHANGELOG")) repository.log(options.merge(path: "encoding/CHANGELOG", offset: 1))
end end
it "should not follow renames" do it "follows renames and skip the latest commit" do
expect(log_commits).not_to include(commit_with_new_name)
expect(log_commits).to include(rename_commit)
expect(log_commits).to include(commit_with_old_name) expect(log_commits).to include(commit_with_old_name)
end
end
context 'with offset=1', 'and limit=1' do
let(:log_commits) do
repository.log(options.merge(path: "encoding/CHANGELOG", offset: 1, limit: 1))
end
it "follows renames, skip the latest commit and return only one commit" do
expect(log_commits).not_to include(commit_with_new_name)
expect(log_commits).to include(rename_commit) expect(log_commits).to include(rename_commit)
expect(log_commits).not_to include(commit_with_old_name)
end
end
context 'with offset=1', 'and limit=2' do
let(:log_commits) do
repository.log(options.merge(path: "encoding/CHANGELOG", offset: 1, limit: 2))
end
it "follows renames, skip the latest commit and return only two commits" do
expect(log_commits).not_to include(commit_with_new_name)
expect(log_commits).to include(rename_commit)
expect(log_commits).to include(commit_with_old_name)
end
end
context 'with offset=2' do
let(:log_commits) do
repository.log(options.merge(path: "encoding/CHANGELOG", offset: 2))
end
it "follows renames and skip the latest commit" do
expect(log_commits).not_to include(commit_with_new_name) expect(log_commits).not_to include(commit_with_new_name)
expect(log_commits).not_to include(rename_commit)
expect(log_commits).to include(commit_with_old_name)
end
end
context 'with offset=2', 'and limit=1' do
let(:log_commits) do
repository.log(options.merge(path: "encoding/CHANGELOG", offset: 2, limit: 1))
end
it "follows renames, skip the two latest commit and return only one commit" do
expect(log_commits).not_to include(commit_with_new_name)
expect(log_commits).not_to include(rename_commit)
expect(log_commits).to include(commit_with_old_name)
end
end
context 'with offset=2', 'and limit=2' do
let(:log_commits) do
repository.log(options.merge(path: "encoding/CHANGELOG", offset: 2, limit: 2))
end
it "follows renames, skip the two latest commit and return only one commit" do
expect(log_commits).not_to include(commit_with_new_name)
expect(log_commits).not_to include(rename_commit)
expect(log_commits).to include(commit_with_old_name)
end
end
end
context "and 'path' is a file that matches the old filename" do
let(:log_commits) do
repository.log(options.merge(path: "CHANGELOG"))
end
it "does not follow renames" do
expect(log_commits).not_to include(commit_with_new_name)
expect(log_commits).to include(rename_commit)
expect(log_commits).to include(commit_with_old_name)
end end
end end
context "unknown ref" do context "unknown ref" do
let(:log_commits) { repository.log(options.merge(ref: 'unknown')) } let(:log_commits) { repository.log(options.merge(ref: 'unknown')) }
it "should return empty" do it "return an empty array" do
expect(log_commits).to eq([]) expect(log_commits).to eq([])
end end
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment