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. Please view this file on the master branch, on stable branches it's out of date.
v 7.14.0 (unreleased) 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) - 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 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) - 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 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 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) - Fix the "Reload with full diff" URL button (Stan Hu)
...@@ -15,9 +18,11 @@ v 7.14.0 (unreleased) ...@@ -15,9 +18,11 @@ v 7.14.0 (unreleased)
- Expire Rails cache entries after two weeks to prevent endless Redis growth - Expire Rails cache entries after two weeks to prevent endless Redis growth
- Add support for destroying project milestones (Stan Hu) - Add support for destroying project milestones (Stan Hu)
- Add fetch command to the MR page - 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. - Fix bug causing Bitbucket importer to crash when OAuth application had been removed.
- Add fetch command to the MR page. - Add fetch command to the MR page.
- Add ability to manage user email addresses via the API. - Add ability to manage user email addresses via the API.
- Disabled autocapitalize and autocorrect on login field (Daryl Chan)
v 7.13.2 v 7.13.2
- Fix randomly failed spec - Fix randomly failed spec
......
...@@ -7,6 +7,10 @@ class Projects::NetworkController < Projects::ApplicationController ...@@ -7,6 +7,10 @@ class Projects::NetworkController < Projects::ApplicationController
before_action :authorize_download_code! before_action :authorize_download_code!
def show 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| respond_to do |format|
format.html format.html
......
...@@ -56,6 +56,12 @@ class Group < Namespace ...@@ -56,6 +56,12 @@ class Group < Namespace
name name
end end
def avatar_url(size = nil)
if avatar.present?
[gitlab_config.url, avatar.url].join
end
end
def owners def owners
@owners ||= group_members.owners.map(&:user) @owners ||= group_members.owners.map(&:user)
end end
......
...@@ -36,7 +36,6 @@ class Project < ActiveRecord::Base ...@@ -36,7 +36,6 @@ class Project < ActiveRecord::Base
include Gitlab::ConfigHelper include Gitlab::ConfigHelper
include Gitlab::ShellAdapter include Gitlab::ShellAdapter
include Gitlab::VisibilityLevel include Gitlab::VisibilityLevel
include Rails.application.routes.url_helpers
include Referable include Referable
include Sortable include Sortable
...@@ -316,7 +315,7 @@ class Project < ActiveRecord::Base ...@@ -316,7 +315,7 @@ class Project < ActiveRecord::Base
end end
def web_url def web_url
[gitlab_config.url, path_with_namespace].join('/') Rails.application.routes.url_helpers.namespace_project_url(self.namespace, self)
end end
def web_url_without_protocol def web_url_without_protocol
...@@ -433,7 +432,7 @@ class Project < ActiveRecord::Base ...@@ -433,7 +432,7 @@ class Project < ActiveRecord::Base
if avatar.present? if avatar.present?
[gitlab_config.url, avatar.url].join [gitlab_config.url, avatar.url].join
elsif avatar_in_git 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
end end
...@@ -571,7 +570,7 @@ class Project < ActiveRecord::Base ...@@ -571,7 +570,7 @@ class Project < ActiveRecord::Base
end end
def http_url_to_repo def http_url_to_repo
[gitlab_config.url, '/', path_with_namespace, '.git'].join('') "#{web_url}.git"
end end
# Check if current branch name is marked as protected in the system # Check if current branch name is marked as protected in the system
...@@ -705,14 +704,14 @@ class Project < ActiveRecord::Base ...@@ -705,14 +704,14 @@ class Project < ActiveRecord::Base
ensure_satellite_exists ensure_satellite_exists
true true
else else
errors.add(:base, 'Failed to fork repository') errors.add(:base, 'Failed to fork repository via gitlab-shell')
false false
end end
else else
if gitlab_shell.add_repository(path_with_namespace) if gitlab_shell.add_repository(path_with_namespace)
true true
else else
errors.add(:base, 'Failed to create repository') errors.add(:base, 'Failed to create repository via gitlab-shell')
false false
end end
end end
......
= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| = 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" = f.password_field :password, class: "form-control bottom", placeholder: "Password"
- if devise_mapping.rememberable? - if devise_mapping.rememberable?
.remember-me.checkbox .remember-me.checkbox
......
...@@ -6,14 +6,13 @@ ...@@ -6,14 +6,13 @@
- @key.errors.full_messages.each do |msg| - @key.errors.full_messages.each do |msg|
%li= msg %li= msg
.form-group
= f.label :title, class: 'control-label'
.col-sm-10= f.text_field :title, class: "form-control"
.form-group .form-group
= f.label :key, class: 'control-label' = f.label :key, class: 'control-label'
.col-sm-10 .col-sm-10
= f.text_area :key, class: "form-control", rows: 8 = 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 .form-actions
= f.submit 'Add key', class: "btn btn-create" = f.submit 'Add key', class: "btn btn-create"
......
...@@ -17,9 +17,9 @@ ...@@ -17,9 +17,9 @@
:javascript :javascript
network_graph = new Network({ network_graph = new Network({
url: '#{namespace_project_network_path(@project.namespace, @project, @ref, @options.merge(format: :json))}', url: "#{escape_javascript(@url)}",
commit_url: '#{namespace_project_commit_path(@project.namespace, @project, 'ae45ca32').gsub("ae45ca32", "%s")}', commit_url: "#{escape_javascript(@commit_url)}",
ref: '#{@ref}', ref: "#{escape_javascript(@ref)}",
commit_id: '#{@commit.id}' commit_id: '#{@commit.id}'
}) })
new ShortcutsNetwork(network_graph.branch_graph) new ShortcutsNetwork(network_graph.branch_graph)
...@@ -2,9 +2,7 @@ ...@@ -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. 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. Sign in to [Twitter Application Management](https://apps.twitter.com/).
1. Hover over the avatar in the top right corner and select "My applications."
1. Select "Create new app" 1. Select "Create new app"
...@@ -14,18 +12,18 @@ To enable the Twitter OmniAuth provider you must register your application with ...@@ -14,18 +12,18 @@ To enable the Twitter OmniAuth provider you must register your application with
- Description: Create a description. - Description: Create a description.
- Website: The URL to your GitLab installation. 'https://gitlab.example.com' - Website: The URL to your GitLab installation. 'https://gitlab.example.com'
- Callback URL: 'https://gitlab.example.com/users/auth/twitter/callback' - 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) ![Twitter App Details](twitter_app_details.png)
1. Select "Create your Twitter application." 1. Select "Create your Twitter application."
1. Select the "Settings" tab. 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 "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. 1. You should now see an API key and API secret (see screenshot). Keep this page open as you continue configuration.
......
...@@ -31,7 +31,7 @@ We think there is still room for improvement and will detail a set of practices ...@@ -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 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. 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. 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. ...@@ -54,7 +54,7 @@ And doing releases doesn't automatically mean also doing hotfixes.
![Master branch with feature branches merged in](github_flow.png) ![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 flow has only feature branches and a master branch.
This is very simple and clean, many organizations have adopted it with great success. 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. 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 ...@@ -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. 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. 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. So if you want to merge it into a protected branch you assign it to someone with master authorizations.
## Issues with GitLab flow ## Issues with GitLab flow
...@@ -216,7 +216,7 @@ This prevents creating a merge commit when merging master into your feature bran ...@@ -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. 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. 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/). 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. 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. 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 ...@@ -10,6 +10,11 @@ Feature: Project Network Graph
And page should select "master" in select box And page should select "master" in select box
And page should have "master" on graph 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 @javascript
Scenario: I should switch "branch" and "tag" Scenario: I should switch "branch" and "tag"
When I switch ref to "feature" When I switch ref to "feature"
......
...@@ -11,8 +11,12 @@ class Spinach::Features::ProjectNetworkGraph < Spinach::FeatureSteps ...@@ -11,8 +11,12 @@ class Spinach::Features::ProjectNetworkGraph < Spinach::FeatureSteps
# Stub Graph max_size to speed up test (10 commits vs. 650) # Stub Graph max_size to speed up test (10 commits vs. 650)
Network::Graph.stub(max_count: 10) Network::Graph.stub(max_count: 10)
project = Project.find_by(name: "Shop") @project = Project.find_by(name: "Shop")
visit namespace_project_network_path(project.namespace, project, "master") 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 end
step 'page should select "master" in select box' do step 'page should select "master" in select box' do
...@@ -29,6 +33,12 @@ class Spinach::Features::ProjectNetworkGraph < Spinach::FeatureSteps ...@@ -29,6 +33,12 @@ class Spinach::Features::ProjectNetworkGraph < Spinach::FeatureSteps
end end
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 When 'I switch ref to "feature"' do
select 'feature', from: 'ref' select 'feature', from: 'ref'
sleep 2 sleep 2
......
...@@ -6,6 +6,10 @@ module API ...@@ -6,6 +6,10 @@ module API
class UserBasic < UserSafe class UserBasic < UserSafe
expose :id, :state, :avatar_url expose :id, :state, :avatar_url
expose :web_url do |user, options|
Rails.application.routes.url_helpers.user_url(user)
end
end end
class User < UserBasic class User < UserBasic
...@@ -63,6 +67,7 @@ module API ...@@ -63,6 +67,7 @@ module API
expose :namespace expose :namespace
expose :forked_from_project, using: Entities::ForkedFromProject, if: lambda{ | project, options | project.forked? } expose :forked_from_project, using: Entities::ForkedFromProject, if: lambda{ | project, options | project.forked? }
expose :avatar_url expose :avatar_url
expose :star_count, :forks_count
end end
class ProjectMember < UserBasic class ProjectMember < UserBasic
...@@ -73,6 +78,11 @@ module API ...@@ -73,6 +78,11 @@ module API
class Group < Grape::Entity class Group < Grape::Entity
expose :id, :name, :path, :description expose :id, :name, :path, :description
expose :avatar_url
expose :web_url do |group, options|
Rails.application.routes.url_helpers.group_url(group)
end
end end
class GroupDetail < Group class GroupDetail < Group
......
...@@ -7,7 +7,11 @@ module Backup ...@@ -7,7 +7,11 @@ module Backup
def initialize def initialize
@config = YAML.load_file(File.join(Rails.root,'config','database.yml'))[Rails.env] @config = YAML.load_file(File.join(Rails.root,'config','database.yml'))[Rails.env]
@db_dir = File.join(Gitlab.config.backup.path, 'db') @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 end
def dump def dump
...@@ -25,7 +29,6 @@ module Backup ...@@ -25,7 +29,6 @@ module Backup
abort 'Backup failed' unless success abort 'Backup failed' unless success
$progress.print 'Compressing database ... ' $progress.print 'Compressing database ... '
FileUtils.rm_f db_file_name_gz
success = system('gzip', db_file_name) success = system('gzip', db_file_name)
report_success(success) report_success(success)
abort 'Backup failed: compress error' unless success abort 'Backup failed: compress error' unless success
......
...@@ -16,8 +16,6 @@ module Backup ...@@ -16,8 +16,6 @@ module Backup
file << s.to_yaml.gsub(/^---\n/,'') file << s.to_yaml.gsub(/^---\n/,'')
end end
FileUtils.chmod(0700, folders_to_backup)
# create archive # create archive
$progress.print "Creating backup archive: #{tar_file} ... " $progress.print "Creating backup archive: #{tar_file} ... "
orig_umask = File.umask(0077) orig_umask = File.umask(0077)
......
...@@ -130,7 +130,10 @@ module Backup ...@@ -130,7 +130,10 @@ module Backup
def prepare def prepare
FileUtils.rm_rf(backup_repos_path) 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 end
def silent def silent
......
...@@ -10,7 +10,11 @@ module Backup ...@@ -10,7 +10,11 @@ module Backup
# Copy uploads from public/uploads to backup/uploads # Copy uploads from public/uploads to backup/uploads
def dump 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) FileUtils.cp_r(app_uploads_dir, backup_dir)
end end
......
...@@ -98,15 +98,25 @@ module Gitlab ...@@ -98,15 +98,25 @@ module Gitlab
# #
# Returns a String # Returns a String
def path_type(path) def path_type(path)
if repository.tree(current_sha, path).entries.any? unescaped_path = Addressable::URI.unescape(path)
if tree?(unescaped_path)
'tree' 'tree'
elsif repository.blob_at(current_sha, path).try(:image?) elsif image?(unescaped_path)
'raw' 'raw'
else else
'blob' 'blob'
end end
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 def current_sha
context[:commit].try(:id) || context[:commit].try(:id) ||
ref ? repository.commit(ref).try(:sha) : repository.head_commit.sha ref ? repository.commit(ref).try(:sha) : repository.head_commit.sha
......
...@@ -485,7 +485,8 @@ namespace :gitlab do ...@@ -485,7 +485,8 @@ namespace :gitlab do
if project.empty_repo? if project.empty_repo?
puts "repository is empty".magenta 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 puts 'ok'.green
else else
puts "wrong or missing hooks".red puts "wrong or missing hooks".red
...@@ -806,4 +807,3 @@ namespace :gitlab do ...@@ -806,4 +807,3 @@ namespace :gitlab do
end end
end end
end end
...@@ -62,11 +62,11 @@ namespace :gitlab do ...@@ -62,11 +62,11 @@ namespace :gitlab do
project = Projects::CreateService.new(user, project_params).execute project = Projects::CreateService.new(user, project_params).execute
if project.valid? if project.persisted?
puts " * Created #{project.name} (#{repo_path})".green puts " * Created #{project.name} (#{repo_path})".green
else else
puts " * Failed trying to create #{project.name} (#{repo_path})".red 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 end
end end
......
...@@ -17,410 +17,215 @@ require 'erb' ...@@ -17,410 +17,215 @@ require 'erb'
# -> Post-process HTML # -> Post-process HTML
# -> `gfm_with_options` helper # -> `gfm_with_options` helper
# -> HTML::Pipeline # -> HTML::Pipeline
# -> Sanitize # -> SanitizationFilter
# -> RelativeLink # -> Other filters, depending on pipeline
# -> Emoji
# -> Table of Contents
# -> Autolinks
# -> Rinku (http, https, ftp)
# -> Other schemes
# -> ExternalLink
# -> References
# -> TaskList
# -> `html_safe` # -> `html_safe`
# -> Template # -> Template
# #
# See the MarkdownFeature class for setup details. # See the MarkdownFeature class for setup details.
describe 'GitLab Markdown', feature: true do describe 'GitLab Markdown', feature: true do
include ActionView::Helpers::TagHelper
include ActionView::Helpers::UrlHelper
include Capybara::Node::Matchers include Capybara::Node::Matchers
include GitlabMarkdownHelper include GitlabMarkdownHelper
include MarkdownMatchers
# `markdown` calls these two methods # Sometimes it can be useful to see the parsed output of the Markdown document
def current_user # for debugging. Call this method to write the output to
@feat.user # `tmp/capybara/<filename>.html`.
end def write_markdown(filename = 'markdown_spec')
File.open(Rails.root.join("tmp/capybara/#{filename}.html"), 'w') do |file|
def user_color_scheme_class file.puts @html
: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 end
after(:all) do
@feat.teardown
end end
# Given a header ID, goes to that element's parent (the header itself), then def doc(html = @html)
# its next sibling element (the body). Nokogiri::HTML::DocumentFragment.parse(html)
def get_section(id)
@doc.at_css("##{id}").parent.next_element
end end
# Sometimes it can be useful to see the parsed output of the Markdown document # Shared behavior that all pipelines should exhibit
# for debugging. Uncomment this block to write the output to shared_examples 'all pipelines' do
# tmp/capybara/markdown_spec.html. describe 'Redcarpet extensions' do
#
# 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
it 'does not parse emphasis inside of words' do it 'does not parse emphasis inside of words' do
body = get_section('no-intra-emphasis') expect(doc.to_html).not_to match('foo<em>bar</em>baz')
expect(body.to_html).not_to match('foo<em>bar</em>baz')
end
end end
describe 'Tables' do
it 'parses table Markdown' do it 'parses table Markdown' do
body = get_section('tables') aggregate_failures do
expect(body).to have_selector('th:contains("Header")') expect(doc).to have_selector('th:contains("Header")')
expect(body).to have_selector('th:contains("Row")') expect(doc).to have_selector('th:contains("Row")')
expect(body).to have_selector('th:contains("Example")') expect(doc).to have_selector('th:contains("Example")')
end
end end
it 'allows Markdown in tables' do 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>' to eq '<strong>Baz</strong>'
end end
end
describe 'Fenced Code Blocks' do
it 'parses fenced code blocks' do it 'parses fenced code blocks' do
expect(@doc).to have_selector('pre.code.highlight.white.c') aggregate_failures do
expect(@doc).to have_selector('pre.code.highlight.white.python') expect(doc).to have_selector('pre.code.highlight.white.c')
expect(doc).to have_selector('pre.code.highlight.white.python')
end end
end end
describe 'Strikethrough' do
it 'parses strikethroughs' 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 end
describe 'Superscript' do
it 'parses superscript' do it 'parses superscript' do
body = get_section('superscript') expect(doc).to have_selector('sup', count: 2)
expect(body.to_html).to match('1<sup>st</sup>')
expect(body.to_html).to match('2<sup>nd</sup>')
end
end end
end end
describe 'HTML::Pipeline' do
describe 'SanitizationFilter' do describe 'SanitizationFilter' do
it 'uses a permissive whitelist' do it 'permits b elements' do
expect(@doc).to have_selector('b:contains("b tag")') 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')
end end
it 'permits span elements' do it 'permits em elements' do
expect(@doc).to have_selector('span:contains("span tag")') expect(doc).to have_selector('em:contains("em tag")')
end end
it 'permits table alignment' do it 'permits code elements' do
expect(@doc.at_css('th:contains("Header")')['style']).to eq 'text-align: center' expect(doc).to have_selector('code:contains("code tag")')
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'
end end
it 'removes `rel` attribute from links' do it 'permits kbd elements' do
body = get_section('sanitizationfilter') expect(doc).to have_selector('kbd:contains("s")')
expect(body).not_to have_selector('a[rel="bookmark"]')
end end
it "removes `href` from `a` elements if it's fishy" do it 'permits strike elements' do
expect(@doc).not_to have_selector('a[href*="javascript"]') expect(doc).to have_selector('strike:contains(Emoji)')
end
end end
describe 'Escaping' do it 'permits img elements' do
let(:table) { @doc.css('table').last.at_css('tbody') } expect(doc).to have_selector('img[src*="smile.png"]')
it 'escapes non-tag angle brackets' do
expect(table.at_xpath('.//tr[1]/td[3]').inner_html).to eq '1 &lt; 3 &amp; 5'
end
end end
describe 'Edge Cases' do it 'permits br elements' do
it 'allows markup inside link elements' do expect(doc).to have_selector('br')
expect(@doc.at_css('a[href="#link-emphasis"]').to_html). end
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). it 'permits hr elements' do
to eq %{<a href="#link-code"><code>text</code></a>} expect(doc).to have_selector('hr')
end end
it 'permits span elements' do
expect(doc).to have_selector('span:contains("span tag")')
end end
describe 'EmojiFilter' do it 'permits style attribute in th elements' do
it 'parses Emoji' do aggregate_failures do
expect(@doc).to have_selector('img.emoji', count: 10) 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
end end
describe 'TableOfContentsFilter' do it 'permits style attribute in td elements' do
it 'creates anchors inside header elements' do aggregate_failures do
expect(@doc).to have_selector('h1 a#gitlab-markdown') expect(doc.at_css('td:contains("Foo")')['style']).to eq 'text-align: center'
expect(@doc).to have_selector('h2 a#markdown') expect(doc.at_css('td:contains("Bar")')['style']).to eq 'text-align: right'
expect(@doc).to have_selector('h3 a#autolinkfilter') expect(doc.at_css('td:contains("Baz")')['style']).to eq 'text-align: left'
end end
end end
describe 'AutolinkFilter' do it 'removes `rel` attribute from links' do
let(:list) { get_section('autolinkfilter').next_element } expect(doc).not_to have_selector('a[rel="bookmark"]')
def item(index)
list.at_css("li:nth-child(#{index})")
end end
it 'autolinks http://' do it "removes `href` from `a` elements if it's fishy" do
expect(item(1).children.first.name).to eq 'a' expect(doc).not_to have_selector('a[href*="javascript"]')
expect(item(1).children.first['href']).to eq 'http://about.gitlab.com/'
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/'
end end
it 'autolinks ftp://' do describe 'Escaping' do
expect(item(3).children.first.name).to eq 'a' it 'escapes non-tag angle brackets' do
expect(item(3).children.first['href']).to eq 'ftp://ftp.us.debian.org/debian/' 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 smb://' do
expect(item(4).children.first.name).to eq 'a'
expect(item(4).children.first['href']).to eq 'smb://foo/bar/baz'
end end
it 'autolinks irc://' do describe 'Edge Cases' do
expect(item(5).children.first.name).to eq 'a' it 'allows markup inside link elements' do
expect(item(5).children.first['href']).to eq 'irc://irc.freenode.net/git' aggregate_failures do
end 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(doc.at_css('a[href="#link-strong"]').to_html).
expect(item(6).children.first.name).to eq 'a' to eq %{<a href="#link-strong"><strong>text</strong></a>}
expect(item(6).children.first['href']).to eq 'http://localhost:3000'
end
%w(code a kbd).each do |elem| expect(doc.at_css('a[href="#link-code"]').to_html).
it "ignores links inside '#{elem}' element" do to eq %{<a href="#link-code"><code>text</code></a>}
body = get_section('autolinkfilter')
expect(body).not_to have_selector("#{elem} a")
end end
end end
end end
describe 'ExternalLinkFilter' do describe 'ExternalLinkFilter' do
let(:links) { get_section('externallinkfilter').next_element }
it 'adds nofollow to external link' do 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 end
it 'ignores internal link' do 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 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 end
it "handles references in Markdown" do context 'default pipeline' do
body = get_section('reference-filters-eg-1') before(:all) do
expect(body).to have_selector('em a.gfm-merge_request', count: 1) @feat = MarkdownFeature.new
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
it 'parses snippet references' do
body = get_section('snippetreferencefilter')
expect(body).to have_selector('a.gfm.gfm-snippet', count: 2)
end
it 'parses commit range references' do
body = get_section('commitrangereferencefilter')
expect(body).to have_selector('a.gfm.gfm-commit_range', count: 2)
end
it 'parses commit references' do
body = get_section('commitreferencefilter')
expect(body).to have_selector('a.gfm.gfm-commit', count: 2)
end
it 'parses label references' do
body = get_section('labelreferencefilter')
expect(body).to have_selector('a.gfm.gfm-label', count: 3)
end
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
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)
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 # `gfm_with_options` depends on a `@project` variable
unless @commit_range @project = @feat.project
commit2 = project.commit('HEAD~3')
@commit_range = CommitRange.new("#{commit.id}...#{commit2.id}", project)
end
@commit_range @html = markdown(@feat.raw_markdown)
end end
def simple_label it_behaves_like 'all pipelines'
@simple_label ||= create(:label, name: 'gfm', project: project)
end
def label it 'includes RelativeLinkFilter' do
@label ||= create(:label, name: 'awaiting feedback', project: project) expect(doc).to parse_relative_links
end end
# Cross-references ----------------------------------------------------------- it 'includes EmojiFilter' do
expect(doc).to parse_emoji
def xproject
unless @xproject
namespace = create(:namespace, name: 'cross-reference')
@xproject = create(:project, namespace: namespace)
@xproject.team << [user, :developer]
end end
@xproject it 'includes TableOfContentsFilter' do
expect(doc).to create_header_links
end end
def xissue it 'includes AutolinkFilter' do
@xissue ||= create(:issue, project: xproject) expect(doc).to create_autolinks
end end
def xmerge_request it 'includes all reference filters' do
@xmerge_request ||= create(:merge_request, :simple, source_project: xproject) 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
def xsnippet
@xsnippet ||= create(:project_snippet, project: xproject)
end end
def xcommit it 'includes TaskListFilter' do
@xcommit ||= xproject.commit expect(doc).to parse_task_lists
end end
def xcommit_range
unless @xcommit_range
xcommit2 = xproject.commit('HEAD~2')
@xcommit_range = CommitRange.new("#{xcommit.id}...#{xcommit2.id}", xproject)
end end
@xcommit_range # `markdown` calls these two methods
def current_user
@feat.user
end end
def raw_markdown def user_color_scheme_class
fixture = Rails.root.join('spec/fixtures/markdown.md.erb') :white
ERB.new(File.read(fixture)).result(binding)
end end
end end
...@@ -100,6 +100,13 @@ Markdown should be usable inside a link. Let's try! ...@@ -100,6 +100,13 @@ Markdown should be usable inside a link. Let's try!
- [**text**](#link-strong) - [**text**](#link-strong)
- [`text`](#link-code) - [`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 ### EmojiFilter
Because life would be :zzz: without Emoji, right? :rocket: Because life would be :zzz: without Emoji, right? :rocket:
...@@ -123,9 +130,9 @@ These are all plain text that should get turned into links: ...@@ -123,9 +130,9 @@ These are all plain text that should get turned into links:
But it shouldn't autolink text inside certain tags: But it shouldn't autolink text inside certain tags:
- <code>http://about.gitlab.com/</code> - <code>http://code.gitlab.com/</code>
- <a>http://about.gitlab.com/</a> - <a>http://a.gitlab.com/</a>
- <kbd>http://about.gitlab.com/</kbd> - <kbd>http://kbd.gitlab.com/</kbd>
### ExternalLinkFilter ### ExternalLinkFilter
......
# encoding: UTF-8
require 'spec_helper' require 'spec_helper'
module Gitlab::Markdown module Gitlab::Markdown
...@@ -101,6 +103,20 @@ module Gitlab::Markdown ...@@ -101,6 +103,20 @@ module Gitlab::Markdown
expect(doc.at_css('a')['href']).to eq 'http://example.com' expect(doc.at_css('a')['href']).to eq 'http://example.com'
end 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 context 'when requested path is a file in the repo' do
let(:requested_path) { 'doc/api/README.md' } let(:requested_path) { 'doc/api/README.md' }
include_examples :relative_to_requested include_examples :relative_to_requested
......
...@@ -111,14 +111,20 @@ describe Project do ...@@ -111,14 +111,20 @@ describe Project do
expect(project.url_to_repo).to eq(Gitlab.config.gitlab_shell.ssh_path_prefix + 'somewhere.git') expect(project.url_to_repo).to eq(Gitlab.config.gitlab_shell.ssh_path_prefix + 'somewhere.git')
end end
describe "#web_url" do
let(:project) { create(:empty_project, path: "somewhere") }
it 'returns the full web URL for this repo' do 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}/#{project.namespace.path}/somewhere")
expect(project.web_url).to eq("#{Gitlab.config.gitlab.url}/somewhere") end
end end
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 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]}/#{project.namespace.path}/somewhere")
expect(project.web_url_without_protocol).to eq("#{Gitlab.config.gitlab.url.split('://')[1]}/somewhere") end
end end
describe 'last_activity methods' do describe 'last_activity methods' do
......
...@@ -29,7 +29,7 @@ describe Projects::ForkService do ...@@ -29,7 +29,7 @@ describe Projects::ForkService do
it "fails due to transaction failure" do it "fails due to transaction failure" do
@to_project = fork_project(@from_project, @to_user, false) @to_project = fork_project(@from_project, @to_user, false)
expect(@to_project.errors).not_to be_empty 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
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