Commit 51e5f379 authored by Douwe Maan's avatar Douwe Maan

Merge branch 'master' into api-useremails

parents 1c7a8b8c a51a3fb8
Please view this file on the master branch, on stable branches it's out of date.
v 7.14.0 (unreleased)
- Fix network graph when branch name has single quotes (Stan Hu)
- Upgrade gitlab_git to version 7.2.6 to fix Error 500 when creating network graphs (Stan Hu)
- Add support for Unicode filenames in relative links (Hiroyuki Sato)
- Fix URL used for refreshing notes if relative_url is present (Bartłomiej Święcki)
- Fix commit data retrieval when branch name has single quotes (Stan Hu)
- Check that project was actually created rather than just validated in import:repos task (Stan Hu)
- Fix full screen mode for snippet comments (Daniel Gerhardt)
- Fix 404 error in files view after deleting the last file in a repository (Stan Hu)
- Fix the "Reload with full diff" URL button (Stan Hu)
......@@ -15,9 +18,11 @@ v 7.14.0 (unreleased)
- Expire Rails cache entries after two weeks to prevent endless Redis growth
- Add support for destroying project milestones (Stan Hu)
- Add fetch command to the MR page
- Add project star and fork count, group avatar URL and user/group web URL attributes to API
- Fix bug causing Bitbucket importer to crash when OAuth application had been removed.
- Add fetch command to the MR page.
- Add ability to manage user email addresses via the API.
- Disabled autocapitalize and autocorrect on login field (Daryl Chan)
v 7.13.2
- Fix randomly failed spec
......
......@@ -7,6 +7,10 @@ class Projects::NetworkController < Projects::ApplicationController
before_action :authorize_download_code!
def show
@url = namespace_project_network_path(@project.namespace, @project, @ref, @options.merge(format: :json))
@commit_url = namespace_project_commit_path(@project.namespace, @project, 'ae45ca32').gsub("ae45ca32", "%s")
respond_to do |format|
format.html
......
......@@ -56,6 +56,12 @@ class Group < Namespace
name
end
def avatar_url(size = nil)
if avatar.present?
[gitlab_config.url, avatar.url].join
end
end
def owners
@owners ||= group_members.owners.map(&:user)
end
......
......@@ -36,7 +36,6 @@ class Project < ActiveRecord::Base
include Gitlab::ConfigHelper
include Gitlab::ShellAdapter
include Gitlab::VisibilityLevel
include Rails.application.routes.url_helpers
include Referable
include Sortable
......@@ -316,7 +315,7 @@ class Project < ActiveRecord::Base
end
def web_url
[gitlab_config.url, path_with_namespace].join('/')
Rails.application.routes.url_helpers.namespace_project_url(self.namespace, self)
end
def web_url_without_protocol
......@@ -433,7 +432,7 @@ class Project < ActiveRecord::Base
if avatar.present?
[gitlab_config.url, avatar.url].join
elsif avatar_in_git
[gitlab_config.url, namespace_project_avatar_path(namespace, self)].join
Rails.application.routes.url_helpers.namespace_project_avatar_url(namespace, self)
end
end
......@@ -571,7 +570,7 @@ class Project < ActiveRecord::Base
end
def http_url_to_repo
[gitlab_config.url, '/', path_with_namespace, '.git'].join('')
"#{web_url}.git"
end
# Check if current branch name is marked as protected in the system
......@@ -705,14 +704,14 @@ class Project < ActiveRecord::Base
ensure_satellite_exists
true
else
errors.add(:base, 'Failed to fork repository')
errors.add(:base, 'Failed to fork repository via gitlab-shell')
false
end
else
if gitlab_shell.add_repository(path_with_namespace)
true
else
errors.add(:base, 'Failed to create repository')
errors.add(:base, 'Failed to create repository via gitlab-shell')
false
end
end
......
= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f|
= f.text_field :login, class: "form-control top", placeholder: "Username or Email", autofocus: "autofocus"
= f.text_field :login, class: "form-control top", placeholder: "Username or Email", autofocus: "autofocus", autocapitalize: "off", autocorrect: "off"
= f.password_field :password, class: "form-control bottom", placeholder: "Password"
- if devise_mapping.rememberable?
.remember-me.checkbox
......
......@@ -6,14 +6,13 @@
- @key.errors.full_messages.each do |msg|
%li= msg
.form-group
= f.label :title, class: 'control-label'
.col-sm-10= f.text_field :title, class: "form-control"
.form-group
= f.label :key, class: 'control-label'
.col-sm-10
= f.text_area :key, class: "form-control", rows: 8
.form-group
= f.label :title, class: 'control-label'
.col-sm-10= f.text_field :title, class: "form-control"
.form-actions
= f.submit 'Add key', class: "btn btn-create"
......
......@@ -17,9 +17,9 @@
:javascript
network_graph = new Network({
url: '#{namespace_project_network_path(@project.namespace, @project, @ref, @options.merge(format: :json))}',
commit_url: '#{namespace_project_commit_path(@project.namespace, @project, 'ae45ca32').gsub("ae45ca32", "%s")}',
ref: '#{@ref}',
url: "#{escape_javascript(@url)}",
commit_url: "#{escape_javascript(@commit_url)}",
ref: "#{escape_javascript(@ref)}",
commit_id: '#{@commit.id}'
})
new ShortcutsNetwork(network_graph.branch_graph)
......@@ -2,9 +2,7 @@
To enable the Twitter OmniAuth provider you must register your application with Twitter. Twitter will generate a client ID and secret key for you to use.
1. Sign in to [Twitter Developers](https://dev.twitter.com/) area.
1. Hover over the avatar in the top right corner and select "My applications."
1. Sign in to [Twitter Application Management](https://apps.twitter.com/).
1. Select "Create new app"
......@@ -14,18 +12,18 @@ To enable the Twitter OmniAuth provider you must register your application with
- Description: Create a description.
- Website: The URL to your GitLab installation. 'https://gitlab.example.com'
- Callback URL: 'https://gitlab.example.com/users/auth/twitter/callback'
- Agree to the "Rules of the Road."
- Agree to the "Developer Agreement".
![Twitter App Details](twitter_app_details.png)
1. Select "Create your Twitter application."
1. Select the "Settings" tab.
1. Underneath the Callback URL check the box next to "Allow this application to be used to Sign in the Twitter."
1. Underneath the Callback URL check the box next to "Allow this application to be used to Sign in with Twitter."
1. Select "Update settings" at the bottom to save changes.
1. Select the "API Keys" tab.
1. Select the "Keys and Access Tokens" tab.
1. You should now see an API key and API secret (see screenshot). Keep this page open as you continue configuration.
......@@ -78,4 +76,4 @@ To enable the Twitter OmniAuth provider you must register your application with
1. Restart GitLab for the changes to take effect.
On the sign in page there should now be a Twitter icon below the regular sign in form. Click the icon to begin the authentication process. Twitter will ask the user to sign in and authorize the GitLab application. If everything goes well the user will be returned to GitLab and will be signed in.
On the sign in page there should now be a Twitter icon below the regular sign in form. Click the icon to begin the authentication process. Twitter will ask the user to sign in and authorize the GitLab application. If everything goes well the user will be returned to GitLab and will be signed in.
\ No newline at end of file
......@@ -31,7 +31,7 @@ We think there is still room for improvement and will detail a set of practices
## Git flow and its problems
[![Git Flow timeline by Vincent Driessen, used with permission](gitdashflow.png)
![Git Flow timeline by Vincent Driessen, used with permission](gitdashflow.png)
Git flow was one of the first proposals to use git branches and it has gotten a lot of attention.
It advocates a master branch and a separate develop branch as well as supporting branches for features, releases and hotfixes.
......@@ -54,7 +54,7 @@ And doing releases doesn't automatically mean also doing hotfixes.
![Master branch with feature branches merged in](github_flow.png)
In reaction to git flow a simpler alternative was detailed, [GitHub flow](https://guides.github.com/introduction/flow/index.html).
In reaction to git flow a simpler alternative was detailed, [GitHub flow](https://guides.github.com/introduction/flow/index.html).
This flow has only feature branches and a master branch.
This is very simple and clean, many organizations have adopted it with great success.
Atlassian recommends [a similar strategy](http://blogs.atlassian.com/2014/01/simple-git-workflow-simple/) although they rebase feature branches.
......@@ -131,7 +131,7 @@ When you feel comfortable with it to be merged you assign it to the person that
There is room for more feedback and after the assigned person feels comfortable with the result the branch is merged.
If the assigned person does not feel comfortable they can close the merge request without merging.
In GitLab it is common to protect the long-lived branches (e.g. the master branch) so that normal developers [can't modify these protected branches](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/permissions/permissions.md).
In GitLab it is common to protect the long-lived branches (e.g. the master branch) so that normal developers [can't modify these protected branches](http://doc.gitlab.com/ce/permissions/permissions.html).
So if you want to merge it into a protected branch you assign it to someone with master authorizations.
## Issues with GitLab flow
......@@ -216,7 +216,7 @@ This prevents creating a merge commit when merging master into your feature bran
However, just like with squashing you should never rebase commits you have pushed to a remote server.
This makes it impossible to rebase work in progress that you already shared with your team which is something we recommend.
When using rebase to keep your feature branch updated you [need to resolve similar conflicts again and again](http://blogs.atlassian.com/2013/10/git-team-workflows-merge-or-rebase/).
You can reuse recorded resolutions (rerere) sometimes, but with without rebasing you only have to solve the conflicts one time and you’re set.
You can reuse recorded resolutions (rerere) sometimes, but without rebasing you only have to solve the conflicts one time and you’re set.
There has to be a better way to avoid many merge commits.
The way to prevent creating many merge commits is to not frequently merge master into the feature branch.
......
......@@ -10,6 +10,11 @@ Feature: Project Network Graph
And page should select "master" in select box
And page should have "master" on graph
@javascript
Scenario: I should see project network with 'test' branch
When I visit project network page on branch 'test'
Then page should have 'test' on graph
@javascript
Scenario: I should switch "branch" and "tag"
When I switch ref to "feature"
......
......@@ -11,8 +11,12 @@ class Spinach::Features::ProjectNetworkGraph < Spinach::FeatureSteps
# Stub Graph max_size to speed up test (10 commits vs. 650)
Network::Graph.stub(max_count: 10)
project = Project.find_by(name: "Shop")
visit namespace_project_network_path(project.namespace, project, "master")
@project = Project.find_by(name: "Shop")
visit namespace_project_network_path(@project.namespace, @project, "master")
end
step "I visit project network page on branch 'test'" do
visit namespace_project_network_path(@project.namespace, @project, "'test'")
end
step 'page should select "master" in select box' do
......@@ -29,6 +33,12 @@ class Spinach::Features::ProjectNetworkGraph < Spinach::FeatureSteps
end
end
step "page should have 'test' on graph" do
page.within '.network-graph' do
expect(page).to have_content "'test'"
end
end
When 'I switch ref to "feature"' do
select 'feature', from: 'ref'
sleep 2
......
......@@ -6,6 +6,10 @@ module API
class UserBasic < UserSafe
expose :id, :state, :avatar_url
expose :web_url do |user, options|
Rails.application.routes.url_helpers.user_url(user)
end
end
class User < UserBasic
......@@ -63,6 +67,7 @@ module API
expose :namespace
expose :forked_from_project, using: Entities::ForkedFromProject, if: lambda{ | project, options | project.forked? }
expose :avatar_url
expose :star_count, :forks_count
end
class ProjectMember < UserBasic
......@@ -73,6 +78,11 @@ module API
class Group < Grape::Entity
expose :id, :name, :path, :description
expose :avatar_url
expose :web_url do |group, options|
Rails.application.routes.url_helpers.group_url(group)
end
end
class GroupDetail < Group
......
......@@ -7,7 +7,11 @@ module Backup
def initialize
@config = YAML.load_file(File.join(Rails.root,'config','database.yml'))[Rails.env]
@db_dir = File.join(Gitlab.config.backup.path, 'db')
FileUtils.mkdir_p(@db_dir) unless Dir.exists?(@db_dir)
FileUtils.rm_rf(@db_dir)
# Ensure the parent dir of @db_dir exists
FileUtils.mkdir_p(Gitlab.config.backup.path)
# Fail if somebody raced to create @db_dir before us
FileUtils.mkdir(@db_dir, mode: 0700)
end
def dump
......@@ -25,7 +29,6 @@ module Backup
abort 'Backup failed' unless success
$progress.print 'Compressing database ... '
FileUtils.rm_f db_file_name_gz
success = system('gzip', db_file_name)
report_success(success)
abort 'Backup failed: compress error' unless success
......
......@@ -16,8 +16,6 @@ module Backup
file << s.to_yaml.gsub(/^---\n/,'')
end
FileUtils.chmod(0700, folders_to_backup)
# create archive
$progress.print "Creating backup archive: #{tar_file} ... "
orig_umask = File.umask(0077)
......
......@@ -130,7 +130,10 @@ module Backup
def prepare
FileUtils.rm_rf(backup_repos_path)
FileUtils.mkdir_p(backup_repos_path)
# Ensure the parent dir of backup_repos_path exists
FileUtils.mkdir_p(Gitlab.config.backup.path)
# Fail if somebody raced to create backup_repos_path before us
FileUtils.mkdir(backup_repos_path, mode: 0700)
end
def silent
......
......@@ -10,7 +10,11 @@ module Backup
# Copy uploads from public/uploads to backup/uploads
def dump
FileUtils.mkdir_p(backup_uploads_dir)
FileUtils.rm_rf(backup_uploads_dir)
# Ensure the parent dir of backup_uploads_dir exists
FileUtils.mkdir_p(Gitlab.config.backup.path)
# Fail if somebody raced to create backup_uploads_dir before us
FileUtils.mkdir(backup_uploads_dir, mode: 0700)
FileUtils.cp_r(app_uploads_dir, backup_dir)
end
......
......@@ -98,15 +98,25 @@ module Gitlab
#
# Returns a String
def path_type(path)
if repository.tree(current_sha, path).entries.any?
unescaped_path = Addressable::URI.unescape(path)
if tree?(unescaped_path)
'tree'
elsif repository.blob_at(current_sha, path).try(:image?)
elsif image?(unescaped_path)
'raw'
else
'blob'
end
end
def tree?(path)
repository.tree(current_sha, path).entries.any?
end
def image?(path)
repository.blob_at(current_sha, path).try(:image?)
end
def current_sha
context[:commit].try(:id) ||
ref ? repository.commit(ref).try(:sha) : repository.head_commit.sha
......
......@@ -485,7 +485,8 @@ namespace :gitlab do
if project.empty_repo?
puts "repository is empty".magenta
elsif File.realpath(project_hook_directory) == File.realpath(gitlab_shell_hooks_path)
elsif File.directory?(project_hook_directory) && File.directory?(gitlab_shell_hooks_path) &&
(File.realpath(project_hook_directory) == File.realpath(gitlab_shell_hooks_path))
puts 'ok'.green
else
puts "wrong or missing hooks".red
......@@ -754,7 +755,7 @@ namespace :gitlab do
print "Ruby version >= #{required_version} ? ... "
if current_version.valid? && required_version <= current_version
puts "yes (#{current_version})".green
puts "yes (#{current_version})".green
else
puts "no".red
try_fixing_it(
......@@ -772,7 +773,7 @@ namespace :gitlab do
print "Git version >= #{required_version} ? ... "
if current_version.valid? && required_version <= current_version
puts "yes (#{current_version})".green
puts "yes (#{current_version})".green
else
puts "no".red
try_fixing_it(
......@@ -806,4 +807,3 @@ namespace :gitlab do
end
end
end
......@@ -62,11 +62,11 @@ namespace :gitlab do
project = Projects::CreateService.new(user, project_params).execute
if project.valid?
if project.persisted?
puts " * Created #{project.name} (#{repo_path})".green
else
puts " * Failed trying to create #{project.name} (#{repo_path})".red
puts " Validation Errors: #{project.errors.messages}".red
puts " Errors: #{project.errors.messages}".red
end
end
end
......
......@@ -17,410 +17,215 @@ require 'erb'
# -> Post-process HTML
# -> `gfm_with_options` helper
# -> HTML::Pipeline
# -> Sanitize
# -> RelativeLink
# -> Emoji
# -> Table of Contents
# -> Autolinks
# -> Rinku (http, https, ftp)
# -> Other schemes
# -> ExternalLink
# -> References
# -> TaskList
# -> SanitizationFilter
# -> Other filters, depending on pipeline
# -> `html_safe`
# -> Template
#
# See the MarkdownFeature class for setup details.
describe 'GitLab Markdown', feature: true do
include ActionView::Helpers::TagHelper
include ActionView::Helpers::UrlHelper
include Capybara::Node::Matchers
include GitlabMarkdownHelper
include MarkdownMatchers
# `markdown` calls these two methods
def current_user
@feat.user
end
def user_color_scheme_class
:white
end
# Let's only parse this thing once
before(:all) do
@feat = MarkdownFeature.new
# `markdown` expects a `@project` variable
@project = @feat.project
@md = markdown(@feat.raw_markdown)
@doc = Nokogiri::HTML::DocumentFragment.parse(@md)
end
after(:all) do
@feat.teardown
# Sometimes it can be useful to see the parsed output of the Markdown document
# for debugging. Call this method to write the output to
# `tmp/capybara/<filename>.html`.
def write_markdown(filename = 'markdown_spec')
File.open(Rails.root.join("tmp/capybara/#{filename}.html"), 'w') do |file|
file.puts @html
end
end
# Given a header ID, goes to that element's parent (the header itself), then
# its next sibling element (the body).
def get_section(id)
@doc.at_css("##{id}").parent.next_element
def doc(html = @html)
Nokogiri::HTML::DocumentFragment.parse(html)
end
# Sometimes it can be useful to see the parsed output of the Markdown document
# for debugging. Uncomment this block to write the output to
# tmp/capybara/markdown_spec.html.
#
# it 'writes to a file' do
# File.open(Rails.root.join('tmp/capybara/markdown_spec.html'), 'w') do |file|
# file.puts @md
# end
# end
describe 'Markdown' do
describe 'No Intra Emphasis' do
# Shared behavior that all pipelines should exhibit
shared_examples 'all pipelines' do
describe 'Redcarpet extensions' do
it 'does not parse emphasis inside of words' do
body = get_section('no-intra-emphasis')
expect(body.to_html).not_to match('foo<em>bar</em>baz')
expect(doc.to_html).not_to match('foo<em>bar</em>baz')
end
end
describe 'Tables' do
it 'parses table Markdown' do
body = get_section('tables')
expect(body).to have_selector('th:contains("Header")')
expect(body).to have_selector('th:contains("Row")')
expect(body).to have_selector('th:contains("Example")')
aggregate_failures do
expect(doc).to have_selector('th:contains("Header")')
expect(doc).to have_selector('th:contains("Row")')
expect(doc).to have_selector('th:contains("Example")')
end
end
it 'allows Markdown in tables' do
expect(@doc.at_css('td:contains("Baz")').children.to_html).
expect(doc.at_css('td:contains("Baz")').children.to_html).
to eq '<strong>Baz</strong>'
end
end
describe 'Fenced Code Blocks' do
it 'parses fenced code blocks' do
expect(@doc).to have_selector('pre.code.highlight.white.c')
expect(@doc).to have_selector('pre.code.highlight.white.python')
aggregate_failures do
expect(doc).to have_selector('pre.code.highlight.white.c')
expect(doc).to have_selector('pre.code.highlight.white.python')
end
end
end
describe 'Strikethrough' do
it 'parses strikethroughs' do
expect(@doc).to have_selector(%{del:contains("and this text doesn't")})
expect(doc).to have_selector(%{del:contains("and this text doesn't")})
end
end
describe 'Superscript' do
it 'parses superscript' do
body = get_section('superscript')
expect(body.to_html).to match('1<sup>st</sup>')
expect(body.to_html).to match('2<sup>nd</sup>')
expect(doc).to have_selector('sup', count: 2)
end
end
end
describe 'HTML::Pipeline' do
describe 'SanitizationFilter' do
it 'uses a permissive whitelist' do
expect(@doc).to have_selector('b:contains("b tag")')
expect(@doc).to have_selector('em:contains("em tag")')
expect(@doc).to have_selector('code:contains("code tag")')
expect(@doc).to have_selector('kbd:contains("s")')
expect(@doc).to have_selector('strike:contains(Emoji)')
expect(@doc).to have_selector('img[src*="smile.png"]')
expect(@doc).to have_selector('br')
expect(@doc).to have_selector('hr')
it 'permits b elements' do
expect(doc).to have_selector('b:contains("b tag")')
end
it 'permits span elements' do
expect(@doc).to have_selector('span:contains("span tag")')
it 'permits em elements' do
expect(doc).to have_selector('em:contains("em tag")')
end
it 'permits table alignment' do
expect(@doc.at_css('th:contains("Header")')['style']).to eq 'text-align: center'
expect(@doc.at_css('th:contains("Row")')['style']).to eq 'text-align: right'
expect(@doc.at_css('th:contains("Example")')['style']).to eq 'text-align: left'
expect(@doc.at_css('td:contains("Foo")')['style']).to eq 'text-align: center'
expect(@doc.at_css('td:contains("Bar")')['style']).to eq 'text-align: right'
expect(@doc.at_css('td:contains("Baz")')['style']).to eq 'text-align: left'
it 'permits code elements' do
expect(doc).to have_selector('code:contains("code tag")')
end
it 'removes `rel` attribute from links' do
body = get_section('sanitizationfilter')
expect(body).not_to have_selector('a[rel="bookmark"]')
it 'permits kbd elements' do
expect(doc).to have_selector('kbd:contains("s")')
end
it "removes `href` from `a` elements if it's fishy" do
expect(@doc).not_to have_selector('a[href*="javascript"]')
it 'permits strike elements' do
expect(doc).to have_selector('strike:contains(Emoji)')
end
end
describe 'Escaping' do
let(:table) { @doc.css('table').last.at_css('tbody') }
it 'escapes non-tag angle brackets' do
expect(table.at_xpath('.//tr[1]/td[3]').inner_html).to eq '1 &lt; 3 &amp; 5'
it 'permits img elements' do
expect(doc).to have_selector('img[src*="smile.png"]')
end
end
describe 'Edge Cases' do
it 'allows markup inside link elements' do
expect(@doc.at_css('a[href="#link-emphasis"]').to_html).
to eq %{<a href="#link-emphasis"><em>text</em></a>}
expect(@doc.at_css('a[href="#link-strong"]').to_html).
to eq %{<a href="#link-strong"><strong>text</strong></a>}
expect(@doc.at_css('a[href="#link-code"]').to_html).
to eq %{<a href="#link-code"><code>text</code></a>}
it 'permits br elements' do
expect(doc).to have_selector('br')
end
end
describe 'EmojiFilter' do
it 'parses Emoji' do
expect(@doc).to have_selector('img.emoji', count: 10)
it 'permits hr elements' do
expect(doc).to have_selector('hr')
end
end
describe 'TableOfContentsFilter' do
it 'creates anchors inside header elements' do
expect(@doc).to have_selector('h1 a#gitlab-markdown')
expect(@doc).to have_selector('h2 a#markdown')
expect(@doc).to have_selector('h3 a#autolinkfilter')
it 'permits span elements' do
expect(doc).to have_selector('span:contains("span tag")')
end
end
describe 'AutolinkFilter' do
let(:list) { get_section('autolinkfilter').next_element }
def item(index)
list.at_css("li:nth-child(#{index})")
it 'permits style attribute in th elements' do
aggregate_failures do
expect(doc.at_css('th:contains("Header")')['style']).to eq 'text-align: center'
expect(doc.at_css('th:contains("Row")')['style']).to eq 'text-align: right'
expect(doc.at_css('th:contains("Example")')['style']).to eq 'text-align: left'
end
end
it 'autolinks http://' do
expect(item(1).children.first.name).to eq 'a'
expect(item(1).children.first['href']).to eq 'http://about.gitlab.com/'
it 'permits style attribute in td elements' do
aggregate_failures do
expect(doc.at_css('td:contains("Foo")')['style']).to eq 'text-align: center'
expect(doc.at_css('td:contains("Bar")')['style']).to eq 'text-align: right'
expect(doc.at_css('td:contains("Baz")')['style']).to eq 'text-align: left'
end
end
it 'autolinks https://' do
expect(item(2).children.first.name).to eq 'a'
expect(item(2).children.first['href']).to eq 'https://google.com/'
it 'removes `rel` attribute from links' do
expect(doc).not_to have_selector('a[rel="bookmark"]')
end
it 'autolinks ftp://' do
expect(item(3).children.first.name).to eq 'a'
expect(item(3).children.first['href']).to eq 'ftp://ftp.us.debian.org/debian/'
it "removes `href` from `a` elements if it's fishy" do
expect(doc).not_to have_selector('a[href*="javascript"]')
end
end
it 'autolinks smb://' do
expect(item(4).children.first.name).to eq 'a'
expect(item(4).children.first['href']).to eq 'smb://foo/bar/baz'
describe 'Escaping' do
it 'escapes non-tag angle brackets' do
table = doc.css('table').last.at_css('tbody')
expect(table.at_xpath('.//tr[1]/td[3]').inner_html).to eq '1 &lt; 3 &amp; 5'
end
end
it 'autolinks irc://' do
expect(item(5).children.first.name).to eq 'a'
expect(item(5).children.first['href']).to eq 'irc://irc.freenode.net/git'
end
describe 'Edge Cases' do
it 'allows markup inside link elements' do
aggregate_failures do
expect(doc.at_css('a[href="#link-emphasis"]').to_html).
to eq %{<a href="#link-emphasis"><em>text</em></a>}
it 'autolinks short, invalid URLs' do
expect(item(6).children.first.name).to eq 'a'
expect(item(6).children.first['href']).to eq 'http://localhost:3000'
end
expect(doc.at_css('a[href="#link-strong"]').to_html).
to eq %{<a href="#link-strong"><strong>text</strong></a>}
%w(code a kbd).each do |elem|
it "ignores links inside '#{elem}' element" do
body = get_section('autolinkfilter')
expect(body).not_to have_selector("#{elem} a")
expect(doc.at_css('a[href="#link-code"]').to_html).
to eq %{<a href="#link-code"><code>text</code></a>}
end
end
end
describe 'ExternalLinkFilter' do
let(:links) { get_section('externallinkfilter').next_element }
it 'adds nofollow to external link' do
expect(links.css('a').first.to_html).to match 'nofollow'
link = doc.at_css('a:contains("Google")')
expect(link.attr('rel')).to match 'nofollow'
end
it 'ignores internal link' do
expect(links.css('a').last.to_html).not_to match 'nofollow'
link = doc.at_css('a:contains("GitLab Root")')
expect(link.attr('rel')).not_to match 'nofollow'
end
end
end
describe 'ReferenceFilter' do
it 'handles references in headers' do
header = @doc.at_css('#reference-filters-eg-1').parent
expect(header.css('a').size).to eq 2
end
it "handles references in Markdown" do
body = get_section('reference-filters-eg-1')
expect(body).to have_selector('em a.gfm-merge_request', count: 1)
end
it 'parses user references' do
body = get_section('userreferencefilter')
expect(body).to have_selector('a.gfm.gfm-project_member', count: 3)
end
it 'parses issue references' do
body = get_section('issuereferencefilter')
expect(body).to have_selector('a.gfm.gfm-issue', count: 2)
end
it 'parses merge request references' do
body = get_section('mergerequestreferencefilter')
expect(body).to have_selector('a.gfm.gfm-merge_request', count: 2)
end
context 'default pipeline' do
before(:all) do
@feat = MarkdownFeature.new
it 'parses snippet references' do
body = get_section('snippetreferencefilter')
expect(body).to have_selector('a.gfm.gfm-snippet', count: 2)
end
# `gfm_with_options` depends on a `@project` variable
@project = @feat.project
it 'parses commit range references' do
body = get_section('commitrangereferencefilter')
expect(body).to have_selector('a.gfm.gfm-commit_range', count: 2)
end
@html = markdown(@feat.raw_markdown)
end
it 'parses commit references' do
body = get_section('commitreferencefilter')
expect(body).to have_selector('a.gfm.gfm-commit', count: 2)
end
it_behaves_like 'all pipelines'
it 'parses label references' do
body = get_section('labelreferencefilter')
expect(body).to have_selector('a.gfm.gfm-label', count: 3)
end
it 'includes RelativeLinkFilter' do
expect(doc).to parse_relative_links
end
describe 'Task Lists' do
it 'generates task lists' do
body = get_section('task-lists')
expect(body).to have_selector('ul.task-list', count: 2)
expect(body).to have_selector('li.task-list-item', count: 7)
expect(body).to have_selector('input[checked]', count: 3)
end
it 'includes EmojiFilter' do
expect(doc).to parse_emoji
end
end
end
# This is a helper class used by the GitLab Markdown feature spec
#
# Because the feature spec only cares about the output of the Markdown, and the
# test setup and teardown and parsing is fairly expensive, we only want to do it
# once. Unfortunately RSpec will not let you access `let`s in a `before(:all)`
# block, so we fake it by encapsulating all the shared setup in this class.
#
# The class renders `spec/fixtures/markdown.md.erb` using ERB, allowing for
# reference to the factory-created objects.
class MarkdownFeature
include FactoryGirl::Syntax::Methods
def initialize
DatabaseCleaner.start
end
def teardown
DatabaseCleaner.clean
end
def user
@user ||= create(:user)
end
def group
unless @group
@group = create(:group)
@group.add_user(user, Gitlab::Access::DEVELOPER)
it 'includes TableOfContentsFilter' do
expect(doc).to create_header_links
end
@group
end
# Direct references ----------------------------------------------------------
def project
@project ||= create(:project)
end
def issue
@issue ||= create(:issue, project: project)
end
def merge_request
@merge_request ||= create(:merge_request, :simple, source_project: project)
end
def snippet
@snippet ||= create(:project_snippet, project: project)
end
def commit
@commit ||= project.commit
end
def commit_range
unless @commit_range
commit2 = project.commit('HEAD~3')
@commit_range = CommitRange.new("#{commit.id}...#{commit2.id}", project)
it 'includes AutolinkFilter' do
expect(doc).to create_autolinks
end
@commit_range
end
def simple_label
@simple_label ||= create(:label, name: 'gfm', project: project)
end
def label
@label ||= create(:label, name: 'awaiting feedback', project: project)
end
# Cross-references -----------------------------------------------------------
def xproject
unless @xproject
namespace = create(:namespace, name: 'cross-reference')
@xproject = create(:project, namespace: namespace)
@xproject.team << [user, :developer]
it 'includes all reference filters' do
aggregate_failures do
expect(doc).to reference_users
expect(doc).to reference_issues
expect(doc).to reference_merge_requests
expect(doc).to reference_snippets
expect(doc).to reference_commit_ranges
expect(doc).to reference_commits
expect(doc).to reference_labels
end
end
@xproject
end
def xissue
@xissue ||= create(:issue, project: xproject)
end
def xmerge_request
@xmerge_request ||= create(:merge_request, :simple, source_project: xproject)
end
def xsnippet
@xsnippet ||= create(:project_snippet, project: xproject)
end
def xcommit
@xcommit ||= xproject.commit
end
def xcommit_range
unless @xcommit_range
xcommit2 = xproject.commit('HEAD~2')
@xcommit_range = CommitRange.new("#{xcommit.id}...#{xcommit2.id}", xproject)
it 'includes TaskListFilter' do
expect(doc).to parse_task_lists
end
end
@xcommit_range
# `markdown` calls these two methods
def current_user
@feat.user
end
def raw_markdown
fixture = Rails.root.join('spec/fixtures/markdown.md.erb')
ERB.new(File.read(fixture)).result(binding)
def user_color_scheme_class
:white
end
end
......@@ -100,6 +100,13 @@ Markdown should be usable inside a link. Let's try!
- [**text**](#link-strong)
- [`text`](#link-code)
### RelativeLinkFilter
Linking to a file relative to this project's repository should work.
[Relative Link](doc/README.md)
![Relative Image](app/assets/images/touch-icon-ipad.png)
### EmojiFilter
Because life would be :zzz: without Emoji, right? :rocket:
......@@ -123,9 +130,9 @@ These are all plain text that should get turned into links:
But it shouldn't autolink text inside certain tags:
- <code>http://about.gitlab.com/</code>
- <a>http://about.gitlab.com/</a>
- <kbd>http://about.gitlab.com/</kbd>
- <code>http://code.gitlab.com/</code>
- <a>http://a.gitlab.com/</a>
- <kbd>http://kbd.gitlab.com/</kbd>
### ExternalLinkFilter
......
# encoding: UTF-8
require 'spec_helper'
module Gitlab::Markdown
......@@ -101,6 +103,20 @@ module Gitlab::Markdown
expect(doc.at_css('a')['href']).to eq 'http://example.com'
end
it 'supports Unicode filenames' do
path = 'files/images/한글.png'
escaped = Addressable::URI.escape(path)
# Stub these methods so the file doesn't actually need to be in the repo
allow_any_instance_of(described_class).to receive(:file_exists?).
and_return(true)
allow_any_instance_of(described_class).
to receive(:image?).with(path).and_return(true)
doc = filter(image(escaped))
expect(doc.at_css('img')['src']).to match '/raw/'
end
context 'when requested path is a file in the repo' do
let(:requested_path) { 'doc/api/README.md' }
include_examples :relative_to_requested
......
......@@ -111,14 +111,20 @@ describe Project do
expect(project.url_to_repo).to eq(Gitlab.config.gitlab_shell.ssh_path_prefix + 'somewhere.git')
end
it 'returns the full web URL for this repo' do
project = Project.new(path: 'somewhere')
expect(project.web_url).to eq("#{Gitlab.config.gitlab.url}/somewhere")
describe "#web_url" do
let(:project) { create(:empty_project, path: "somewhere") }
it 'returns the full web URL for this repo' do
expect(project.web_url).to eq("#{Gitlab.config.gitlab.url}/#{project.namespace.path}/somewhere")
end
end
it 'returns the web URL without the protocol for this repo' do
project = Project.new(path: 'somewhere')
expect(project.web_url_without_protocol).to eq("#{Gitlab.config.gitlab.url.split('://')[1]}/somewhere")
describe "#web_url_without_protocol" do
let(:project) { create(:empty_project, path: "somewhere") }
it 'returns the web URL without the protocol for this repo' do
expect(project.web_url_without_protocol).to eq("#{Gitlab.config.gitlab.url.split('://')[1]}/#{project.namespace.path}/somewhere")
end
end
describe 'last_activity methods' do
......
......@@ -29,7 +29,7 @@ describe Projects::ForkService do
it "fails due to transaction failure" do
@to_project = fork_project(@from_project, @to_user, false)
expect(@to_project.errors).not_to be_empty
expect(@to_project.errors[:base]).to include("Failed to fork repository")
expect(@to_project.errors[:base]).to include("Failed to fork repository via gitlab-shell")
end
end
......
# This is a helper class used by the GitLab Markdown feature spec
#
# Because the feature spec only cares about the output of the Markdown, and the
# test setup and teardown and parsing is fairly expensive, we only want to do it
# once. Unfortunately RSpec will not let you access `let`s in a `before(:all)`
# block, so we fake it by encapsulating all the shared setup in this class.
#
# The class renders `spec/fixtures/markdown.md.erb` using ERB, allowing for
# reference to the factory-created objects.
class MarkdownFeature
include FactoryGirl::Syntax::Methods
def user
@user ||= create(:user)
end
def group
unless @group
@group = create(:group)
@group.add_user(user, Gitlab::Access::DEVELOPER)
end
@group
end
# Direct references ----------------------------------------------------------
def project
@project ||= create(:project)
end
def issue
@issue ||= create(:issue, project: project)
end
def merge_request
@merge_request ||= create(:merge_request, :simple, source_project: project)
end
def snippet
@snippet ||= create(:project_snippet, project: project)
end
def commit
@commit ||= project.commit
end
def commit_range
unless @commit_range
commit2 = project.commit('HEAD~3')
@commit_range = CommitRange.new("#{commit.id}...#{commit2.id}", project)
end
@commit_range
end
def simple_label
@simple_label ||= create(:label, name: 'gfm', project: project)
end
def label
@label ||= create(:label, name: 'awaiting feedback', project: project)
end
# Cross-references -----------------------------------------------------------
def xproject
unless @xproject
namespace = create(:namespace, name: 'cross-reference')
@xproject = create(:project, namespace: namespace)
@xproject.team << [user, :developer]
end
@xproject
end
def xissue
@xissue ||= create(:issue, project: xproject)
end
def xmerge_request
@xmerge_request ||= create(:merge_request, :simple, source_project: xproject)
end
def xsnippet
@xsnippet ||= create(:project_snippet, project: xproject)
end
def xcommit
@xcommit ||= xproject.commit
end
def xcommit_range
unless @xcommit_range
xcommit2 = xproject.commit('HEAD~2')
@xcommit_range = CommitRange.new("#{xcommit.id}...#{xcommit2.id}", xproject)
end
@xcommit_range
end
def raw_markdown
fixture = Rails.root.join('spec/fixtures/markdown.md.erb')
ERB.new(File.read(fixture)).result(binding)
end
end
# MarkdownMatchers
#
# Custom matchers for our custom HTML::Pipeline filters. These are used to test
# that specific filters are or are not used by our defined pipelines.
#
# Must be included manually.
module MarkdownMatchers
extend RSpec::Matchers::DSL
include Capybara::Node::Matchers
# RelativeLinkFilter
matcher :parse_relative_links do
set_default_markdown_messages
match do |actual|
link = actual.at_css('a:contains("Relative Link")')
image = actual.at_css('img[alt="Relative Image"]')
expect(link['href']).to end_with('master/doc/README.md')
expect(image['src']).to end_with('master/app/assets/images/touch-icon-ipad.png')
end
end
# EmojiFilter
matcher :parse_emoji do
set_default_markdown_messages
match do |actual|
expect(actual).to have_selector('img.emoji', count: 10)
end
end
# TableOfContentsFilter
matcher :create_header_links do
set_default_markdown_messages
match do |actual|
expect(actual).to have_selector('h1 a#gitlab-markdown')
expect(actual).to have_selector('h2 a#markdown')
expect(actual).to have_selector('h3 a#autolinkfilter')
end
end
# AutolinkFilter
matcher :create_autolinks do
def have_autolink(link)
have_link(link, href: link)
end
set_default_markdown_messages
match do |actual|
expect(actual).to have_autolink('http://about.gitlab.com/')
expect(actual).to have_autolink('https://google.com/')
expect(actual).to have_autolink('ftp://ftp.us.debian.org/debian/')
expect(actual).to have_autolink('smb://foo/bar/baz')
expect(actual).to have_autolink('irc://irc.freenode.net/git')
expect(actual).to have_autolink('http://localhost:3000')
%w(code a kbd).each do |elem|
expect(body).not_to have_selector("#{elem} a")
end
end
end
# UserReferenceFilter
matcher :reference_users do
set_default_markdown_messages
match do |actual|
expect(actual).to have_selector('a.gfm.gfm-project_member', count: 3)
end
end
# IssueReferenceFilter
matcher :reference_issues do
set_default_markdown_messages
match do |actual|
expect(actual).to have_selector('a.gfm.gfm-issue', count: 3)
end
end
# MergeRequestReferenceFilter
matcher :reference_merge_requests do
set_default_markdown_messages
match do |actual|
expect(actual).to have_selector('a.gfm.gfm-merge_request', count: 3)
expect(actual).to have_selector('em a.gfm-merge_request')
end
end
# SnippetReferenceFilter
matcher :reference_snippets do
set_default_markdown_messages
match do |actual|
expect(actual).to have_selector('a.gfm.gfm-snippet', count: 2)
end
end
# CommitRangeReferenceFilter
matcher :reference_commit_ranges do
set_default_markdown_messages
match do |actual|
expect(actual).to have_selector('a.gfm.gfm-commit_range', count: 2)
end
end
# CommitReferenceFilter
matcher :reference_commits do
set_default_markdown_messages
match do |actual|
expect(actual).to have_selector('a.gfm.gfm-commit', count: 2)
end
end
# LabelReferenceFilter
matcher :reference_labels do
set_default_markdown_messages
match do |actual|
expect(actual).to have_selector('a.gfm.gfm-label', count: 3)
end
end
# TaskListFilter
matcher :parse_task_lists do
set_default_markdown_messages
match do |actual|
expect(actual).to have_selector('ul.task-list', count: 2)
expect(actual).to have_selector('li.task-list-item', count: 7)
expect(actual).to have_selector('input[checked]', count: 3)
end
end
end
# Monkeypatch the matcher DSL so that we can reduce some noisy duplication for
# setting the failure messages for these matchers
module RSpec::Matchers::DSL::Macros
def set_default_markdown_messages
failure_message do
# expected to parse emoji, but didn't
"expected to #{description}, but didn't"
end
failure_message_when_negated do
# expected not to parse task lists, but did
"expected not to #{description}, but did"
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