Hide Gollum inside Gitlab::Git::Wiki

This commit is contained in:
Jacob Vosmaer (GitLab) 2017-10-03 16:58:33 +00:00 committed by Sean McGivern
parent 6e0d17b8c6
commit 5dd26d4e5a
14 changed files with 278 additions and 101 deletions

View file

@ -18,16 +18,12 @@ class Projects::WikisController < Projects::ApplicationController
response.headers['Content-Security-Policy'] = "default-src 'none'" response.headers['Content-Security-Policy'] = "default-src 'none'"
response.headers['X-Content-Security-Policy'] = "default-src 'none'" response.headers['X-Content-Security-Policy'] = "default-src 'none'"
if file.on_disk? send_data(
send_file file.on_disk_path, disposition: 'inline' file.raw_data,
else type: file.mime_type,
send_data( disposition: 'inline',
file.raw_data, filename: file.name
type: file.mime_type, )
disposition: 'inline',
filename: file.name
)
end
else else
return render('empty') unless can?(current_user, :create_wiki, @project) return render('empty') unless can?(current_user, :create_wiki, @project)
@page = WikiPage.new(@project_wiki) @page = WikiPage.new(@project_wiki)

View file

@ -54,12 +54,15 @@ class ProjectWiki
[Gitlab.config.gitlab.relative_url_root, '/', @project.full_path, '/wikis'].join('') [Gitlab.config.gitlab.relative_url_root, '/', @project.full_path, '/wikis'].join('')
end end
# Returns the Gollum::Wiki object. # Returns the Gitlab::Git::Wiki object.
def wiki def wiki
@wiki ||= begin @wiki ||= begin
Gollum::Wiki.new(path_to_repo) gl_repository = Gitlab::GlRepository.gl_repository(project, true)
rescue Rugged::OSError raw_repository = Gitlab::Git::Repository.new(project.repository_storage, disk_path + '.git', gl_repository)
create_repo!
create_repo!(raw_repository) unless raw_repository.exists?
Gitlab::Git::Wiki.new(raw_repository)
end end
end end
@ -86,20 +89,14 @@ class ProjectWiki
# Returns an initialized WikiPage instance or nil # Returns an initialized WikiPage instance or nil
def find_page(title, version = nil) def find_page(title, version = nil)
page_title, page_dir = page_title_and_dir(title) page_title, page_dir = page_title_and_dir(title)
if page = wiki.page(page_title, version, page_dir)
if page = wiki.page(title: page_title, version: version, dir: page_dir)
WikiPage.new(self, page, true) WikiPage.new(self, page, true)
else
nil
end end
end end
def find_file(name, version = nil, try_on_disk = true) def find_file(name, version = nil)
version = wiki.ref if version.nil? # Gollum::Wiki#file ? wiki.file(name, version)
if wiki_file = wiki.file(name, version, try_on_disk)
wiki_file
else
nil
end
end end
def create_page(title, content, format = :markdown, message = nil) def create_page(title, content, format = :markdown, message = nil)
@ -108,7 +105,7 @@ class ProjectWiki
wiki.write_page(title, format.to_sym, content, commit) wiki.write_page(title, format.to_sym, content, commit)
update_project_activity update_project_activity
rescue Gollum::DuplicatePageError => e rescue Gitlab::Git::Wiki::DuplicatePageError => e
@error_message = "Duplicate page: #{e.message}" @error_message = "Duplicate page: #{e.message}"
return false return false
end end
@ -116,13 +113,13 @@ class ProjectWiki
def update_page(page, content:, title: nil, format: :markdown, message: nil) def update_page(page, content:, title: nil, format: :markdown, message: nil)
commit = commit_details(:updated, message, page.title) commit = commit_details(:updated, message, page.title)
wiki.update_page(page, title || page.name, format.to_sym, content, commit) wiki.update_page(page.path, title || page.name, format.to_sym, content, commit)
update_project_activity update_project_activity
end end
def delete_page(page, message = nil) def delete_page(page, message = nil)
wiki.delete_page(page, commit_details(:deleted, message, page.title)) wiki.delete_page(page.path, commit_details(:deleted, message, page.title))
update_project_activity update_project_activity
end end
@ -145,20 +142,8 @@ class ProjectWiki
wiki.class.default_ref wiki.class.default_ref
end end
def create_repo!
if init_repo(disk_path)
wiki = Gollum::Wiki.new(path_to_repo)
else
raise CouldNotCreateWikiError
end
repository.after_create
wiki
end
def ensure_repository def ensure_repository
create_repo! unless repository_exists? raise CouldNotCreateWikiError unless wiki.repository_exists?
end end
def hook_attrs def hook_attrs
@ -173,24 +158,24 @@ class ProjectWiki
private private
def init_repo(disk_path) def create_repo!(raw_repository)
gitlab_shell.add_repository(project.repository_storage, disk_path) gitlab_shell.add_repository(project.repository_storage, disk_path)
raise CouldNotCreateWikiError unless raw_repository.exists?
repository.after_create
end end
def commit_details(action, message = nil, title = nil) def commit_details(action, message = nil, title = nil)
commit_message = message || default_message(action, title) commit_message = message || default_message(action, title)
{ email: @user.email, name: @user.name, message: commit_message } Gitlab::Git::Wiki::CommitDetails.new(@user.name, @user.email, commit_message)
end end
def default_message(action, title) def default_message(action, title)
"#{@user.username} #{action} page: #{title}" "#{@user.username} #{action} page: #{title}"
end end
def path_to_repo
@path_to_repo ||= File.join(project.repository_storage_path, "#{disk_path}.git")
end
def update_project_activity def update_project_activity
@project.touch(:last_activity_at, :last_repository_updated_at) @project.touch(:last_activity_at, :last_repository_updated_at)
end end

