Commit 93855a67 authored by Valery Sizov's avatar Valery Sizov

Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce into ce_upstream

parents b18b4bc1 7df149bb
...@@ -4,6 +4,7 @@ v 8.5.0 (unreleased) ...@@ -4,6 +4,7 @@ v 8.5.0 (unreleased)
- Ensure rake tasks that don't need a DB connection can be run without one - Ensure rake tasks that don't need a DB connection can be run without one
- Add "visibility" flag to GET /projects api endpoint - Add "visibility" flag to GET /projects api endpoint
- Ignore binary files in code search to prevent Error 500 (Stan Hu) - Ignore binary files in code search to prevent Error 500 (Stan Hu)
- Render sanitized SVG images (Stan Hu)
- Upgrade gitlab_git to 7.2.23 to fix commit message mentions in first branch push - Upgrade gitlab_git to 7.2.23 to fix commit message mentions in first branch push
- New UI for pagination - New UI for pagination
- Don't prevent sign out when 2FA enforcement is enabled and user hasn't yet - Don't prevent sign out when 2FA enforcement is enabled and user hasn't yet
...@@ -14,13 +15,21 @@ v 8.5.0 (unreleased) ...@@ -14,13 +15,21 @@ v 8.5.0 (unreleased)
- Display 404 error on group not found - Display 404 error on group not found
- Track project import failure - Track project import failure
- Fix visibility level text in admin area (Zeger-Jan van de Weg) - Fix visibility level text in admin area (Zeger-Jan van de Weg)
- Warn admin during OAuth of granting admin rights (Zeger-Jan van de Weg)
- Update the ExternalIssue regex pattern (Blake Hitchcock) - Update the ExternalIssue regex pattern (Blake Hitchcock)
- Optimized performance of finding issues to be closed by a merge request
- Revert "Add IP check against DNSBLs at account sign-up" - Revert "Add IP check against DNSBLs at account sign-up"
- Deprecate API "merge_request/:merge_request_id/comments". Use "merge_requests/:merge_request_id/notes" instead - Deprecate API "merge_request/:merge_request_id/comments". Use "merge_requests/:merge_request_id/notes" instead
- Deprecate API "merge_request/:merge_request_id/...". Use "merge_requests/:merge_request_id/..." instead - Deprecate API "merge_request/:merge_request_id/...". Use "merge_requests/:merge_request_id/..." instead
- Mark inline difference between old and new paths when a file is renamed
v 8.4.3 v 8.4.3
- Increase lfs_objects size column to 8-byte integer to allow files larger than 2.1GB - Increase lfs_objects size column to 8-byte integer to allow files larger
than 2.1GB
- Correctly highlight MR diff when MR has merge conflicts
- Fix highlighting in blame view
- Update sentry-raven gem to prevent "Not a git repository" console output
when running certain commands
v 8.4.2 v 8.4.2
- Bump required gitlab-workhorse version to bring in a fix for missing - Bump required gitlab-workhorse version to bring in a fix for missing
...@@ -31,7 +40,6 @@ v 8.4.2 ...@@ -31,7 +40,6 @@ v 8.4.2
improvement when checking if a repository was empty improvement when checking if a repository was empty
- Add instrumentation for Gitlab::Git::Repository instance methods so we can - Add instrumentation for Gitlab::Git::Repository instance methods so we can
track them in Performance Monitoring. track them in Performance Monitoring.
- Correctly highlight MR diff when MR has merge conflicts
- Increase contrast between highlighted code comments and inline diff marker - Increase contrast between highlighted code comments and inline diff marker
- Fix method undefined when using external commit status in builds - Fix method undefined when using external commit status in builds
- Fix highlighting in blame view. - Fix highlighting in blame view.
...@@ -41,6 +49,7 @@ v 8.4.1 ...@@ -41,6 +49,7 @@ v 8.4.1
and Nokogiri (1.6.7.2) and Nokogiri (1.6.7.2)
- Fix redirect loop during import - Fix redirect loop during import
- Fix diff highlighting for all syntax themes - Fix diff highlighting for all syntax themes
- Delete project and associations in a background worker
v 8.4.0 (unreleased) v 8.4.0 (unreleased)
- Hide issues settings when issues are disabled (Hannes Rosenögger) - Hide issues settings when issues are disabled (Hannes Rosenögger)
...@@ -90,7 +99,7 @@ v 8.4.0 ...@@ -90,7 +99,7 @@ v 8.4.0
- Show 'All' tab by default in the builds page - Show 'All' tab by default in the builds page
- Add Open Graph and Twitter Card data to all pages - Add Open Graph and Twitter Card data to all pages
- Fix API project lookups when querying with a namespace with dots (Stan Hu) - Fix API project lookups when querying with a namespace with dots (Stan Hu)
- Enable forcing Two-Factor authentication sitewide, with optional grace period - Enable forcing Two-factor authentication sitewide, with optional grace period
- Import GitHub Pull Requests into GitLab - Import GitHub Pull Requests into GitLab
- Change single user API endpoint to return more detailed data (Michael Potthoff) - Change single user API endpoint to return more detailed data (Michael Potthoff)
- Update version check images to use SVG - Update version check images to use SVG
......
...@@ -147,7 +147,7 @@ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production SANITIZE=true) ...@@ -147,7 +147,7 @@ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production SANITIZE=true)
sudo gitlab-rake gitlab:env:info) sudo gitlab-rake gitlab:env:info)
(For installations from source run and paste the output of: (For installations from source run and paste the output of:
sudo -u git -H bundle exec rake gitlab:env:info) sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production)
## Possible fixes ## Possible fixes
...@@ -255,6 +255,8 @@ For examples of feedback on merge requests please look at already ...@@ -255,6 +255,8 @@ For examples of feedback on merge requests please look at already
request feel free to mention one of the Merge Marshalls of the [core team][]. request feel free to mention one of the Merge Marshalls of the [core team][].
Please ensure that your merge request meets the contribution acceptance criteria. Please ensure that your merge request meets the contribution acceptance criteria.
When having your code reviewed and when reviewing merge requests please take the [thoughtbot code review guidelines](https://github.com/thoughtbot/guides/tree/master/code-review) into account.
## Definition of done ## Definition of done
If you contribute to GitLab please know that changes involve more than just If you contribute to GitLab please know that changes involve more than just
......
...@@ -186,6 +186,9 @@ gem "underscore-rails", "~> 1.8.0" ...@@ -186,6 +186,9 @@ gem "underscore-rails", "~> 1.8.0"
gem "sanitize", '~> 2.0' gem "sanitize", '~> 2.0'
gem 'babosa', '~> 1.0.2' gem 'babosa', '~> 1.0.2'
# Sanitizes SVG input
gem "loofah", "~> 2.0.3"
# Protect against bruteforcing # Protect against bruteforcing
gem "rack-attack", '~> 4.3.1' gem "rack-attack", '~> 4.3.1'
......
...@@ -982,6 +982,7 @@ DEPENDENCIES ...@@ -982,6 +982,7 @@ DEPENDENCIES
jquery-ui-rails (~> 5.0.0) jquery-ui-rails (~> 5.0.0)
kaminari (~> 0.16.3) kaminari (~> 0.16.3)
letter_opener (~> 1.1.2) letter_opener (~> 1.1.2)
loofah (~> 2.0.3)
mail_room (~> 0.6.1) mail_room (~> 0.6.1)
method_source (~> 0.8) method_source (~> 0.8)
minitest (~> 5.7.0) minitest (~> 5.7.0)
......
#= require jquery.ba-resize
#= require autosize #= require autosize
$ -> $ ->
autosize($('.js-autosize')) $fields = $('.js-autosize')
$fields.on 'autosize:resized', ->
$field = $(@)
$field.data('height', $field.outerHeight())
$fields.on 'resize.autosize', ->
$field = $(@)
if $field.data('height') != $field.outerHeight()
$field.data('height', $field.outerHeight())
autosize.destroy($field)
$field.css('max-height', window.outerHeight)
autosize($fields)
autosize.update($fields)
$fields.css('resize', 'vertical')
...@@ -36,6 +36,20 @@ ...@@ -36,6 +36,20 @@
} }
} }
.filename {
&.old {
span.idiff {
background-color: #f8cbcb;
}
}
&.new {
span.idiff {
background-color: #a6f3a6;
}
}
}
.left-options { .left-options {
margin-top: -3px; margin-top: -3px;
} }
...@@ -158,3 +172,15 @@ ...@@ -158,3 +172,15 @@
} }
} }
} }
span.idiff {
&.left {
border-top-left-radius: 2px;
border-bottom-left-radius: 2px;
}
&.right {
border-top-right-radius: 2px;
border-bottom-right-radius: 2px;
}
}
...@@ -83,7 +83,7 @@ ...@@ -83,7 +83,7 @@
background: #FFF; background: #FFF;
border: 1px solid #ddd; border: 1px solid #ddd;
min-height: 140px; min-height: 140px;
max-height: 430px; max-height: 500px;
padding: 5px; padding: 5px;
box-shadow: none; box-shadow: none;
width: 100%; width: 100%;
......
...@@ -147,7 +147,7 @@ ...@@ -147,7 +147,7 @@
.edit_note { .edit_note {
.markdown-area { .markdown-area {
min-height: 140px; min-height: 140px;
max-height: 430px; max-height: 500px;
} }
.note-form-actions { .note-form-actions {
background: transparent; background: transparent;
......
...@@ -94,6 +94,10 @@ class ProjectsController < ApplicationController ...@@ -94,6 +94,10 @@ class ProjectsController < ApplicationController
return return
end end
if @project.pending_delete?
flash[:alert] = "Project queued for delete."
end
respond_to do |format| respond_to do |format|
format.html do format.html do
if @project.repository_exists? if @project.repository_exists?
...@@ -121,8 +125,8 @@ class ProjectsController < ApplicationController ...@@ -121,8 +125,8 @@ class ProjectsController < ApplicationController
def destroy def destroy
return access_denied! unless can?(current_user, :remove_project, @project) return access_denied! unless can?(current_user, :remove_project, @project)
::Projects::DestroyService.new(@project, current_user, {}).execute ::Projects::DestroyService.new(@project, current_user, {}).pending_delete!
flash[:alert] = "Project '#{@project.name}' was deleted." flash[:alert] = "Project '#{@project.name}' will be deleted."
redirect_to dashboard_projects_path redirect_to dashboard_projects_path
rescue Projects::DestroyService::DestroyError => ex rescue Projects::DestroyService::DestroyError => ex
......
...@@ -126,4 +126,16 @@ module BlobHelper ...@@ -126,4 +126,16 @@ module BlobHelper
blob.size blob.size
end end
end end
def blob_svg?(blob)
blob.language && blob.language.name == 'SVG'
end
# SVGs can contain malicious JavaScript; only include whitelisted
# elements and attributes. Note that this whitelist is by no means complete
# and may omit some elements.
def sanitize_svg(blob)
blob.data = Loofah.scrub_fragment(blob.data, :strip).to_xml
blob
end
end end
module DiffHelper module DiffHelper
def mark_inline_diffs(old_line, new_line)
old_diffs, new_diffs = Gitlab::Diff::InlineDiff.new(old_line, new_line).inline_diffs
marked_old_line = Gitlab::Diff::InlineDiffMarker.new(old_line).mark(old_diffs)
marked_new_line = Gitlab::Diff::InlineDiffMarker.new(new_line).mark(new_diffs)
[marked_old_line, marked_new_line]
end
def diff_view def diff_view
params[:view] == 'parallel' ? 'parallel' : 'inline' params[:view] == 'parallel' ? 'parallel' : 'inline'
end end
...@@ -55,7 +64,7 @@ module DiffHelper ...@@ -55,7 +64,7 @@ module DiffHelper
if line.blank? if line.blank?
" &nbsp;".html_safe " &nbsp;".html_safe
else else
line.html_safe line
end end
end end
......
...@@ -26,8 +26,10 @@ class BroadcastMessage < ActiveRecord::Base ...@@ -26,8 +26,10 @@ class BroadcastMessage < ActiveRecord::Base
default_value_for :font, '#FFFFFF' default_value_for :font, '#FFFFFF'
def self.current def self.current
Rails.cache.fetch("broadcast_message_current", expires_in: 1.minute) do
where("ends_at > :now AND starts_at <= :now", now: Time.zone.now).last where("ends_at > :now AND starts_at <= :now", now: Time.zone.now).last
end end
end
def active? def active?
started? && !ended? started? && !ended?
......
...@@ -68,18 +68,18 @@ class Commit ...@@ -68,18 +68,18 @@ class Commit
# Pattern used to extract commit references from text # Pattern used to extract commit references from text
# #
# The SHA can be between 6 and 40 hex characters. # The SHA can be between 7 and 40 hex characters.
# #
# This pattern supports cross-project references. # This pattern supports cross-project references.
def self.reference_pattern def self.reference_pattern
%r{ %r{
(?:#{Project.reference_pattern}#{reference_prefix})? (?:#{Project.reference_pattern}#{reference_prefix})?
(?<commit>\h{6,40}) (?<commit>\h{7,40})
}x }x
end end
def self.link_reference_pattern def self.link_reference_pattern
super("commit", /(?<commit>\h{6,40})/) super("commit", /(?<commit>\h{7,40})/)
end end
def to_reference(from_project = nil) def to_reference(from_project = nil)
......
...@@ -32,8 +32,8 @@ class CommitRange ...@@ -32,8 +32,8 @@ class CommitRange
PATTERN = /#{REF_PATTERN}\.{2,3}#{REF_PATTERN}/ PATTERN = /#{REF_PATTERN}\.{2,3}#{REF_PATTERN}/
# In text references, the beginning and ending refs can only be SHAs # In text references, the beginning and ending refs can only be SHAs
# between 6 and 40 hex characters. # between 7 and 40 hex characters.
STRICT_PATTERN = /\h{6,40}\.{2,3}\h{6,40}/ STRICT_PATTERN = /\h{7,40}\.{2,3}\h{7,40}/
def self.reference_prefix def self.reference_prefix
'@' '@'
......
...@@ -350,10 +350,10 @@ class MergeRequest < ActiveRecord::Base ...@@ -350,10 +350,10 @@ class MergeRequest < ActiveRecord::Base
# Return the set of issues that will be closed if this merge request is accepted. # Return the set of issues that will be closed if this merge request is accepted.
def closes_issues(current_user = self.author) def closes_issues(current_user = self.author)
if target_branch == project.default_branch if target_branch == project.default_branch
issues = commits.flat_map { |c| c.closes_issues(current_user) } messages = commits.map(&:safe_message) << description
issues.push(*Gitlab::ClosingIssueExtractor.new(project, current_user).
closed_by_message(description)) Gitlab::ClosingIssueExtractor.new(project, current_user).
issues.uniq(&:id) closed_by_message(messages.join("\n"))
else else
[] []
end end
......
...@@ -36,6 +36,7 @@ ...@@ -36,6 +36,7 @@
# build_coverage_regex :string # build_coverage_regex :string
# build_allow_git_fetch :boolean default(TRUE), not null # build_allow_git_fetch :boolean default(TRUE), not null
# build_timeout :integer default(3600), not null # build_timeout :integer default(3600), not null
# pending_delete :boolean
# #
require 'carrierwave/orm/activerecord' require 'carrierwave/orm/activerecord'
......
...@@ -13,7 +13,7 @@ class DeleteUserService ...@@ -13,7 +13,7 @@ class DeleteUserService
user.personal_projects.each do |project| user.personal_projects.each do |project|
# Skip repository removal because we remove directory with namespace # Skip repository removal because we remove directory with namespace
# that contain all this repositories # that contain all this repositories
::Projects::DestroyService.new(project, current_user, skip_repo: true).execute ::Projects::DestroyService.new(project, current_user, skip_repo: true).pending_delete!
end end
user.destroy user.destroy
......
...@@ -9,7 +9,7 @@ class DestroyGroupService ...@@ -9,7 +9,7 @@ class DestroyGroupService
@group.projects.each do |project| @group.projects.each do |project|
# Skip repository removal because we remove directory with namespace # Skip repository removal because we remove directory with namespace
# that contain all this repositories # that contain all this repositories
::Projects::DestroyService.new(project, current_user, skip_repo: true).execute ::Projects::DestroyService.new(project, current_user, skip_repo: true).pending_delete!
end end
@group.destroy @group.destroy
......
...@@ -6,6 +6,12 @@ module Projects ...@@ -6,6 +6,12 @@ module Projects
DELETED_FLAG = '+deleted' DELETED_FLAG = '+deleted'
def pending_delete!
project.update_attribute(:pending_delete, true)
ProjectDestroyWorker.perform_in(1.minute, project.id, current_user.id, params)
end
def execute def execute
return false unless can?(current_user, :remove_project, project) return false unless can?(current_user, :remove_project, project)
......
...@@ -105,14 +105,14 @@ ...@@ -105,14 +105,14 @@
= f.check_box :signin_enabled = f.check_box :signin_enabled
Sign-in enabled Sign-in enabled
.form-group .form-group
= f.label :two_factor_authentication, 'Two-Factor authentication', class: 'control-label col-sm-2' = f.label :two_factor_authentication, 'Two-factor authentication', class: 'control-label col-sm-2'
.col-sm-10 .col-sm-10
.checkbox .checkbox
= f.label :require_two_factor_authentication do = f.label :require_two_factor_authentication do
= f.check_box :require_two_factor_authentication = f.check_box :require_two_factor_authentication
Require all users to setup Two-Factor authentication Require all users to setup Two-factor authentication
.form-group .form-group
= f.label :two_factor_authentication, 'Two-Factor grace period (hours)', class: 'control-label col-sm-2' = f.label :two_factor_authentication, 'Two-factor grace period (hours)', class: 'control-label col-sm-2'
.col-sm-10 .col-sm-10
= f.number_field :two_factor_grace_period, min: 0, class: 'form-control', placeholder: '0' = f.number_field :two_factor_grace_period, min: 0, class: 'form-control', placeholder: '0'
.help-block Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication .help-block Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication
......
...@@ -4,6 +4,15 @@ ...@@ -4,6 +4,15 @@
Authorize Authorize
%strong.text-info= @pre_auth.client.name %strong.text-info= @pre_auth.client.name
to use your account? to use your account?
- if current_user.admin?
.text-warning.prepend-top-20
%p
= icon("exclamation-triangle fw")
You are an admin, which means granting access to
%strong #{@pre_auth.client.name}
will allow them to interact with GitLab as an admin as well. Proceed with caution.
- if @pre_auth.scopes - if @pre_auth.scopes
#oauth-permissions #oauth-permissions
%p This application will be able to: %p This application will be able to:
......
- page_title 'Two-factor Authentication', 'Account' - page_title 'Two-factor Authentication', 'Account'
%h2.page-title Two-Factor Authentication (2FA) %h2.page-title Two-factor Authentication (2FA)
%p %p
Download the Google Authenticator application from App Store for iOS or Google Download the Google Authenticator application from App Store for iOS or Google
Play for Android and scan this code. Play for Android and scan this code.
......
...@@ -35,6 +35,9 @@ ...@@ -35,6 +35,9 @@
- if blob.lfs_pointer? - if blob.lfs_pointer?
= render "download", blob: blob = render "download", blob: blob
- elsif blob.text? - elsif blob.text?
- if blob_svg?(blob)
= render "image", blob: sanitize_svg(blob)
- else
= render "text", blob: blob = render "text", blob: blob
- elsif blob.image? - elsif blob.image?
= render "image", blob: blob = render "image", blob: blob
......
...@@ -7,16 +7,20 @@ ...@@ -7,16 +7,20 @@
= submodule_link(blob, @commit.id, project.repository) = submodule_link(blob, @commit.id, project.repository)
- else - else
= blob_icon blob.mode, blob.name = blob_icon blob.mode, blob.name
= link_to "#diff-#{i}" do = link_to "#diff-#{i}" do
- if diff_file.renamed_file
- old_path, new_path = mark_inline_diffs(diff_file.old_path, diff_file.new_path)
%strong.filename.old
= old_path
&rarr;
%strong.filename.new
= new_path
- else
%strong %strong
= diff_file.new_path = diff_file.new_path
- if diff_file.deleted_file - if diff_file.deleted_file
deleted deleted
- elsif diff_file.renamed_file
renamed from
%strong
= diff_file.old_path
- if diff_file.mode_changed? - if diff_file.mode_changed?
%small %small
......
class ProjectDestroyWorker
include Sidekiq::Worker
sidekiq_options queue: :default
def perform(project_id, user_id, params)
begin
project = Project.find(project_id)
rescue ActiveRecord::RecordNotFound
return
end
user = User.find(user_id)
::Projects::DestroyService.new(project, user, params).execute
end
end
...@@ -532,7 +532,7 @@ Rails.application.routes.draw do ...@@ -532,7 +532,7 @@ Rails.application.routes.draw do
end end
resource :avatar, only: [:show, :destroy] resource :avatar, only: [:show, :destroy]
resources :commit, only: [:show], constraints: { id: /[[:alnum:]]{6,40}/ } do resources :commit, only: [:show], constraints: { id: /\h{7,40}/ } do
member do member do
get :branches get :branches
get :builds get :builds
......
class AddPendingDeleteToProject < ActiveRecord::Migration
def change
add_column :projects, :pending_delete, :boolean, default: false
end
end
...@@ -788,6 +788,7 @@ ActiveRecord::Schema.define(version: 20160129075828) do ...@@ -788,6 +788,7 @@ ActiveRecord::Schema.define(version: 20160129075828) do
t.string "build_coverage_regex" t.string "build_coverage_regex"
t.boolean "build_allow_git_fetch", default: true, null: false t.boolean "build_allow_git_fetch", default: true, null: false
t.integer "build_timeout", default: 3600, null: false t.integer "build_timeout", default: 3600, null: false
t.boolean "pending_delete", default: false
t.boolean "mirror_trigger_builds", default: false, null: false t.boolean "mirror_trigger_builds", default: false, null: false
end end
......
...@@ -18,7 +18,7 @@ GET /projects/:id/builds ...@@ -18,7 +18,7 @@ GET /projects/:id/builds
### Example of request ### Example of request
``` ```
curl -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds" curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds"
``` ```
### Example of response ### Example of response
...@@ -123,7 +123,7 @@ GET /projects/:id/repository/commits/:sha/builds ...@@ -123,7 +123,7 @@ GET /projects/:id/repository/commits/:sha/builds
### Example of request ### Example of request
``` ```
curl -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/repository/commits/0ff3ae198f8601a285adcf5c0fff204ee6fba5fd/builds" curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/repository/commits/0ff3ae198f8601a285adcf5c0fff204ee6fba5fd/builds"
``` ```
### Example of response ### Example of response
...@@ -213,7 +213,7 @@ GET /projects/:id/builds/:build_id ...@@ -213,7 +213,7 @@ GET /projects/:id/builds/:build_id
### Example of request ### Example of request
``` ```
curl -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/8" curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/8"
``` ```
### Example of response ### Example of response
...@@ -277,7 +277,7 @@ POST /projects/:id/builds/:build_id/cancel ...@@ -277,7 +277,7 @@ POST /projects/:id/builds/:build_id/cancel
### Example of request ### Example of request
``` ```
curl -X POST -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/1/cancel" curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/1/cancel"
``` ```
### Example of response ### Example of response
...@@ -327,7 +327,7 @@ POST /projects/:id/builds/:build_id/retry ...@@ -327,7 +327,7 @@ POST /projects/:id/builds/:build_id/retry
### Example of request ### Example of request
``` ```
curl -X POST -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/1/retry" curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/builds/1/retry"
``` ```
### Example of response ### Example of response
......
...@@ -7,4 +7,4 @@ ...@@ -7,4 +7,4 @@
- [Reset your root password](reset_root_password.md) - [Reset your root password](reset_root_password.md)
- [User File Uploads](user_file_uploads.md) - [User File Uploads](user_file_uploads.md)
- [How we manage the CRIME vulnerability](crime_vulnerability.md) - [How we manage the CRIME vulnerability](crime_vulnerability.md)
- [Enforce Two-Factor authentication](two_factor_authentication.md) - [Enforce Two-factor authentication](two_factor_authentication.md)
...@@ -152,9 +152,10 @@ The name of this branch should start with the issue number, for example '15-requ ...@@ -152,9 +152,10 @@ The name of this branch should start with the issue number, for example '15-requ
When you are done or want to discuss the code you open a merge request. When you are done or want to discuss the code you open a merge request.
This is an online place to discuss the change and review the code. This is an online place to discuss the change and review the code.
Creating a branch is a manual action since you do not always want to merge a new branch you push, it could be a long-running environment or release branch. Opening a merge request is a manual action since you do not always want to merge a new branch you push, it could be a long-running environment or release branch.
If you create the merge request but do not assign it to anyone it is a 'work-in-process' merge request. If you open the merge request but do not assign it to anyone it is a 'Work In Progress' merge request.
These are used to discuss the proposed implementation but are not ready for inclusion in the master branch yet. These are used to discuss the proposed implementation but are not ready for inclusion in the master branch yet.
_Pro tip:_ Start the title of the merge request with `[WIP]` or `WIP:` to prevent it from being merged before it's ready.
When the author thinks the code is ready the merge request is assigned to reviewer. When the author thinks the code is ready the merge request is assigned to reviewer.
The reviewer presses the merge button when they think the code is ready for inclusion in the master branch. The reviewer presses the merge button when they think the code is ready for inclusion in the master branch.
...@@ -185,7 +186,7 @@ If you have an issue that spans across multiple repositories, the best thing is ...@@ -185,7 +186,7 @@ If you have an issue that spans across multiple repositories, the best thing is
![Vim screen showing the rebase view](rebase.png) ![Vim screen showing the rebase view](rebase.png)
With git you can use an interactive rebase (rebase -i) to squash multiple commits into one and reorder them. With git you can use an interactive rebase (`rebase -i`) to squash multiple commits into one and reorder them.
This functionality is useful if you made a couple of commits for small changes during development and want to replace them with a single commit or if you want to make the order more logical. This functionality is useful if you made a couple of commits for small changes during development and want to replace them with a single commit or if you want to make the order more logical.
However you should never rebase commits you have pushed to a remote server. However you should never rebase commits you have pushed to a remote server.
Somebody can have referred to the commits or cherry-picked them. Somebody can have referred to the commits or cherry-picked them.
......
...@@ -320,3 +320,13 @@ Feature: Project Source Browse Files ...@@ -320,3 +320,13 @@ Feature: Project Source Browse Files
Then I should see download link and object size Then I should see download link and object size
And I should not see lfs pointer details And I should not see lfs pointer details
And I should see buttons for allowed commands And I should see buttons for allowed commands
@javascript
Scenario: I preview an SVG file
Given I click on "Upload file" link in repo
And I upload a new SVG file
And I fill the upload file commit message
And I fill the new branch name
And I click on "Upload file"
Given I visit the SVG file
Then I can see the new rendered SVG image
...@@ -351,6 +351,19 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps ...@@ -351,6 +351,19 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
expect(page).to have_content "You're not allowed to make changes to this project directly. A fork of this project has been created that you can make changes in, so you can submit a merge request." expect(page).to have_content "You're not allowed to make changes to this project directly. A fork of this project has been created that you can make changes in, so you can submit a merge request."
end end
# SVG files
step 'I upload a new SVG file' do
drop_in_dropzone test_svg_file
end
step 'I visit the SVG file' do
visit namespace_project_blob_path(@project.namespace, @project, 'new_branch_name/logo_sample.svg')
end
step 'I can see the new rendered SVG image' do
expect(find('.file-content')).to have_css('img')
end
private private
def set_new_content def set_new_content
...@@ -410,4 +423,8 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps ...@@ -410,4 +423,8 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
def test_image_file def test_image_file
File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif') File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif')
end end
def test_svg_file
File.join(Rails.root, 'spec', 'fixtures', 'logo_sample.svg')
end
end end
...@@ -246,7 +246,7 @@ module API ...@@ -246,7 +246,7 @@ module API
# DELETE /projects/:id # DELETE /projects/:id
delete ":id" do delete ":id" do
authorize! :remove_project, user_project authorize! :remove_project, user_project
::Projects::DestroyService.new(user_project, current_user, {}).execute ::Projects::DestroyService.new(user_project, current_user, {}).pending_delete!
end end
# Mark this project as forked from another # Mark this project as forked from another
......
...@@ -21,13 +21,13 @@ module Gitlab ...@@ -21,13 +21,13 @@ module Gitlab
# ignore highlighting for "match" lines # ignore highlighting for "match" lines
next diff_line if diff_line.type == 'match' || diff_line.type == 'nonewline' next diff_line if diff_line.type == 'match' || diff_line.type == 'nonewline'
rich_line = highlight_line(diff_line, i) rich_line = highlight_line(diff_line) || diff_line.text
if line_inline_diffs = inline_diffs[i] if line_inline_diffs = inline_diffs[i]
rich_line = InlineDiffMarker.new(diff_line.text, rich_line).mark(line_inline_diffs) rich_line = InlineDiffMarker.new(diff_line.text, rich_line).mark(line_inline_diffs)
end end
diff_line.text = rich_line.html_safe diff_line.text = rich_line
diff_line diff_line
end end
...@@ -35,8 +35,8 @@ module Gitlab ...@@ -35,8 +35,8 @@ module Gitlab
private private
def highlight_line(diff_line, index) def highlight_line(diff_line)
return html_escape(diff_line.text) unless diff_file && diff_file.diff_refs return unless diff_file && diff_file.diff_refs
line_prefix = diff_line.text.match(/\A(.)/) ? $1 : ' ' line_prefix = diff_line.text.match(/\A(.)/) ? $1 : ' '
...@@ -49,11 +49,11 @@ module Gitlab ...@@ -49,11 +49,11 @@ module Gitlab
# Only update text if line is found. This will prevent # Only update text if line is found. This will prevent
# issues with submodules given the line only exists in diff content. # issues with submodules given the line only exists in diff content.
rich_line ? line_prefix + rich_line : html_escape(diff_line.text) "#{line_prefix}#{rich_line}".html_safe if rich_line
end end
def inline_diffs def inline_diffs
@inline_diffs ||= InlineDiff.new(@raw_lines).inline_diffs @inline_diffs ||= InlineDiff.for_lines(@raw_lines)
end end
def old_lines def old_lines
...@@ -72,11 +72,6 @@ module Gitlab ...@@ -72,11 +72,6 @@ module Gitlab
[ref.project.repository, ref.id, path] [ref.project.repository, ref.id, path]
end end
def html_escape(str)
replacements = { '&' => '&amp;', '>' => '&gt;', '<' => '&lt;', '"' => '&quot;', "'" => '&#39;' }
str.gsub(/[&"'><]/, replacements)
end
end end
end end
end end
module Gitlab module Gitlab
module Diff module Diff
class InlineDiff class InlineDiff
attr_accessor :lines attr_accessor :old_line, :new_line, :offset
def initialize(lines) def self.for_lines(lines)
@lines = lines local_edit_indexes = self.find_local_edits(lines)
end
def inline_diffs
inline_diffs = [] inline_diffs = []
local_edit_indexes.each do |index| local_edit_indexes.each do |index|
old_index = index old_index = index
new_index = index + 1 new_index = index + 1
old_line = @lines[old_index] old_line = lines[old_index]
new_line = @lines[new_index] new_line = lines[new_index]
old_diffs, new_diffs = new(old_line, new_line, offset: 1).inline_diffs
inline_diffs[old_index] = old_diffs
inline_diffs[new_index] = new_diffs
end
inline_diffs
end
def initialize(old_line, new_line, offset: 0)
@old_line = old_line[offset..-1]
@new_line = new_line[offset..-1]
@offset = offset
end
def inline_diffs
# Skip inline diff if empty line was replaced with content # Skip inline diff if empty line was replaced with content
next if old_line[1..-1] == "" return if old_line == ""
# Add one, because this is based on the prefixless version lcp = longest_common_prefix(old_line, new_line)
lcp = longest_common_prefix(old_line[1..-1], new_line[1..-1]) + 1
lcs = longest_common_suffix(old_line[lcp..-1], new_line[lcp..-1]) lcs = longest_common_suffix(old_line[lcp..-1], new_line[lcp..-1])
old_diff_range = lcp..(old_line.length - lcs - 1) lcp += offset
new_diff_range = lcp..(new_line.length - lcs - 1) old_length = old_line.length + offset
new_length = new_line.length + offset
inline_diffs[old_index] = [old_diff_range] if old_diff_range.begin <= old_diff_range.end old_diff_range = lcp..(old_length - lcs - 1)
inline_diffs[new_index] = [new_diff_range] if new_diff_range.begin <= new_diff_range.end new_diff_range = lcp..(new_length - lcs - 1)
end
inline_diffs old_diffs = [old_diff_range] if old_diff_range.begin <= old_diff_range.end
new_diffs = [new_diff_range] if new_diff_range.begin <= new_diff_range.end
[old_diffs, new_diffs]
end end
private private
# Find runs of single line edits def self.find_local_edits(lines)
def local_edit_indexes line_prefixes = lines.map { |line| line.match(/\A([+-])/) ? $1 : ' ' }
line_prefixes = @lines.map { |line| line.match(/\A([+-])/) ? $1 : ' ' }
joined_line_prefixes = " #{line_prefixes.join} " joined_line_prefixes = " #{line_prefixes.join} "
offset = 0 offset = 0
......
...@@ -5,10 +5,12 @@ module Gitlab ...@@ -5,10 +5,12 @@ module Gitlab
def initialize(raw_line, rich_line = raw_line) def initialize(raw_line, rich_line = raw_line)
@raw_line = raw_line @raw_line = raw_line
@rich_line = rich_line @rich_line = ERB::Util.html_escape(rich_line)
end end
def mark(line_inline_diffs) def mark(line_inline_diffs)
return rich_line unless line_inline_diffs
marker_ranges = [] marker_ranges = []
line_inline_diffs.each do |inline_diff_range| line_inline_diffs.each do |inline_diff_range|
# Map the inline-diff range based on the raw line to character positions in the rich line # Map the inline-diff range based on the raw line to character positions in the rich line
...@@ -19,11 +21,15 @@ module Gitlab ...@@ -19,11 +21,15 @@ module Gitlab
offset = 0 offset = 0
# Mark each range # Mark each range
marker_ranges.each do |range| marker_ranges.each_with_index do |range, i|
offset = insert_around_range(rich_line, range, "<span class='idiff'>", "</span>", offset) class_names = ["idiff"]
class_names << "left" if i == 0
class_names << "right" if i == marker_ranges.length - 1
offset = insert_around_range(rich_line, range, "<span class='#{class_names.join(" ")}'>", "</span>", offset)
end end
rich_line rich_line.html_safe
end end
private private
......
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="210px" height="210px" viewBox="0 0 210 210" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
<!-- Generator: Sketch 3.3.2 (12043) - http://www.bohemiancoding.com/sketch -->
<title>Slice 1</title>
<desc>Created with Sketch.</desc>
<script>alert('FAIL')</script>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
<g id="logo" sketch:type="MSLayerGroup" transform="translate(0.000000, 10.000000)">
<g id="Page-1" sketch:type="MSShapeGroup">
<g id="Fill-1-+-Group-24">
<g id="Group-24">
<g id="Group">
<path d="M105.0614,193.655 L105.0614,193.655 L143.7014,74.734 L66.4214,74.734 L105.0614,193.655 L105.0614,193.655 Z" id="Fill-4" fill="#E24329" class="tanuki-shape"></path>
<path d="M105.0614,193.6548 L66.4214,74.7338 L12.2684,74.7338 L105.0614,193.6548 L105.0614,193.6548 Z" id="Fill-8" fill="#FC6D26" class="tanuki-shape"></path>
<path d="M12.2685,74.7341 L12.2685,74.7341 L0.5265,110.8731 C-0.5445,114.1691 0.6285,117.7801 3.4325,119.8171 L105.0615,193.6551 L12.2685,74.7341 L12.2685,74.7341 Z" id="Fill-12" fill="#FCA326" class="tanuki-shape"></path>
<path d="M12.2685,74.7342 L66.4215,74.7342 L43.1485,3.1092 C41.9515,-0.5768 36.7375,-0.5758 35.5405,3.1092 L12.2685,74.7342 L12.2685,74.7342 Z" id="Fill-16" fill="#E24329" class="tanuki-shape"></path>
<path d="M105.0614,193.6548 L143.7014,74.7338 L197.8544,74.7338 L105.0614,193.6548 L105.0614,193.6548 Z" id="Fill-18" fill="#FC6D26" class="tanuki-shape"></path>
<path d="M197.8544,74.7341 L197.8544,74.7341 L209.5964,110.8731 C210.6674,114.1691 209.4944,117.7801 206.6904,119.8171 L105.0614,193.6551 L197.8544,74.7341 L197.8544,74.7341 Z" id="Fill-20" fill="#FCA326" class="tanuki-shape"></path>
<path d="M197.8544,74.7342 L143.7014,74.7342 L166.9744,3.1092 C168.1714,-0.5768 173.3854,-0.5758 174.5824,3.1092 L197.8544,74.7342 L197.8544,74.7342 Z" id="Fill-22" fill="#E24329" class="tanuki-shape"></path>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>
...@@ -55,7 +55,7 @@ ...@@ -55,7 +55,7 @@
:type: new :type: new
:number: 9 :number: 9
:text: | :text: |
+<span id="LC9" class="line"> <span class="k">raise</span> <span class="no"><span class='idiff'>RuntimeError</span></span><span class="p"><span class='idiff'>,</span></span><span class='idiff'> </span><span class="s2">&quot;System commands must be given as an array of strings&quot;</span></span> +<span id="LC9" class="line"> <span class="k">raise</span> <span class="no"><span class='idiff left'>RuntimeError</span></span><span class="p"><span class='idiff'>,</span></span><span class='idiff right'> </span><span class="s2">&quot;System commands must be given as an array of strings&quot;</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9 :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9
- :left: - :left:
:type: :type:
......
...@@ -104,8 +104,7 @@ describe DiffHelper do ...@@ -104,8 +104,7 @@ describe DiffHelper do
end end
end end
describe 'diff_line_content' do describe '#diff_line_content' do
it 'should return non breaking space when line is empty' do it 'should return non breaking space when line is empty' do
expect(diff_line_content(nil)).to eq(' &nbsp;') expect(diff_line_content(nil)).to eq(' &nbsp;')
end end
...@@ -116,9 +115,19 @@ describe DiffHelper do ...@@ -116,9 +115,19 @@ describe DiffHelper do
expect(diff_line_content(diff_file.diff_lines.first.type)).to eq('match') expect(diff_line_content(diff_file.diff_lines.first.type)).to eq('match')
expect(diff_file.diff_lines.first.new_pos).to eq(6) expect(diff_file.diff_lines.first.new_pos).to eq(6)
end end
end
describe "#mark_inline_diffs" do
let(:old_line) { %{abc 'def'} }
let(:new_line) { %{abc "def"} }
it "returns strings with marked inline diffs" do
marked_old_line, marked_new_line = mark_inline_diffs(old_line, new_line)
it 'should return safe HTML' do expect(marked_old_line).to eq("abc <span class='idiff left right'>&#39;def&#39;</span>")
expect(diff_line_content(diff_file.diff_lines.first.text)).to be_html_safe expect(marked_old_line).to be_html_safe
expect(marked_new_line).to eq("abc <span class='idiff left right'>&quot;def&quot;</span>")
expect(marked_new_line).to be_html_safe
end end
end end
end end
#= require behaviors/autosize
describe 'Autosize behavior', ->
beforeEach ->
fixture.set('<textarea class="js-autosize" style="resize: vertical"></textarea>')
it 'does not overwrite the resize property', ->
load()
expect($('textarea')).toHaveCss(resize: 'vertical')
load = -> $(document).trigger('page:load')
...@@ -21,7 +21,7 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do ...@@ -21,7 +21,7 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do
let(:reference) { commit.id } let(:reference) { commit.id }
# Let's test a variety of commit SHA sizes just to be paranoid # Let's test a variety of commit SHA sizes just to be paranoid
[6, 8, 12, 18, 20, 32, 40].each do |size| [7, 8, 12, 18, 20, 32, 40].each do |size|
it "links to a valid reference of #{size} characters" do it "links to a valid reference of #{size} characters" do
doc = reference_filter("See #{reference[0...size]}") doc = reference_filter("See #{reference[0...size]}")
...@@ -35,7 +35,7 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do ...@@ -35,7 +35,7 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do
doc = reference_filter("See #{commit.id}") doc = reference_filter("See #{commit.id}")
expect(doc.text).to eq "See #{commit.short_id}" expect(doc.text).to eq "See #{commit.short_id}"
doc = reference_filter("See #{commit.id[0...6]}") doc = reference_filter("See #{commit.id[0...7]}")
expect(doc.text).to eq "See #{commit.short_id}" expect(doc.text).to eq "See #{commit.short_id}"
end end
......
...@@ -9,33 +9,69 @@ describe Gitlab::Diff::Highlight, lib: true do ...@@ -9,33 +9,69 @@ describe Gitlab::Diff::Highlight, lib: true do
let(:diff_file) { Gitlab::Diff::File.new(diff, [commit.parent, commit]) } let(:diff_file) { Gitlab::Diff::File.new(diff, [commit.parent, commit]) }
describe '#highlight' do describe '#highlight' do
let(:diff_lines) { Gitlab::Diff::Highlight.new(diff_file).highlight } context "with a diff file" do
let(:subject) { Gitlab::Diff::Highlight.new(diff_file).highlight }
it 'should return Gitlab::Diff::Line elements' do it 'should return Gitlab::Diff::Line elements' do
expect(diff_lines.first).to be_an_instance_of(Gitlab::Diff::Line) expect(subject.first).to be_an_instance_of(Gitlab::Diff::Line)
end end
it 'should not modify "match" lines' do it 'should not modify "match" lines' do
expect(diff_lines[0].text).to eq('@@ -6,12 +6,18 @@ module Popen') expect(subject[0].text).to eq('@@ -6,12 +6,18 @@ module Popen')
expect(diff_lines[22].text).to eq('@@ -19,6 +25,7 @@ module Popen') expect(subject[22].text).to eq('@@ -19,6 +25,7 @@ module Popen')
end end
it 'should highlight unchanged lines' do it 'highlights and marks unchanged lines' do
code = %Q{ <span id="LC7" class="line"> <span class="k">def</span> <span class="nf">popen</span><span class="p">(</span><span class="n">cmd</span><span class="p">,</span> <span class="n">path</span><span class="o">=</span><span class="kp">nil</span><span class="p">)</span></span>\n} code = %Q{ <span id="LC7" class="line"> <span class="k">def</span> <span class="nf">popen</span><span class="p">(</span><span class="n">cmd</span><span class="p">,</span> <span class="n">path</span><span class="o">=</span><span class="kp">nil</span><span class="p">)</span></span>\n}
expect(diff_lines[2].text).to eq(code) expect(subject[2].text).to eq(code)
end end
it 'should highlight removed lines' do it 'highlights and marks removed lines' do
code = %Q{-<span id="LC9" class="line"> <span class="k">raise</span> <span class="s2">&quot;System commands must be given as an array of strings&quot;</span></span>\n} code = %Q{-<span id="LC9" class="line"> <span class="k">raise</span> <span class="s2">&quot;System commands must be given as an array of strings&quot;</span></span>\n}
expect(diff_lines[4].text).to eq(code) expect(subject[4].text).to eq(code)
end end
it 'should highlight added lines' do it 'highlights and marks added lines' do
code = %Q{+<span id="LC9" class="line"> <span class="k">raise</span> <span class="no"><span class='idiff'>RuntimeError</span></span><span class="p"><span class='idiff'>,</span></span><span class='idiff'> </span><span class="s2">&quot;System commands must be given as an array of strings&quot;</span></span>\n} code = %Q{+<span id="LC9" class="line"> <span class="k">raise</span> <span class="no"><span class='idiff left'>RuntimeError</span></span><span class="p"><span class='idiff'>,</span></span><span class='idiff right'> </span><span class="s2">&quot;System commands must be given as an array of strings&quot;</span></span>\n}
expect(diff_lines[5].text).to eq(code) expect(subject[5].text).to eq(code)
end
end
context "with diff lines" do
let(:subject) { Gitlab::Diff::Highlight.new(diff_file.diff_lines).highlight }
it 'should return Gitlab::Diff::Line elements' do
expect(subject.first).to be_an_instance_of(Gitlab::Diff::Line)
end
it 'should not modify "match" lines' do
expect(subject[0].text).to eq('@@ -6,12 +6,18 @@ module Popen')
expect(subject[22].text).to eq('@@ -19,6 +25,7 @@ module Popen')
end
it 'marks unchanged lines' do
code = %Q{ def popen(cmd, path=nil)}
expect(subject[2].text).to eq(code)
expect(subject[2].text).not_to be_html_safe
end
it 'marks removed lines' do
code = %Q{- raise "System commands must be given as an array of strings"}
expect(subject[4].text).to eq(code)
expect(subject[4].text).not_to be_html_safe
end
it 'marks added lines' do
code = %Q{+ raise <span class='idiff left right'>RuntimeError, </span>&quot;System commands must be given as an array of strings&quot;}
expect(subject[5].text).to eq(code)
expect(subject[5].text).to be_html_safe
end
end end
end end
end end
...@@ -2,14 +2,28 @@ require 'spec_helper' ...@@ -2,14 +2,28 @@ require 'spec_helper'
describe Gitlab::Diff::InlineDiffMarker, lib: true do describe Gitlab::Diff::InlineDiffMarker, lib: true do
describe '#inline_diffs' do describe '#inline_diffs' do
context "when the rich text is html safe" do
let(:raw) { "abc 'def'" } let(:raw) { "abc 'def'" }
let(:rich) { %{<span class="abc">abc</span><span class="space"> </span><span class="def">&#39;def&#39;</span>} } let(:rich) { %{<span class="abc">abc</span><span class="space"> </span><span class="def">&#39;def&#39;</span>}.html_safe }
let(:inline_diffs) { [2..5] } let(:inline_diffs) { [2..5] }
let(:subject) { Gitlab::Diff::InlineDiffMarker.new(raw, rich).mark(inline_diffs) } let(:subject) { Gitlab::Diff::InlineDiffMarker.new(raw, rich).mark(inline_diffs) }
it 'marks the inline diffs' do it 'marks the inline diffs' do
expect(subject).to eq(%{<span class="abc">ab<span class='idiff'>c</span></span><span class="space"><span class='idiff'> </span></span><span class="def"><span class='idiff'>&#39;d</span>ef&#39;</span>}) expect(subject).to eq(%{<span class="abc">ab<span class='idiff left'>c</span></span><span class="space"><span class='idiff'> </span></span><span class="def"><span class='idiff right'>&#39;d</span>ef&#39;</span>})
expect(subject).to be_html_safe
end
end
context "when the text text is not html safe" do
let(:raw) { "abc 'def'" }
let(:inline_diffs) { [2..5] }
let(:subject) { Gitlab::Diff::InlineDiffMarker.new(raw).mark(inline_diffs) }
it 'marks the inline diffs' do
expect(subject).to eq(%{ab<span class='idiff left right'>c &#39;d</span>ef&#39;})
expect(subject).to be_html_safe
end
end end
end end
end end
require 'spec_helper' require 'spec_helper'
describe Gitlab::Diff::InlineDiff, lib: true do describe Gitlab::Diff::InlineDiff, lib: true do
describe '#inline_diffs' do describe '.for_lines' do
let(:diff) do let(:diff) do
<<eos <<eos
class Test class Test
...@@ -13,7 +13,7 @@ describe Gitlab::Diff::InlineDiff, lib: true do ...@@ -13,7 +13,7 @@ describe Gitlab::Diff::InlineDiff, lib: true do
eos eos
end end
let(:subject) { Gitlab::Diff::InlineDiff.new(diff.lines).inline_diffs } let(:subject) { described_class.for_lines(diff.lines) }
it 'finds all inline diffs' do it 'finds all inline diffs' do
expect(subject[0]).to be_nil expect(subject[0]).to be_nil
...@@ -24,4 +24,17 @@ eos ...@@ -24,4 +24,17 @@ eos
expect(subject[5]).to be_nil expect(subject[5]).to be_nil
end end
end end
describe "#inline_diffs" do
let(:old_line) { "XXX def initialize(test = true)" }
let(:new_line) { "YYY def initialize(test = false)" }
let(:subject) { described_class.new(old_line, new_line, offset: 3).inline_diffs }
it "finds the inline diff" do
old_diffs, new_diffs = subject
expect(old_diffs).to eq([26..28])
expect(new_diffs).to eq([26..29])
end
end
end end
...@@ -36,7 +36,7 @@ describe SystemHook, models: true do ...@@ -36,7 +36,7 @@ describe SystemHook, models: true do
it "project_destroy hook" do it "project_destroy hook" do
user = create(:user) user = create(:user)
project = create(:empty_project, namespace: user.namespace) project = create(:empty_project, namespace: user.namespace)
Projects::DestroyService.new(project, user, {}).execute Projects::DestroyService.new(project, user, {}).pending_delete!
expect(WebMock).to have_requested(:post, @system_hook.url).with( expect(WebMock).to have_requested(:post, @system_hook.url).with(
body: /project_destroy/, body: /project_destroy/,
headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' } headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' }
......
...@@ -137,9 +137,10 @@ describe MergeRequest, models: true do ...@@ -137,9 +137,10 @@ describe MergeRequest, models: true do
describe 'detection of issues to be closed' do describe 'detection of issues to be closed' do
let(:issue0) { create :issue, project: subject.project } let(:issue0) { create :issue, project: subject.project }
let(:issue1) { create :issue, project: subject.project } let(:issue1) { create :issue, project: subject.project }
let(:commit0) { double('commit0', closes_issues: [issue0]) }
let(:commit1) { double('commit1', closes_issues: [issue0]) } let(:commit0) { double('commit0', safe_message: "Fixes #{issue0.to_reference}") }
let(:commit2) { double('commit2', closes_issues: [issue1]) } let(:commit1) { double('commit1', safe_message: "Fixes #{issue0.to_reference}") }
let(:commit2) { double('commit2', safe_message: "Fixes #{issue1.to_reference}") }
before do before do
allow(subject).to receive(:commits).and_return([commit0, commit1, commit2]) allow(subject).to receive(:commits).and_return([commit0, commit1, commit2])
...@@ -149,7 +150,9 @@ describe MergeRequest, models: true do ...@@ -149,7 +150,9 @@ describe MergeRequest, models: true do
allow(subject.project).to receive(:default_branch). allow(subject.project).to receive(:default_branch).
and_return(subject.target_branch) and_return(subject.target_branch)
expect(subject.closes_issues).to eq([issue0, issue1].sort_by(&:id)) closed = subject.closes_issues
expect(closed).to include(issue0, issue1)
end end
it 'only lists issues as to be closed if it targets the default branch' do it 'only lists issues as to be closed if it targets the default branch' do
...@@ -167,17 +170,6 @@ describe MergeRequest, models: true do ...@@ -167,17 +170,6 @@ describe MergeRequest, models: true do
expect(subject.closes_issues).to include(issue2) expect(subject.closes_issues).to include(issue2)
end end
context 'for a project with JIRA integration' do
let(:issue0) { JiraIssue.new('JIRA-123', subject.project) }
let(:issue1) { JiraIssue.new('FOOBAR-4567', subject.project) }
it 'returns sorted JiraIssues' do
allow(subject.project).to receive_messages(default_branch: subject.target_branch)
expect(subject.closes_issues).to eq([issue0, issue1])
end
end
end end
describe "#work_in_progress?" do describe "#work_in_progress?" do
......
...@@ -321,12 +321,12 @@ describe Projects::HooksController, 'routing' do ...@@ -321,12 +321,12 @@ describe Projects::HooksController, 'routing' do
end end
end end
# project_commit GET /:project_id/commit/:id(.:format) commit#show {id: /[[:alnum:]]{6,40}/, project_id: /[^\/]+/} # project_commit GET /:project_id/commit/:id(.:format) commit#show {id: /\h{7,40}/, project_id: /[^\/]+/}
describe Projects::CommitController, 'routing' do describe Projects::CommitController, 'routing' do
it 'to #show' do it 'to #show' do
expect(get('/gitlab/gitlabhq/commit/4246fb')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fb') expect(get('/gitlab/gitlabhq/commit/4246fbd')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fbd')
expect(get('/gitlab/gitlabhq/commit/4246fb.diff')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fb', format: 'diff') expect(get('/gitlab/gitlabhq/commit/4246fbd.diff')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fbd', format: 'diff')
expect(get('/gitlab/gitlabhq/commit/4246fb.patch')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fb', format: 'patch') expect(get('/gitlab/gitlabhq/commit/4246fbd.patch')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fbd', format: 'patch')
expect(get('/gitlab/gitlabhq/commit/4246fbd13872934f72a8fd0d6fb1317b47b59cb5')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fbd13872934f72a8fd0d6fb1317b47b59cb5') expect(get('/gitlab/gitlabhq/commit/4246fbd13872934f72a8fd0d6fb1317b47b59cb5')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fbd13872934f72a8fd0d6fb1317b47b59cb5')
end end
end end
......
...@@ -67,9 +67,9 @@ module FilterSpecHelper ...@@ -67,9 +67,9 @@ module FilterSpecHelper
if reference =~ /\A(.+)?.\d+\z/ if reference =~ /\A(.+)?.\d+\z/
# Integer-based reference with optional project prefix # Integer-based reference with optional project prefix
reference.gsub(/\d+\z/) { |i| i.to_i + 1 } reference.gsub(/\d+\z/) { |i| i.to_i + 1 }
elsif reference =~ /\A(.+@)?(\h{6,40}\z)/ elsif reference =~ /\A(.+@)?(\h{7,40}\z)/
# SHA-based reference with optional prefix # SHA-based reference with optional prefix
reference.gsub(/\h{6,40}\z/) { |v| v.reverse } reference.gsub(/\h{7,40}\z/) { |v| v.reverse }
else else
reference.gsub(/\w+\z/) { |v| v.reverse } reference.gsub(/\w+\z/) { |v| v.reverse }
end end
......
/*!
* jQuery resize event - v1.1 - 3/14/2010
* http://benalman.com/projects/jquery-resize-plugin/
*
* Copyright (c) 2010 "Cowboy" Ben Alman
* Dual licensed under the MIT and GPL licenses.
* http://benalman.com/about/license/
*/
// Script: jQuery resize event
//
// *Version: 1.1, Last updated: 3/14/2010*
//
// Project Home - http://benalman.com/projects/jquery-resize-plugin/
// GitHub - http://github.com/cowboy/jquery-resize/
// Source - http://github.com/cowboy/jquery-resize/raw/master/jquery.ba-resize.js
// (Minified) - http://github.com/cowboy/jquery-resize/raw/master/jquery.ba-resize.min.js (1.0kb)
//
// About: License
//
// Copyright (c) 2010 "Cowboy" Ben Alman,
// Dual licensed under the MIT and GPL licenses.
// http://benalman.com/about/license/
//
// About: Examples
//
// This working example, complete with fully commented code, illustrates a few
// ways in which this plugin can be used.
//
// resize event - http://benalman.com/code/projects/jquery-resize/examples/resize/
//
// About: Support and Testing
//
// Information about what version or versions of jQuery this plugin has been
// tested with, what browsers it has been tested in, and where the unit tests
// reside (so you can test it yourself).
//
// jQuery Versions - 1.3.2, 1.4.1, 1.4.2
// Browsers Tested - Internet Explorer 6-8, Firefox 2-3.6, Safari 3-4, Chrome, Opera 9.6-10.1.
// Unit Tests - http://benalman.com/code/projects/jquery-resize/unit/
//
// About: Release History
//
// 1.1 - (3/14/2010) Fixed a minor bug that was causing the event to trigger
// immediately after bind in some circumstances. Also changed $.fn.data
// to $.data to improve performance.
// 1.0 - (2/10/2010) Initial release
(function($,window,undefined){
'$:nomunge'; // Used by YUI compressor.
// A jQuery object containing all non-window elements to which the resize
// event is bound.
var elems = $([]),
// Extend $.resize if it already exists, otherwise create it.
jq_resize = $.resize = $.extend( $.resize, {} ),
timeout_id,
// Reused strings.
str_setTimeout = 'setTimeout',
str_resize = 'resize',
str_data = str_resize + '-special-event',
str_delay = 'delay',
str_throttle = 'throttleWindow';
// Property: jQuery.resize.delay
//
// The numeric interval (in milliseconds) at which the resize event polling
// loop executes. Defaults to 250.
jq_resize[ str_delay ] = 250;
// Property: jQuery.resize.throttleWindow
//
// Throttle the native window object resize event to fire no more than once
// every <jQuery.resize.delay> milliseconds. Defaults to true.
//
// Because the window object has its own resize event, it doesn't need to be
// provided by this plugin, and its execution can be left entirely up to the
// browser. However, since certain browsers fire the resize event continuously
// while others do not, enabling this will throttle the window resize event,
// making event behavior consistent across all elements in all browsers.
//
// While setting this property to false will disable window object resize
// event throttling, please note that this property must be changed before any
// window object resize event callbacks are bound.
jq_resize[ str_throttle ] = true;
// Event: resize event
//
// Fired when an element's width or height changes. Because browsers only
// provide this event for the window element, for other elements a polling
// loop is initialized, running every <jQuery.resize.delay> milliseconds
// to see if elements' dimensions have changed. You may bind with either
// .resize( fn ) or .bind( "resize", fn ), and unbind with .unbind( "resize" ).
//
// Usage:
//
// > jQuery('selector').bind( 'resize', function(e) {
// > // element's width or height has changed!
// > ...
// > });
//
// Additional Notes:
//
// * The polling loop is not created until at least one callback is actually
// bound to the 'resize' event, and this single polling loop is shared
// across all elements.
//
// Double firing issue in jQuery 1.3.2:
//
// While this plugin works in jQuery 1.3.2, if an element's event callbacks
// are manually triggered via .trigger( 'resize' ) or .resize() those
// callbacks may double-fire, due to limitations in the jQuery 1.3.2 special
// events system. This is not an issue when using jQuery 1.4+.
//
// > // While this works in jQuery 1.4+
// > $(elem).css({ width: new_w, height: new_h }).resize();
// >
// > // In jQuery 1.3.2, you need to do this:
// > var elem = $(elem);
// > elem.css({ width: new_w, height: new_h });
// > elem.data( 'resize-special-event', { width: elem.width(), height: elem.height() } );
// > elem.resize();
$.event.special[ str_resize ] = {
// Called only when the first 'resize' event callback is bound per element.
setup: function() {
// Since window has its own native 'resize' event, return false so that
// jQuery will bind the event using DOM methods. Since only 'window'
// objects have a .setTimeout method, this should be a sufficient test.
// Unless, of course, we're throttling the 'resize' event for window.
if ( !jq_resize[ str_throttle ] && this[ str_setTimeout ] ) { return false; }
var elem = $(this);
// Add this element to the list of internal elements to monitor.
elems = elems.add( elem );
// Initialize data store on the element.
$.data( this, str_data, { w: elem.width(), h: elem.height() } );
// If this is the first element added, start the polling loop.
if ( elems.length === 1 ) {
loopy();
}
},
// Called only when the last 'resize' event callback is unbound per element.
teardown: function() {
// Since window has its own native 'resize' event, return false so that
// jQuery will unbind the event using DOM methods. Since only 'window'
// objects have a .setTimeout method, this should be a sufficient test.
// Unless, of course, we're throttling the 'resize' event for window.
if ( !jq_resize[ str_throttle ] && this[ str_setTimeout ] ) { return false; }
var elem = $(this);
// Remove this element from the list of internal elements to monitor.
elems = elems.not( elem );
// Remove any data stored on the element.
elem.removeData( str_data );
// If this is the last element removed, stop the polling loop.
if ( !elems.length ) {
clearTimeout( timeout_id );
}
},
// Called every time a 'resize' event callback is bound per element (new in
// jQuery 1.4).
add: function( handleObj ) {
// Since window has its own native 'resize' event, return false so that
// jQuery doesn't modify the event object. Unless, of course, we're
// throttling the 'resize' event for window.
if ( !jq_resize[ str_throttle ] && this[ str_setTimeout ] ) { return false; }
var old_handler;
// The new_handler function is executed every time the event is triggered.
// This is used to update the internal element data store with the width
// and height when the event is triggered manually, to avoid double-firing
// of the event callback. See the "Double firing issue in jQuery 1.3.2"
// comments above for more information.
function new_handler( e, w, h ) {
var elem = $(this),
data = $.data( this, str_data );
// If called from the polling loop, w and h will be passed in as
// arguments. If called manually, via .trigger( 'resize' ) or .resize(),
// those values will need to be computed.
data.w = w !== undefined ? w : elem.width();
data.h = h !== undefined ? h : elem.height();
old_handler.apply( this, arguments );
};
// This may seem a little complicated, but it normalizes the special event
// .add method between jQuery 1.4/1.4.1 and 1.4.2+
if ( $.isFunction( handleObj ) ) {
// 1.4, 1.4.1
old_handler = handleObj;
return new_handler;
} else {
// 1.4.2+
old_handler = handleObj.handler;
handleObj.handler = new_handler;
}
}
};
function loopy() {
// Start the polling loop, asynchronously.
timeout_id = window[ str_setTimeout ](function(){
// Iterate over all elements to which the 'resize' event is bound.
elems.each(function(){
var elem = $(this),
width = elem.width(),
height = elem.height(),
data = $.data( this, str_data );
// If element size has changed since the last time, update the element
// data store and trigger the 'resize' event.
if ( width !== data.w || height !== data.h ) {
elem.trigger( str_resize, [ data.w = width, data.h = height ] );
}
});
// Loop.
loopy();
}, jq_resize[ str_delay ] );
};
})(jQuery,this);
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