Commit b529ee53 authored by Stan Hu's avatar Stan Hu

Fix directory and last commit not loading for special filenames

Filenames that contained special pathspec characters (see
https://css-tricks.com/git-pathspecs-and-how-to-use-them) would not
return a valid last commit or properly load in the repository
browser. For example, `:wq` would cause the GraphQL endpoint to return a
null `lastCommit`, and a file `:wq/test.txt` would not show the commit
information in the repository browser.

This requires https://gitlab.com/gitlab-org/gitaly/-/merge_requests/2303
to work.
parent c3caf581
...@@ -454,7 +454,7 @@ group :ed25519 do ...@@ -454,7 +454,7 @@ group :ed25519 do
end end
# Gitaly GRPC protocol definitions # Gitaly GRPC protocol definitions
gem 'gitaly', '~> 13.2.0.pre.rc1' gem 'gitaly', '~> 13.2.0.pre.rc2'
gem 'grpc', '~> 1.24.0' gem 'grpc', '~> 1.24.0'
......
...@@ -377,7 +377,7 @@ GEM ...@@ -377,7 +377,7 @@ GEM
po_to_json (>= 1.0.0) po_to_json (>= 1.0.0)
rails (>= 3.2.0) rails (>= 3.2.0)
git (1.5.0) git (1.5.0)
gitaly (13.2.0.pre.rc1) gitaly (13.2.0.pre.rc2)
grpc (~> 1.0) grpc (~> 1.0)
github-markup (1.7.0) github-markup (1.7.0)
gitlab-chronic (0.10.5) gitlab-chronic (0.10.5)
...@@ -1239,7 +1239,7 @@ DEPENDENCIES ...@@ -1239,7 +1239,7 @@ DEPENDENCIES
gettext (~> 3.2.2) gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3) gettext_i18n_rails_js (~> 1.3)
gitaly (~> 13.2.0.pre.rc1) gitaly (~> 13.2.0.pre.rc2)
github-markup (~> 1.7.0) github-markup (~> 1.7.0)
gitlab-chronic (~> 0.10.5) gitlab-chronic (~> 0.10.5)
gitlab-labkit (= 0.12.0) gitlab-labkit (= 0.12.0)
......
...@@ -207,7 +207,7 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -207,7 +207,7 @@ class Projects::BlobController < Projects::ApplicationController
def set_last_commit_sha def set_last_commit_sha
@last_commit_sha = Gitlab::Git::Commit @last_commit_sha = Gitlab::Git::Commit
.last_for_path(@repository, @ref, @path).sha .last_for_path(@repository, @ref, @path, literal_pathspec: true).sha
end end
def show_html def show_html
......
...@@ -9,7 +9,7 @@ module Resolvers ...@@ -9,7 +9,7 @@ module Resolvers
def resolve(**args) def resolve(**args)
# Ensure merge commits can be returned by sending nil to Gitaly instead of '/' # Ensure merge commits can be returned by sending nil to Gitaly instead of '/'
path = tree.path == '/' ? nil : tree.path path = tree.path == '/' ? nil : tree.path
commit = Gitlab::Git::Commit.last_for_path(tree.repository, tree.sha, path) commit = Gitlab::Git::Commit.last_for_path(tree.repository, tree.sha, path, literal_pathspec: true)
::Commit.new(commit, tree.repository.project) if commit ::Commit.new(commit, tree.repository.project) if commit
end end
......
...@@ -149,7 +149,8 @@ class Repository ...@@ -149,7 +149,8 @@ class Repository
before: opts[:before], before: opts[:before],
all: !!opts[:all], all: !!opts[:all],
first_parent: !!opts[:first_parent], first_parent: !!opts[:first_parent],
order: opts[:order] order: opts[:order],
literal_pathspec: opts.fetch(:literal_pathspec, true)
} }
commits = Gitlab::Git::Commit.where(options) commits = Gitlab::Git::Commit.where(options)
...@@ -676,8 +677,8 @@ class Repository ...@@ -676,8 +677,8 @@ class Repository
end end
end end
def list_last_commits_for_tree(sha, path, offset: 0, limit: 25) def list_last_commits_for_tree(sha, path, offset: 0, limit: 25, literal_pathspec: false)
commits = raw_repository.list_last_commits_for_tree(sha, path, offset: offset, limit: limit) commits = raw_repository.list_last_commits_for_tree(sha, path, offset: offset, limit: limit, literal_pathspec: literal_pathspec)
commits.each do |path, commit| commits.each do |path, commit|
commits[path] = ::Commit.new(commit, container) commits[path] = ::Commit.new(commit, container)
......
...@@ -25,7 +25,7 @@ module Files ...@@ -25,7 +25,7 @@ module Files
return false unless commit_id return false unless commit_id
last_commit = Gitlab::Git::Commit last_commit = Gitlab::Git::Commit
.last_for_path(@start_project.repository, @start_branch, path) .last_for_path(@start_project.repository, @start_branch, path, literal_pathspec: true)
return false unless last_commit return false unless last_commit
......
---
title: Fix directory and last commit not loading for some filenames
merge_request: 34985
author:
type: fixed
...@@ -90,14 +90,15 @@ module Gitlab ...@@ -90,14 +90,15 @@ module Gitlab
# #
# Commit.last_for_path(repo, 'master', 'Gemfile') # Commit.last_for_path(repo, 'master', 'Gemfile')
# #
def last_for_path(repo, ref, path = nil) def last_for_path(repo, ref, path = nil, literal_pathspec: false)
# rubocop: disable Rails/FindBy # rubocop: disable Rails/FindBy
# This is not where..first from ActiveRecord # This is not where..first from ActiveRecord
where( where(
repo: repo, repo: repo,
ref: ref, ref: ref,
path: path, path: path,
limit: 1 limit: 1,
literal_pathspec: literal_pathspec
).first ).first
# rubocop: enable Rails/FindBy # rubocop: enable Rails/FindBy
end end
......
...@@ -1002,9 +1002,9 @@ module Gitlab ...@@ -1002,9 +1002,9 @@ module Gitlab
end end
end end
def list_last_commits_for_tree(sha, path, offset: 0, limit: 25) def list_last_commits_for_tree(sha, path, offset: 0, limit: 25, literal_pathspec: false)
wrapped_gitaly_errors do wrapped_gitaly_errors do
gitaly_commit_client.list_last_commits_for_tree(sha, path, offset: offset, limit: limit) gitaly_commit_client.list_last_commits_for_tree(sha, path, offset: offset, limit: limit, literal_pathspec: literal_pathspec)
end end
end end
......
...@@ -162,13 +162,14 @@ module Gitlab ...@@ -162,13 +162,14 @@ module Gitlab
[response.left_count, response.right_count] [response.left_count, response.right_count]
end end
def list_last_commits_for_tree(revision, path, offset: 0, limit: 25) def list_last_commits_for_tree(revision, path, offset: 0, limit: 25, literal_pathspec: false)
request = Gitaly::ListLastCommitsForTreeRequest.new( request = Gitaly::ListLastCommitsForTreeRequest.new(
repository: @gitaly_repo, repository: @gitaly_repo,
revision: encode_binary(revision), revision: encode_binary(revision),
path: encode_binary(path.to_s), path: encode_binary(path.to_s),
offset: offset, offset: offset,
limit: limit limit: limit,
global_options: parse_global_options!(literal_pathspec: literal_pathspec)
) )
response = GitalyClient.call(@repository.storage, :commit_service, :list_last_commits_for_tree, request, timeout: GitalyClient.medium_timeout) response = GitalyClient.call(@repository.storage, :commit_service, :list_last_commits_for_tree, request, timeout: GitalyClient.medium_timeout)
...@@ -185,7 +186,7 @@ module Gitlab ...@@ -185,7 +186,7 @@ module Gitlab
repository: @gitaly_repo, repository: @gitaly_repo,
revision: encode_binary(revision), revision: encode_binary(revision),
path: encode_binary(path.to_s), path: encode_binary(path.to_s),
literal_pathspec: literal_pathspec global_options: parse_global_options!(literal_pathspec: literal_pathspec)
) )
gitaly_commit = GitalyClient.call(@repository.storage, :commit_service, :last_commit_for_path, request, timeout: GitalyClient.fast_timeout).commit gitaly_commit = GitalyClient.call(@repository.storage, :commit_service, :last_commit_for_path, request, timeout: GitalyClient.fast_timeout).commit
...@@ -244,14 +245,15 @@ module Gitlab ...@@ -244,14 +245,15 @@ module Gitlab
[] []
end end
def commits_by_message(query, revision: '', path: '', limit: 1000, offset: 0) def commits_by_message(query, revision: '', path: '', limit: 1000, offset: 0, literal_pathspec: true)
request = Gitaly::CommitsByMessageRequest.new( request = Gitaly::CommitsByMessageRequest.new(
repository: @gitaly_repo, repository: @gitaly_repo,
query: query, query: query,
revision: encode_binary(revision), revision: encode_binary(revision),
path: encode_binary(path), path: encode_binary(path),
limit: limit.to_i, limit: limit.to_i,
offset: offset.to_i offset: offset.to_i,
global_options: parse_global_options!(literal_pathspec: literal_pathspec)
) )
GitalyClient.streaming_call(@repository.storage, :commit_service, :commits_by_message, request, timeout: GitalyClient.medium_timeout) do |response| GitalyClient.streaming_call(@repository.storage, :commit_service, :commits_by_message, request, timeout: GitalyClient.medium_timeout) do |response|
...@@ -321,6 +323,7 @@ module Gitlab ...@@ -321,6 +323,7 @@ module Gitlab
skip_merges: options[:skip_merges], skip_merges: options[:skip_merges],
all: !!options[:all], all: !!options[:all],
first_parent: !!options[:first_parent], first_parent: !!options[:first_parent],
global_options: parse_global_options!(options),
disable_walk: true # This option is deprecated. The 'walk' implementation is being removed. disable_walk: true # This option is deprecated. The 'walk' implementation is being removed.
) )
request.after = GitalyClient.timestamp(options[:after]) if options[:after] request.after = GitalyClient.timestamp(options[:after]) if options[:after]
...@@ -408,6 +411,11 @@ module Gitlab ...@@ -408,6 +411,11 @@ module Gitlab
private private
def parse_global_options!(options)
literal_pathspec = options.delete(:literal_pathspec)
Gitaly::GlobalOptions.new(literal_pathspecs: literal_pathspec)
end
def call_commit_diff(request_params, options = {}) def call_commit_diff(request_params, options = {})
request_params[:ignore_whitespace_change] = options.fetch(:ignore_whitespace_change, false) request_params[:ignore_whitespace_change] = options.fetch(:ignore_whitespace_change, false)
request_params[:enforce_limits] = options.fetch(:limits, true) request_params[:enforce_limits] = options.fetch(:limits, true)
......
...@@ -97,7 +97,7 @@ module Gitlab ...@@ -97,7 +97,7 @@ module Gitlab
File.join(*[path, ""]) File.join(*[path, ""])
end end
commits_hsh = repository.list_last_commits_for_tree(commit.id, ensured_path, offset: offset, limit: limit) commits_hsh = repository.list_last_commits_for_tree(commit.id, ensured_path, offset: offset, limit: limit, literal_pathspec: true)
prerender_commit_full_titles!(commits_hsh.values) prerender_commit_full_titles!(commits_hsh.values)
entries.each do |entry| entries.each do |entry|
......
...@@ -137,6 +137,33 @@ RSpec.describe 'User browses commits' do ...@@ -137,6 +137,33 @@ RSpec.describe 'User browses commits' do
.and have_selector('entry summary', text: commit.description[0..10].delete("\r\n")) .and have_selector('entry summary', text: commit.description[0..10].delete("\r\n"))
end end
context "when commit has a filename with pathspec characters" do
let(:path) { ':wq' }
let(:filename) { File.join(path, 'test.txt') }
let(:ref) { project.repository.root_ref }
let(:newrev) { project.repository.commit('master').sha }
let(:short_newrev) { project.repository.commit('master').short_id }
let(:message) { 'Glob characters'}
before do
create_file_in_repo(project, ref, ref, filename, 'Test file', commit_message: message)
visit project_commits_path(project, "#{ref}/#{path}", limit: 1)
wait_for_requests
end
it 'searches commit', :js do
expect(page).to have_content(message)
fill_in 'commits-search', with: 'bogus12345'
expect(page).to have_content "Your search didn't match any commits"
fill_in 'commits-search', with: 'Glob'
expect(page).to have_content message
end
end
context 'when a commit links to a confidential issue' do context 'when a commit links to a confidential issue' do
let(:confidential_issue) { create(:issue, confidential: true, title: 'Secret issue!', project: project) } let(:confidential_issue) { create(:issue, confidential: true, title: 'Secret issue!', project: project) }
......
...@@ -341,7 +341,7 @@ RSpec.describe "User browses files" do ...@@ -341,7 +341,7 @@ RSpec.describe "User browses files" do
end end
end end
context "when browsing a file with glob characters" do context "when browsing a file with pathspec characters" do
let(:filename) { ':wq' } let(:filename) { ':wq' }
let(:newrev) { project.repository.commit('master').sha } let(:newrev) { project.repository.commit('master').sha }
......
...@@ -49,10 +49,11 @@ RSpec.describe 'Projects tree', :js do ...@@ -49,10 +49,11 @@ RSpec.describe 'Projects tree', :js do
expect(page).not_to have_selector('.flash-alert') expect(page).not_to have_selector('.flash-alert')
end end
context "with a tree that contains glob characters" do context "with a tree that contains pathspec characters" do
let(:path) { ':wq' } let(:path) { ':wq' }
let(:filename) { File.join(path, 'test.txt') } let(:filename) { File.join(path, 'test.txt') }
let(:newrev) { project.repository.commit('master').sha } let(:newrev) { project.repository.commit('master').sha }
let(:short_newrev) { project.repository.commit('master').short_id }
let(:message) { 'Glob characters'} let(:message) { 'Glob characters'}
before do before do
...@@ -61,11 +62,14 @@ RSpec.describe 'Projects tree', :js do ...@@ -61,11 +62,14 @@ RSpec.describe 'Projects tree', :js do
wait_for_requests wait_for_requests
end end
# Disabled until https://gitlab.com/gitlab-org/gitaly/-/issues/2888 is resolved it "renders tree table without errors" do
xit "renders tree table without errors" do
expect(page).to have_selector('.tree-item') expect(page).to have_selector('.tree-item')
expect(page).to have_content('test.txt') expect(page).to have_content('test.txt')
expect(page).to have_content(message) expect(page).to have_content(message)
# Check last commit
expect(find('.commit-content').text).to include(message)
expect(find('.commit-sha-group').text).to eq(short_newrev)
end end
end end
......
...@@ -4,8 +4,10 @@ require 'spec_helper' ...@@ -4,8 +4,10 @@ require 'spec_helper'
RSpec.describe Resolvers::LastCommitResolver do RSpec.describe Resolvers::LastCommitResolver do
include GraphqlHelpers include GraphqlHelpers
include RepoHelpers
let(:repository) { create(:project, :repository).repository } let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
let(:tree) { repository.tree(ref, path) } let(:tree) { repository.tree(ref, path) }
let(:commit) { resolve(described_class, obj: tree) } let(:commit) { resolve(described_class, obj: tree) }
...@@ -29,6 +31,28 @@ RSpec.describe Resolvers::LastCommitResolver do ...@@ -29,6 +31,28 @@ RSpec.describe Resolvers::LastCommitResolver do
end end
end end
context 'last commit for a wildcard pathspec' do
let(:ref) { 'fix' }
let(:path) { 'files/*' }
it 'returns nil' do
expect(commit).to be_nil
end
end
context 'last commit with pathspec characters' do
let(:ref) { 'fix' }
let(:path) { ':wq' }
before do
create_file_in_repo(project, ref, ref, path, 'Test file')
end
it 'resolves commit' do
expect(commit).to eq(repository.commits(ref, path: path, limit: 1).last)
end
end
context 'last commit does not exist' do context 'last commit does not exist' do
let(:ref) { 'master' } let(:ref) { 'master' }
let(:path) { 'does-not-exist' } let(:path) { 'does-not-exist' }
......
...@@ -227,6 +227,34 @@ RSpec.describe Gitlab::Git::Commit, :seed_helper do ...@@ -227,6 +227,34 @@ RSpec.describe Gitlab::Git::Commit, :seed_helper do
end end
end end
context 'pathspec' do
let(:pathspec) { 'files/ruby/*' }
context 'with default literal_pathspec value' do
it 'finds the seed commit' do
commit = described_class.last_for_path(repository, 'master', pathspec)
expect(commit.id).to eq(SeedRepo::Commit::ID)
end
end
context 'with literal_pathspec set to false' do
it 'finds the seed commit' do
commit = described_class.last_for_path(repository, 'master', pathspec, literal_pathspec: false)
expect(commit.id).to eq(SeedRepo::Commit::ID)
end
end
context 'with literal_pathspec set to true' do
it 'does not find the seed commit' do
commit = described_class.last_for_path(repository, 'master', pathspec, literal_pathspec: true)
expect(commit).to be_nil
end
end
end
context 'ref + path' do context 'ref + path' do
subject { described_class.last_for_path(repository, SeedRepo::Commit::ID, 'encoding') } subject { described_class.last_for_path(repository, SeedRepo::Commit::ID, 'encoding') }
......
...@@ -290,7 +290,8 @@ RSpec.describe Gitlab::GitalyClient::CommitService do ...@@ -290,7 +290,8 @@ RSpec.describe Gitlab::GitalyClient::CommitService do
request = Gitaly::FindCommitsRequest.new( request = Gitaly::FindCommitsRequest.new(
repository: repository_message, repository: repository_message,
disable_walk: true, disable_walk: true,
order: 'NONE' order: 'NONE',
global_options: Gitaly::GlobalOptions.new(literal_pathspecs: false)
) )
expect_any_instance_of(Gitaly::CommitService::Stub).to receive(:find_commits) expect_any_instance_of(Gitaly::CommitService::Stub).to receive(:find_commits)
...@@ -303,7 +304,8 @@ RSpec.describe Gitlab::GitalyClient::CommitService do ...@@ -303,7 +304,8 @@ RSpec.describe Gitlab::GitalyClient::CommitService do
request = Gitaly::FindCommitsRequest.new( request = Gitaly::FindCommitsRequest.new(
repository: repository_message, repository: repository_message,
disable_walk: true, disable_walk: true,
order: 'TOPO' order: 'TOPO',
global_options: Gitaly::GlobalOptions.new(literal_pathspecs: false)
) )
expect_any_instance_of(Gitaly::CommitService::Stub).to receive(:find_commits) expect_any_instance_of(Gitaly::CommitService::Stub).to receive(:find_commits)
...@@ -317,7 +319,8 @@ RSpec.describe Gitlab::GitalyClient::CommitService do ...@@ -317,7 +319,8 @@ RSpec.describe Gitlab::GitalyClient::CommitService do
repository: repository_message, repository: repository_message,
disable_walk: true, disable_walk: true,
order: 'NONE', order: 'NONE',
author: "Billy Baggins <bilbo@shire.com>" author: "Billy Baggins <bilbo@shire.com>",
global_options: Gitaly::GlobalOptions.new(literal_pathspecs: false)
) )
expect_any_instance_of(Gitaly::CommitService::Stub).to receive(:find_commits) expect_any_instance_of(Gitaly::CommitService::Stub).to receive(:find_commits)
...@@ -338,7 +341,8 @@ RSpec.describe Gitlab::GitalyClient::CommitService do ...@@ -338,7 +341,8 @@ RSpec.describe Gitlab::GitalyClient::CommitService do
revision: (options[:revision] || '').dup.force_encoding(Encoding::ASCII_8BIT), revision: (options[:revision] || '').dup.force_encoding(Encoding::ASCII_8BIT),
path: (options[:path] || '').dup.force_encoding(Encoding::ASCII_8BIT), path: (options[:path] || '').dup.force_encoding(Encoding::ASCII_8BIT),
limit: (options[:limit] || 1000).to_i, limit: (options[:limit] || 1000).to_i,
offset: (options[:offset] || 0).to_i offset: (options[:offset] || 0).to_i,
global_options: Gitaly::GlobalOptions.new(literal_pathspecs: true)
) )
allow_any_instance_of(Gitaly::CommitService::Stub) allow_any_instance_of(Gitaly::CommitService::Stub)
......
...@@ -47,15 +47,17 @@ RSpec.describe Gitlab::TreeSummary do ...@@ -47,15 +47,17 @@ RSpec.describe Gitlab::TreeSummary do
end end
describe '#summarize (entries)' do describe '#summarize (entries)' do
let(:limit) { 2 } let(:limit) { 4 }
custom_files = { custom_files = {
'a.txt' => '', 'a.txt' => '',
'b.txt' => '', 'b.txt' => '',
'directory/c.txt' => '' 'directory/c.txt' => '',
':dir/test.txt' => '',
':file' => ''
} }
let(:project) { create(:project, :custom_repo, files: custom_files) } let!(:project) { create(:project, :custom_repo, files: custom_files) }
let(:commit) { repo.head_commit } let(:commit) { repo.head_commit }
subject(:entries) { summary.summarize.first } subject(:entries) { summary.summarize.first }
...@@ -63,13 +65,16 @@ RSpec.describe Gitlab::TreeSummary do ...@@ -63,13 +65,16 @@ RSpec.describe Gitlab::TreeSummary do
it 'summarizes the entries within the window' do it 'summarizes the entries within the window' do
is_expected.to contain_exactly( is_expected.to contain_exactly(
a_hash_including(type: :tree, file_name: 'directory'), a_hash_including(type: :tree, file_name: 'directory'),
a_hash_including(type: :blob, file_name: 'a.txt') a_hash_including(type: :blob, file_name: 'a.txt'),
a_hash_including(type: :blob, file_name: ':file'),
a_hash_including(type: :tree, file_name: ':dir')
# b.txt is excluded by the limit # b.txt is excluded by the limit
) )
end end
it 'references the commit and commit path in entries' do it 'references the commit and commit path in entries' do
entry = entries.first # There are 2 trees and the summary is not ordered
entry = entries.find { |entry| entry[:commit].id == commit.id }
expected_commit_path = Gitlab::Routing.url_helpers.project_commit_path(project, commit) expected_commit_path = Gitlab::Routing.url_helpers.project_commit_path(project, commit)
expect(entry[:commit]).to be_a(::Commit) expect(entry[:commit]).to be_a(::Commit)
...@@ -85,6 +90,14 @@ RSpec.describe Gitlab::TreeSummary do ...@@ -85,6 +90,14 @@ RSpec.describe Gitlab::TreeSummary do
end end
end end
context 'in a subdirectory with a pathspec character' do
let(:path) { ':dir' }
it 'summarizes the entries in the subdirectory' do
is_expected.to contain_exactly(a_hash_including(type: :blob, file_name: 'test.txt'))
end
end
context 'in a non-existent subdirectory' do context 'in a non-existent subdirectory' do
let(:path) { 'tmp' } let(:path) { 'tmp' }
...@@ -92,7 +105,7 @@ RSpec.describe Gitlab::TreeSummary do ...@@ -92,7 +105,7 @@ RSpec.describe Gitlab::TreeSummary do
end end
context 'custom offset and limit' do context 'custom offset and limit' do
let(:offset) { 2 } let(:offset) { 4 }
it 'returns entries from the offset' do it 'returns entries from the offset' do
is_expected.to contain_exactly(a_hash_including(type: :blob, file_name: 'b.txt')) is_expected.to contain_exactly(a_hash_including(type: :blob, file_name: 'b.txt'))
......
...@@ -253,7 +253,7 @@ RSpec.describe Repository do ...@@ -253,7 +253,7 @@ RSpec.describe Repository do
end end
end end
context 'with filename with glob characters' do context 'with filename with pathspec characters' do
let(:filename) { ':wq' } let(:filename) { ':wq' }
let(:newrev) { project.repository.commit('master').sha } let(:newrev) { project.repository.commit('master').sha }
...@@ -292,7 +292,7 @@ RSpec.describe Repository do ...@@ -292,7 +292,7 @@ RSpec.describe Repository do
end end
end end
context 'with filename with glob characters' do context 'with filename with pathspec characters' do
let(:filename) { ':wq' } let(:filename) { ':wq' }
let(:newrev) { project.repository.commit('master').sha } let(:newrev) { project.repository.commit('master').sha }
......
...@@ -185,7 +185,7 @@ RSpec.describe API::Files do ...@@ -185,7 +185,7 @@ RSpec.describe API::Files do
expect(response.content_type).to eq('application/json') expect(response.content_type).to eq('application/json')
end end
context 'with filename with glob characters' do context 'with filename with pathspec characters' do
let(:file_path) { ':wq' } let(:file_path) { ':wq' }
let(:newrev) { project.repository.commit('master').sha } let(:newrev) { project.repository.commit('master').sha }
......
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