View file

@ -50,7 +50,7 @@ class WikiPage
# The Gitlab ProjectWiki instance. # The Gitlab ProjectWiki instance.
attr_reader :wiki attr_reader :wiki
# The raw Gollum::Page instance. # The raw Gitlab::Git::WikiPage instance.
attr_reader :page attr_reader :page
# The attributes Hash used for storing and validating # The attributes Hash used for storing and validating
@ -75,7 +75,7 @@ class WikiPage
if @attributes[:slug].present? if @attributes[:slug].present?
@attributes[:slug] @attributes[:slug]
else else
wiki.wiki.preview_page(title, '', format).url_path wiki.wiki.preview_slug(title, format)
end end
end end
@ -131,7 +131,7 @@ class WikiPage
def versions def versions
return [] unless persisted? return [] unless persisted?
@page.versions wiki.wiki.page_versions(@page.path)
end end
def commit def commit
@ -264,8 +264,8 @@ class WikiPage
end end
page_title, page_dir = wiki.page_title_and_dir(page_details) page_title, page_dir = wiki.page_title_and_dir(page_details)
gollum_wiki = wiki.wiki gitlab_git_wiki = wiki.wiki
@page = gollum_wiki.paged(page_title, page_dir) @page = gitlab_git_wiki.page(title: page_title, dir: page_dir)
set_attributes set_attributes
@persisted = errors.blank? @persisted = errors.blank?

View file

@ -29,13 +29,13 @@
commit.id, index == 0) do commit.id, index == 0) do
= truncate_sha(commit.id) = truncate_sha(commit.id)
%td %td
= commit.author.name = commit.author_name
%td %td
= commit.message = commit.message
%td %td
#{time_ago_with_tooltip(version.authored_date)} #{time_ago_with_tooltip(version.authored_date)}
%td %td
%strong %strong
= @page.page.wiki.page(@page.page.name, commit.id).try(:format) = version.format
= render 'sidebar' = render 'sidebar'

View file

@ -11,7 +11,7 @@
.nav-text .nav-text
%h2.wiki-page-title= @page.title.capitalize %h2.wiki-page-title= @page.title.capitalize
%span.wiki-last-edit-by %span.wiki-last-edit-by
= (_("Last edited by %{name}") % { name: "<strong>#{@page.commit.author.name}</strong>" }).html_safe = (_("Last edited by %{name}") % { name: "<strong>#{@page.commit.author_name}</strong>" }).html_safe
#{time_ago_with_tooltip(@page.commit.authored_date)} #{time_ago_with_tooltip(@page.commit.authored_date)}
.nav-controls .nav-controls

115
lib/gitlab/git/wiki.rb Normal file
View file

@ -0,0 +1,115 @@
module Gitlab
module Git
class Wiki
DuplicatePageError = Class.new(StandardError)
CommitDetails = Struct.new(:name, :email, :message) do
def to_h
{ name: name, email: email, message: message }
end
end
def self.default_ref
'master'
end
# Initialize with a Gitlab::Git::Repository instance
def initialize(repository)
@repository = repository
end
def repository_exists?
@repository.exists?
end
def write_page(name, format, content, commit_details)
assert_type!(format, Symbol)
assert_type!(commit_details, CommitDetails)
gollum_wiki.write_page(name, format, content, commit_details.to_h)
nil
rescue Gollum::DuplicatePageError => e
raise Gitlab::Git::Wiki::DuplicatePageError, e.message
end
def delete_page(page_path, commit_details)
assert_type!(commit_details, CommitDetails)
gollum_wiki.delete_page(gollum_page_by_path(page_path), commit_details.to_h)
nil
end
def update_page(page_path, title, format, content, commit_details)
assert_type!(format, Symbol)
assert_type!(commit_details, CommitDetails)
gollum_wiki.update_page(gollum_page_by_path(page_path), title, format, content, commit_details.to_h)
nil
end
def pages
gollum_wiki.pages.map { |gollum_page| new_page(gollum_page) }
end
def page(title:, version: nil, dir: nil)
if version
version = Gitlab::Git::Commit.find(@repository, version).id
end
gollum_page = gollum_wiki.page(title, version, dir)
return unless gollum_page
new_page(gollum_page)
end
def file(name, version)
version ||= self.class.default_ref
gollum_file = gollum_wiki.file(name, version)
return unless gollum_file
Gitlab::Git::WikiFile.new(gollum_file)
end
def page_versions(page_path)
current_page = gollum_page_by_path(page_path)
current_page.versions.map do |gollum_git_commit|
gollum_page = gollum_wiki.page(current_page.title, gollum_git_commit.id)
new_version(gollum_page, gollum_git_commit.id)
end
end
def preview_slug(title, format)
gollum_wiki.preview_page(title, '', format).url_path
end
private
def gollum_wiki
@gollum_wiki ||= Gollum::Wiki.new(@repository.path)
end
def gollum_page_by_path(page_path)
page_name = Gollum::Page.canonicalize_filename(page_path)
page_dir = File.split(page_path).first
gollum_wiki.paged(page_name, page_dir)
end
def new_page(gollum_page)
Gitlab::Git::WikiPage.new(gollum_page, new_version(gollum_page, gollum_page.version.id))
end
def new_version(gollum_page, commit_id)
commit = Gitlab::Git::Commit.find(@repository, commit_id)
Gitlab::Git::WikiPageVersion.new(commit, gollum_page&.format)
end
def assert_type!(object, klass)
unless object.is_a?(klass)
raise ArgumentError, "expected a #{klass}, got #{object.inspect}"
end
end
end
end
end

View file

@ -0,0 +1,19 @@
module Gitlab
module Git
class WikiFile
attr_reader :mime_type, :raw_data, :name
# This class is meant to be serializable so that it can be constructed
# by Gitaly and sent over the network to GitLab.
#
# Because Gollum::File is not serializable we must get all the data from
# 'gollum_file' during initialization, and NOT store it in an instance
# variable.
def initialize(gollum_file)
@mime_type = gollum_file.mime_type
@raw_data = gollum_file.raw_data
@name = gollum_file.name
end
end
end
end

View file

@ -0,0 +1,39 @@
module Gitlab
module Git
class WikiPage
attr_reader :url_path, :title, :format, :path, :version, :raw_data, :name, :text_data, :historical
# This class is meant to be serializable so that it can be constructed
# by Gitaly and sent over the network to GitLab.
#
# Because Gollum::Page is not serializable we must get all the data from
# 'gollum_page' during initialization, and NOT store it in an instance
# variable.
#
# Note that 'version' is a WikiPageVersion instance which it itself
# serializable. That means it's OK to store 'version' in an instance
# variable.
def initialize(gollum_page, version)
@url_path = gollum_page.url_path
@title = gollum_page.title
@format = gollum_page.format
@path = gollum_page.path
@raw_data = gollum_page.raw_data
@name = gollum_page.name
@historical = gollum_page.historical?
@version = version
end
def historical?
@historical
end
def text_data
return @text_data if defined?(@text_data)
@text_data = @raw_data && Gitlab::EncodingHelper.encode!(@raw_data.dup)
end
end
end
end

View file

@ -0,0 +1,19 @@
module Gitlab
module Git
class WikiPageVersion
attr_reader :commit, :format
# This class is meant to be serializable so that it can be constructed
# by Gitaly and sent over the network to GitLab.
#
# Both 'commit' (a Gitlab::Git::Commit) and 'format' (a string) are
# serializable.
def initialize(commit, format)
@commit = commit
@format = format
end
delegate :message, :sha, :id, :author_name, :authored_date, to: :commit
end
end
end

View file

@ -83,7 +83,7 @@ describe 'User views a wiki page' do
it 'shows a file stored in a page' do it 'shows a file stored in a page' do
file = Gollum::File.new(project.wiki) file = Gollum::File.new(project.wiki)
allow_any_instance_of(Gollum::Wiki).to receive(:file).with('image.jpg', 'master', true).and_return(file) allow_any_instance_of(Gollum::Wiki).to receive(:file).with('image.jpg', 'master').and_return(file)
allow_any_instance_of(Gollum::File).to receive(:mime_type).and_return('image/jpeg') allow_any_instance_of(Gollum::File).to receive(:mime_type).and_return('image/jpeg')
expect(page).to have_xpath('//img[@data-src="image.jpg"]') expect(page).to have_xpath('//img[@data-src="image.jpg"]')

View file

@ -6,13 +6,10 @@ describe ProjectWiki do
let(:user) { project.owner } let(:user) { project.owner }
let(:gitlab_shell) { Gitlab::Shell.new } let(:gitlab_shell) { Gitlab::Shell.new }
let(:project_wiki) { described_class.new(project, user) } let(:project_wiki) { described_class.new(project, user) }
let(:raw_repository) { Gitlab::Git::Repository.new(project.repository_storage, subject.disk_path + '.git', 'foo') }
subject { project_wiki } subject { project_wiki }
before do
project_wiki.wiki
end
describe "#path_with_namespace" do describe "#path_with_namespace" do
it "returns the project path with namespace with the .wiki extension" do it "returns the project path with namespace with the .wiki extension" do
expect(subject.path_with_namespace).to eq(project.full_path + '.wiki') expect(subject.path_with_namespace).to eq(project.full_path + '.wiki')
@ -61,8 +58,8 @@ describe ProjectWiki do
end end
describe "#wiki" do describe "#wiki" do
it "contains a Gollum::Wiki instance" do it "contains a Gitlab::Git::Wiki instance" do
expect(subject.wiki).to be_a Gollum::Wiki expect(subject.wiki).to be_a Gitlab::Git::Wiki
end end
it "creates a new wiki repo if one does not yet exist" do it "creates a new wiki repo if one does not yet exist" do
@ -70,20 +67,18 @@ describe ProjectWiki do
end end
it "raises CouldNotCreateWikiError if it can't create the wiki repository" do it "raises CouldNotCreateWikiError if it can't create the wiki repository" do
allow(project_wiki).to receive(:init_repo).and_return(false) # Create a fresh project which will not have a wiki
expect { project_wiki.send(:create_repo!) }.to raise_exception(ProjectWiki::CouldNotCreateWikiError) project_wiki = described_class.new(create(:project), user)
gitlab_shell = double(:gitlab_shell)
allow(gitlab_shell).to receive(:add_repository)
allow(project_wiki).to receive(:gitlab_shell).and_return(gitlab_shell)
expect { project_wiki.send(:wiki) }.to raise_exception(ProjectWiki::CouldNotCreateWikiError)
end end
end end
describe "#empty?" do describe "#empty?" do
context "when the wiki repository is empty" do context "when the wiki repository is empty" do
before do
allow_any_instance_of(Gitlab::Shell).to receive(:add_repository) do
create_temp_repo("#{Rails.root}/tmp/test-git-base-path/non-existant.wiki.git")
end
allow(project).to receive(:full_path).and_return("non-existant")
end
describe '#empty?' do describe '#empty?' do
subject { super().empty? } subject { super().empty? }
it { is_expected.to be_truthy } it { is_expected.to be_truthy }
@ -154,13 +149,13 @@ describe ProjectWiki do
before do before do
file = Gollum::File.new(subject.wiki) file = Gollum::File.new(subject.wiki)
allow_any_instance_of(Gollum::Wiki) allow_any_instance_of(Gollum::Wiki)
.to receive(:file).with('image.jpg', 'master', true) .to receive(:file).with('image.jpg', 'master')
.and_return(file) .and_return(file)
allow_any_instance_of(Gollum::File) allow_any_instance_of(Gollum::File)
.to receive(:mime_type) .to receive(:mime_type)
.and_return('image/jpeg') .and_return('image/jpeg')
allow_any_instance_of(Gollum::Wiki) allow_any_instance_of(Gollum::Wiki)
.to receive(:file).with('non-existant', 'master', true) .to receive(:file).with('non-existant', 'master')
.and_return(nil) .and_return(nil)
end end
@ -178,9 +173,9 @@ describe ProjectWiki do
expect(subject.find_file('non-existant')).to eq(nil) expect(subject.find_file('non-existant')).to eq(nil)
end end
it 'returns a Gollum::File instance' do it 'returns a Gitlab::Git::WikiFile instance' do
file = subject.find_file('image.jpg') file = subject.find_file('image.jpg')
expect(file).to be_a Gollum::File expect(file).to be_a Gitlab::Git::WikiFile
end end
end end
@ -222,9 +217,9 @@ describe ProjectWiki do
describe "#update_page" do describe "#update_page" do
before do before do
create_page("update-page", "some content") create_page("update-page", "some content")
@gollum_page = subject.wiki.paged("update-page") @gitlab_git_wiki_page = subject.wiki.page(title: "update-page")
subject.update_page( subject.update_page(
@gollum_page, @gitlab_git_wiki_page,
content: "some other content", content: "some other content",
format: :markdown, format: :markdown,
message: "updated page" message: "updated page"
@ -246,7 +241,7 @@ describe ProjectWiki do
it 'updates project activity' do it 'updates project activity' do
subject.update_page( subject.update_page(
@gollum_page, @gitlab_git_wiki_page,
content: 'Yet more content', content: 'Yet more content',
format: :markdown, format: :markdown,
message: 'Updated page again' message: 'Updated page again'
@ -262,7 +257,7 @@ describe ProjectWiki do
describe "#delete_page" do describe "#delete_page" do
before do before do
create_page("index", "some content") create_page("index", "some content")
@page = subject.wiki.paged("index") @page = subject.wiki.page(title: "index")
end end
it "deletes the page" do it "deletes the page" do
@ -282,27 +277,28 @@ describe ProjectWiki do
describe '#create_repo!' do describe '#create_repo!' do
it 'creates a repository' do it 'creates a repository' do
expect(subject).to receive(:init_repo) expect(raw_repository.exists?).to eq(false)
.with(subject.full_path)
.and_return(true)
expect(subject.repository).to receive(:after_create) expect(subject.repository).to receive(:after_create)
expect(subject.create_repo!).to be_an_instance_of(Gollum::Wiki) subject.send(:create_repo!, raw_repository)
expect(raw_repository.exists?).to eq(true)
end end
end end
describe '#ensure_repository' do describe '#ensure_repository' do
it 'creates the repository if it not exist' do it 'creates the repository if it not exist' do
allow(subject).to receive(:repository_exists?).and_return(false) expect(raw_repository.exists?).to eq(false)
expect(subject).to receive(:create_repo!)
expect(subject).to receive(:create_repo!).and_call_original
subject.ensure_repository subject.ensure_repository
expect(raw_repository.exists?).to eq(true)
end end
it 'does not create the repository if it exists' do it 'does not create the repository if it exists' do
allow(subject).to receive(:repository_exists?).and_return(true) subject.wiki
expect(raw_repository.exists?).to eq(true)
expect(subject).not_to receive(:create_repo!) expect(subject).not_to receive(:create_repo!)
@ -329,7 +325,7 @@ describe ProjectWiki do
end end
def commit_details def commit_details
{ name: user.name, email: user.email, message: "test commit" } Gitlab::Git::Wiki::CommitDetails.new(user.name, user.email, "test commit")
end end
def create_page(name, content) def create_page(name, content)
@ -337,6 +333,6 @@ describe ProjectWiki do
end end
def destroy_page(page) def destroy_page(page)
subject.wiki.delete_page(page, commit_details) subject.delete_page(page, commit_details)
end end
end end

View file

@ -80,7 +80,7 @@ describe WikiPage do
context "when initialized with an existing gollum page" do context "when initialized with an existing gollum page" do
before do before do
create_page("test page", "test content") create_page("test page", "test content")
@page = wiki.wiki.paged("test page") @page = wiki.wiki.page(title: "test page")
@wiki_page = described_class.new(wiki, @page, true) @wiki_page = described_class.new(wiki, @page, true)
end end
@ -105,7 +105,7 @@ describe WikiPage do
end end
it "sets the version attribute" do it "sets the version attribute" do
expect(@wiki_page.version).to be_a Gollum::Git::Commit expect(@wiki_page.version).to be_a Gitlab::Git::WikiPageVersion
end end
end end
end end
@ -321,14 +321,14 @@ describe WikiPage do
end end
it 'returns true when requesting an old version' do it 'returns true when requesting an old version' do
old_version = @page.versions.last.to_s old_version = @page.versions.last.id
old_page = wiki.find_page('Update', old_version) old_page = wiki.find_page('Update', old_version)
expect(old_page.historical?).to eq true expect(old_page.historical?).to eq true
end end
it 'returns false when requesting latest version' do it 'returns false when requesting latest version' do
latest_version = @page.versions.first.to_s latest_version = @page.versions.first.id
latest_page = wiki.find_page('Update', latest_version) latest_page = wiki.find_page('Update', latest_version)
expect(latest_page.historical?).to eq false expect(latest_page.historical?).to eq false
@ -393,7 +393,7 @@ describe WikiPage do
end end
def commit_details def commit_details
{ name: user.name, email: user.email, message: "test commit" } Gitlab::Git::Wiki::CommitDetails.new(user.name, user.email, "test commit")
end end
def create_page(name, content) def create_page(name, content)
@ -401,8 +401,8 @@ describe WikiPage do
end end
def destroy_page(title) def destroy_page(title)
page = wiki.wiki.paged(title) page = wiki.wiki.page(title: title)
wiki.wiki.delete_page(page, commit_details) wiki.delete_page(page, commit_details)
end end
def get_slugs(page_or_dir) def get_slugs(page_or_dir)

View file

@ -76,9 +76,8 @@ describe Projects::CreateService, '#execute' do
context 'wiki_enabled true creates wiki repository directory' do context 'wiki_enabled true creates wiki repository directory' do
it do it do
project = create_project(user, opts) project = create_project(user, opts)
path = ProjectWiki.new(project, user).send(:path_to_repo)
expect(File.exist?(path)).to be_truthy expect(wiki_repo(project).exists?).to be_truthy
end end
end end
@ -86,11 +85,15 @@ describe Projects::CreateService, '#execute' do
it do it do
opts[:wiki_enabled] = false opts[:wiki_enabled] = false
project = create_project(user, opts) project = create_project(user, opts)
path = ProjectWiki.new(project, user).send(:path_to_repo)
expect(File.exist?(path)).to be_falsey expect(wiki_repo(project).exists?).to be_falsey
end end
end end
def wiki_repo(project)
relative_path = ProjectWiki.new(project).disk_path + '.git'
Gitlab::Git::Repository.new(project.repository_storage, relative_path, 'foobar')
end
end end
context 'builds_enabled global setting' do context 'builds_enabled global setting' do

View file

@ -69,7 +69,12 @@ describe RepositoryCheck::SingleRepositoryWorker do
end end
def break_wiki(project) def break_wiki(project)
FileUtils.rm_rf(wiki_path(project) + '/objects') objects_dir = wiki_path(project) + '/objects'
# Replace the /objects directory with a file so that the repo is
# invalid, _and_ 'git init' cannot fix it.
FileUtils.rm_rf(objects_dir)
FileUtils.touch(objects_dir) if File.directory?(wiki_path(project))
end end
def wiki_path(project) def wiki_path(project)