mirror of
https://github.com/wahyd4/gitlabhq.git
synced 2026-08-22 19:16:39 +10:00
Merge branch 'master' into fix-admin-should-be-able-to-add-himself-to-group
This commit is contained in:
@@ -1,16 +1,30 @@
|
||||
Please view this file on the master branch, on stable branches it's out of date.
|
||||
|
||||
v 8.3.0 (unreleased)
|
||||
- Fix application settings cache not expiring after changes (Stan Hu)
|
||||
- Fix API setting of 'public' attribute to false will make a project private (Stan Hu)
|
||||
- Handle and report SSL errors in Web hook test (Stan Hu)
|
||||
- Fix: Assignee selector is empty when 'Unassigned' is selected (Jose Corcuera)
|
||||
- Fix 500 error when update group member permission
|
||||
- Trim leading and trailing whitespace of milestone and issueable titles (Jose Corcuera)
|
||||
- Recognize issue/MR/snippet/commit links as references
|
||||
- Add ignore whitespace change option to commit view
|
||||
- Fire update hook from GitLab
|
||||
- Style warning about mentioning many people in a comment
|
||||
- Fix: sort milestones by due date once again (Greg Smethells)
|
||||
- Don't show project fork event as "imported"
|
||||
- Add API endpoint to fetch merge request commits list
|
||||
- Expose events API with comment information and author info
|
||||
- Fix: Ensure "Remove Source Branch" button is not shown when branch is being deleted. #3583
|
||||
- Fix 500 error when creating a merge request that removes a submodule
|
||||
- Run custom Git hooks when branch is created or deleted.
|
||||
- Fix bug when simultaneously accepting multiple MRs results in MRs that are of "merged" status, but not merged to the target branch
|
||||
|
||||
v 8.2.3
|
||||
- Fix application settings cache not expiring after changes (Stan Hu)
|
||||
- Fix Error 500s when creating global milestones with Unicode characters (Stan Hu)
|
||||
|
||||
v 8.2.3
|
||||
- Webhook payload has an added, modified and removed properties for each commit
|
||||
|
||||
v 8.2.2
|
||||
- Fix 404 in redirection after removing a project (Stan Hu)
|
||||
@@ -21,6 +35,7 @@ v 8.2.2
|
||||
- Fix: As an admin, cannot add oneself as a member to a group/project
|
||||
- Fix invalid links within projects dashboard header
|
||||
- Make current user the first user in assignee dropdown in issues detail page (Stan Hu)
|
||||
- Fix: duplicate email notifications on issue comments
|
||||
|
||||
v 8.2.1
|
||||
- Forcefully update builds that didn't want to update with state machine
|
||||
|
||||
@@ -99,7 +99,7 @@ gem 'org-ruby', '~> 0.9.12'
|
||||
gem 'creole', '~> 0.5.0'
|
||||
gem 'wikicloth', '0.8.1'
|
||||
gem 'asciidoctor', '~> 1.5.2'
|
||||
gem 'net-ssh', '~> 3.0.1'
|
||||
gem 'rouge', '~> 1.10.1'
|
||||
|
||||
# Diffs
|
||||
gem 'diffy', '~> 3.0.3'
|
||||
@@ -120,8 +120,8 @@ gem 'acts-as-taggable-on', '~> 3.4'
|
||||
|
||||
# Background jobs
|
||||
gem 'sinatra', '~> 1.4.4', require: nil
|
||||
gem 'sidekiq', '3.3.0'
|
||||
gem 'sidetiq', '~> 0.6.3'
|
||||
gem 'sidekiq', '~> 3.5.0'
|
||||
gem 'sidekiq-cron', '~> 0.3.0'
|
||||
|
||||
# HTTP requests
|
||||
gem "httparty", '~> 0.13.3'
|
||||
@@ -171,6 +171,7 @@ gem "underscore-rails", "~> 1.4.4"
|
||||
|
||||
# Sanitize user input
|
||||
gem "sanitize", '~> 2.0'
|
||||
gem 'babosa', '~> 1.0.2'
|
||||
|
||||
# Protect against bruteforcing
|
||||
gem "rack-attack", '~> 4.3.0'
|
||||
@@ -204,6 +205,7 @@ gem 'raphael-rails', '~> 2.1.2'
|
||||
gem 'request_store', '~> 1.2.0'
|
||||
gem 'select2-rails', '~> 3.5.9'
|
||||
gem 'virtus', '~> 1.0.1'
|
||||
gem 'net-ssh', '~> 3.0.1'
|
||||
|
||||
group :development do
|
||||
gem "foreman"
|
||||
|
||||
+33
-16
@@ -73,6 +73,7 @@ GEM
|
||||
descendants_tracker (~> 0.0.4)
|
||||
ice_nine (~> 0.11.0)
|
||||
thread_safe (~> 0.3, >= 0.3.1)
|
||||
babosa (1.0.2)
|
||||
bcrypt (3.1.10)
|
||||
benchmark-ips (2.3.0)
|
||||
better_errors (1.0.1)
|
||||
@@ -116,8 +117,23 @@ GEM
|
||||
activemodel (>= 3.2.0)
|
||||
activesupport (>= 3.2.0)
|
||||
json (>= 1.7)
|
||||
celluloid (0.16.0)
|
||||
timers (~> 4.0.0)
|
||||
celluloid (0.17.2)
|
||||
celluloid-essentials
|
||||
celluloid-extras
|
||||
celluloid-fsm
|
||||
celluloid-pool
|
||||
celluloid-supervision
|
||||
timers (>= 4.1.1)
|
||||
celluloid-essentials (0.20.5)
|
||||
timers (>= 4.1.1)
|
||||
celluloid-extras (0.20.5)
|
||||
timers (>= 4.1.1)
|
||||
celluloid-fsm (0.20.5)
|
||||
timers (>= 4.1.1)
|
||||
celluloid-pool (0.20.5)
|
||||
timers (>= 4.1.1)
|
||||
celluloid-supervision (0.20.5)
|
||||
timers (>= 4.1.1)
|
||||
charlock_holmes (0.7.3)
|
||||
chunky_png (1.3.5)
|
||||
cliver (0.3.2)
|
||||
@@ -369,7 +385,6 @@ GEM
|
||||
multi_xml (>= 0.5.2)
|
||||
httpclient (2.7.0.1)
|
||||
i18n (0.7.0)
|
||||
ice_cube (0.11.1)
|
||||
ice_nine (0.11.1)
|
||||
inflecto (0.0.2)
|
||||
ipaddress (0.8.0)
|
||||
@@ -640,6 +655,7 @@ GEM
|
||||
sexp_processor (~> 4.1)
|
||||
rubyntlm (0.5.2)
|
||||
rubypants (0.2.0)
|
||||
rufus-scheduler (3.1.10)
|
||||
rugged (0.23.3)
|
||||
safe_yaml (1.0.4)
|
||||
sanitize (2.1.0)
|
||||
@@ -667,16 +683,15 @@ GEM
|
||||
rack
|
||||
shoulda-matchers (2.8.0)
|
||||
activesupport (>= 3.0.0)
|
||||
sidekiq (3.3.0)
|
||||
celluloid (>= 0.16.0)
|
||||
connection_pool (>= 2.0.0)
|
||||
json
|
||||
redis (>= 3.0.6)
|
||||
redis-namespace (>= 1.3.1)
|
||||
sidetiq (0.6.3)
|
||||
celluloid (>= 0.14.1)
|
||||
ice_cube (= 0.11.1)
|
||||
sidekiq (>= 3.0.0)
|
||||
sidekiq (3.5.3)
|
||||
celluloid (~> 0.17.2)
|
||||
connection_pool (~> 2.2, >= 2.2.0)
|
||||
json (~> 1.0)
|
||||
redis (~> 3.2, >= 3.2.1)
|
||||
redis-namespace (~> 1.5, >= 1.5.2)
|
||||
sidekiq-cron (0.3.1)
|
||||
rufus-scheduler (>= 2.0.24)
|
||||
sidekiq (>= 2.17.3)
|
||||
simple_oauth (0.1.9)
|
||||
simplecov (0.10.0)
|
||||
docile (~> 1.1.0)
|
||||
@@ -742,7 +757,7 @@ GEM
|
||||
thor (0.19.1)
|
||||
thread_safe (0.3.5)
|
||||
tilt (1.4.1)
|
||||
timers (4.0.4)
|
||||
timers (4.1.1)
|
||||
hitimes
|
||||
timfel-krb5-auth (0.8.3)
|
||||
tinder (1.10.1)
|
||||
@@ -823,6 +838,7 @@ DEPENDENCIES
|
||||
asciidoctor (~> 1.5.2)
|
||||
attr_encrypted (~> 1.3.4)
|
||||
awesome_print (~> 1.2.0)
|
||||
babosa (~> 1.0.2)
|
||||
benchmark-ips
|
||||
better_errors (~> 1.0.1)
|
||||
binding_of_caller (~> 0.7.2)
|
||||
@@ -924,6 +940,7 @@ DEPENDENCIES
|
||||
request_store (~> 1.2.0)
|
||||
rerun (~> 0.10.0)
|
||||
responders (~> 2.0)
|
||||
rouge (~> 1.10.1)
|
||||
rqrcode-rails3 (~> 0.1.7)
|
||||
rspec-rails (~> 3.3.0)
|
||||
rubocop (~> 0.28.0)
|
||||
@@ -936,8 +953,8 @@ DEPENDENCIES
|
||||
settingslogic (~> 2.0.9)
|
||||
sham_rack
|
||||
shoulda-matchers (~> 2.8.0)
|
||||
sidekiq (= 3.3.0)
|
||||
sidetiq (~> 0.6.3)
|
||||
sidekiq (~> 3.5.0)
|
||||
sidekiq-cron (~> 0.3.0)
|
||||
simplecov (~> 0.10.0)
|
||||
sinatra (~> 1.4.4)
|
||||
six (~> 0.2.0)
|
||||
|
||||
@@ -80,7 +80,7 @@ There are a lot of [third-party applications integrating with GitLab](https://ab
|
||||
|
||||
## GitLab release cycle
|
||||
|
||||
Since 2011 a minor or major version of GitLab is released on the 22nd of every month. Patch and security releases are published when needed. New features are detailed on the [blog](https://about.gitlab.com/blog/) and in the [changelog](CHANGELOG). For more information about the release process see the [release documentation](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/release). Features that will likely be in the next releases can be found on the [feature request forum](http://feedback.gitlab.com/forums/176466-general) with the status [started](http://feedback.gitlab.com/forums/176466-general/status/796456) and [completed](http://feedback.gitlab.com/forums/176466-general/status/796457).
|
||||
For more information about the release process see the [release documentation](http://doc.gitlab.com/ce/release/).
|
||||
|
||||
## Upgrading
|
||||
|
||||
|
||||
@@ -88,4 +88,9 @@ class @AwardsHandler
|
||||
callback.call()
|
||||
|
||||
findEmojiIcon: (emoji) ->
|
||||
$(".icon[data-emoji='" + emoji + "']")
|
||||
$(".icon[data-emoji='" + emoji + "']")
|
||||
|
||||
scrollToAwards: ->
|
||||
$('body, html').animate({
|
||||
scrollTop: $('.awards').offset().top - 80
|
||||
}, 200)
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
class @Flash
|
||||
constructor: (message, type)->
|
||||
flash = $(".flash-container")
|
||||
flash.html("")
|
||||
@flash = $(".flash-container")
|
||||
@flash.html("")
|
||||
|
||||
$('<div/>',
|
||||
innerDiv = $('<div/>',
|
||||
class: "flash-#{type}",
|
||||
text: message
|
||||
).appendTo(".flash-container")
|
||||
)
|
||||
innerDiv.appendTo(".flash-container")
|
||||
|
||||
flash.click -> $(@).fadeOut()
|
||||
flash.show()
|
||||
@flash.click -> $(@).fadeOut()
|
||||
@flash.show()
|
||||
|
||||
pin: ->
|
||||
@flash.addClass('flash-pinned flash-raised')
|
||||
|
||||
@@ -111,6 +111,12 @@ class @Notes
|
||||
Note: for rendering inline notes use renderDiscussionNote
|
||||
###
|
||||
renderNote: (note) ->
|
||||
unless note.valid
|
||||
if note.award
|
||||
flash = new Flash('You have already used this award emoji!', 'alert')
|
||||
flash.pin()
|
||||
return
|
||||
|
||||
# render note if it not present in loaded list
|
||||
# or skip if rendered
|
||||
if @isNewNote(note) && !note.award
|
||||
@@ -122,6 +128,7 @@ class @Notes
|
||||
|
||||
if note.award
|
||||
awards_handler.addAwardToEmojiBar(note.note, note.emoji_path)
|
||||
awards_handler.scrollToAwards()
|
||||
|
||||
###
|
||||
Check if note does not exists on page
|
||||
@@ -362,8 +369,8 @@ class @Notes
|
||||
note = $(this).closest(".note")
|
||||
note.find(".note-attachment").remove()
|
||||
note.find(".note-body > .note-text").show()
|
||||
note.find(".js-note-attachment-delete").hide()
|
||||
note.find(".note-edit-form").hide()
|
||||
note.find(".note-header").show()
|
||||
note.find(".current-note-edit-form").remove()
|
||||
|
||||
###
|
||||
Called when clicking on the "reply" button for a diff line.
|
||||
|
||||
@@ -2,3 +2,9 @@ class @User
|
||||
constructor: ->
|
||||
$('.profile-groups-avatars').tooltip("placement": "top")
|
||||
new ProjectsList()
|
||||
|
||||
$('.hide-project-limit-message').on 'click', (e) ->
|
||||
path = '/'
|
||||
$.cookie('hide_project_limit_message', 'false', { path: path })
|
||||
$(@).parents('.project-limit-message').remove()
|
||||
e.preventDefault()
|
||||
|
||||
@@ -15,3 +15,13 @@
|
||||
@extend .alert-danger;
|
||||
}
|
||||
}
|
||||
|
||||
.flash-pinned {
|
||||
position: fixed;
|
||||
top: 80px;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.flash-raised {
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
@@ -73,11 +73,8 @@
|
||||
}
|
||||
|
||||
.referenced-users {
|
||||
padding: 10px 0;
|
||||
color: #999;
|
||||
margin-left: 10px;
|
||||
margin-top: 1px;
|
||||
margin-right: 130px;
|
||||
color: #4c4e54;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.md-preview-holder {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
.no-ssh-key-message {
|
||||
.no-ssh-key-message, .project-limit-message {
|
||||
background-color: #f28d35;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
@@ -2,8 +2,10 @@ module GlobalMilestones
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def milestones
|
||||
epoch = DateTime.parse('1970-01-01')
|
||||
@milestones = MilestonesFinder.new.execute(@projects, params)
|
||||
@milestones = GlobalMilestone.build_collection(@milestones)
|
||||
@milestones = @milestones.sort_by { |x| x.due_date.nil? ? epoch : x.due_date }
|
||||
@milestones = Kaminari.paginate_array(@milestones).page(params[:page]).per(ApplicationController::PER_PAGE)
|
||||
end
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ class Groups::MilestonesController < Groups::ApplicationController
|
||||
end
|
||||
|
||||
def milestone_path(title)
|
||||
group_milestone_path(@group, title.parameterize, title: title)
|
||||
group_milestone_path(@group, title.to_slug.to_s, title: title)
|
||||
end
|
||||
|
||||
def projects
|
||||
|
||||
@@ -70,6 +70,7 @@ class ProfilesController < Profiles::ApplicationController
|
||||
:email,
|
||||
:hide_no_password,
|
||||
:hide_no_ssh_key,
|
||||
:hide_project_limit,
|
||||
:linkedin,
|
||||
:location,
|
||||
:name,
|
||||
|
||||
@@ -25,13 +25,12 @@ class Projects::HooksController < Projects::ApplicationController
|
||||
|
||||
def test
|
||||
if !@project.empty_repo?
|
||||
status = TestHookService.new.execute(hook, current_user)
|
||||
status, message = TestHookService.new.execute(hook, current_user)
|
||||
|
||||
if status
|
||||
flash[:notice] = 'Hook successfully executed.'
|
||||
else
|
||||
flash[:alert] = 'Hook execution failed. '\
|
||||
'Ensure hook URL is correct and service is up.'
|
||||
flash[:alert] = "Hook execution failed: #{message}"
|
||||
end
|
||||
else
|
||||
flash[:alert] = 'Hook execution failed. Ensure the project has commits.'
|
||||
|
||||
@@ -131,16 +131,25 @@ class Projects::NotesController < Projects::ApplicationController
|
||||
end
|
||||
|
||||
def render_note_json(note)
|
||||
render json: {
|
||||
id: note.id,
|
||||
discussion_id: note.discussion_id,
|
||||
html: note_to_html(note),
|
||||
award: note.is_award,
|
||||
emoji_path: note.is_award ? view_context.image_url(::AwardEmoji.path_to_emoji_image(note.note)) : "",
|
||||
note: note.note,
|
||||
discussion_html: note_to_discussion_html(note),
|
||||
discussion_with_diff_html: note_to_discussion_with_diff_html(note)
|
||||
}
|
||||
if note.valid?
|
||||
render json: {
|
||||
valid: true,
|
||||
id: note.id,
|
||||
discussion_id: note.discussion_id,
|
||||
html: note_to_html(note),
|
||||
award: note.is_award,
|
||||
emoji_path: note.is_award ? view_context.image_url(::AwardEmoji.path_to_emoji_image(note.note)) : "",
|
||||
note: note.note,
|
||||
discussion_html: note_to_discussion_html(note),
|
||||
discussion_with_diff_html: note_to_discussion_with_diff_html(note)
|
||||
}
|
||||
else
|
||||
render json: {
|
||||
valid: false,
|
||||
award: note.is_award,
|
||||
errors: note.errors
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def authorize_admin_note!
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
class MilestonesFinder
|
||||
def execute(projects, params)
|
||||
milestones = Milestone.of_projects(projects)
|
||||
milestones = milestones.order("due_date ASC")
|
||||
milestones = milestones.reorder("due_date ASC")
|
||||
|
||||
case params[:state]
|
||||
when 'closed' then milestones.closed
|
||||
|
||||
@@ -209,7 +209,7 @@ module ApplicationHelper
|
||||
title: time.in_time_zone.stamp('Aug 21, 2011 9:23pm'),
|
||||
data: { toggle: 'tooltip', placement: placement, container: 'body' }
|
||||
|
||||
element += javascript_tag "$('.js-timeago').timeago()" unless skip_js
|
||||
element += javascript_tag "$('.js-timeago').last().timeago()" unless skip_js
|
||||
|
||||
element
|
||||
end
|
||||
|
||||
@@ -87,7 +87,11 @@ module IssuesHelper
|
||||
end
|
||||
|
||||
def merge_requests_sentence(merge_requests)
|
||||
merge_requests.map(&:to_reference).to_sentence(last_word_connector: ', or ')
|
||||
# Sorting based on the `!123` or `group/project!123` reference will sort
|
||||
# local merge requests first.
|
||||
merge_requests.map do |merge_request|
|
||||
merge_request.to_reference(@project)
|
||||
end.sort.to_sentence(last_word_connector: ', or ')
|
||||
end
|
||||
|
||||
def url_to_emoji(name)
|
||||
|
||||
@@ -39,7 +39,11 @@ module MergeRequestsHelper
|
||||
end
|
||||
|
||||
def issues_sentence(issues)
|
||||
issues.map(&:to_reference).to_sentence
|
||||
# Sorting based on the `#123` or `group/project#123` reference will sort
|
||||
# local issues first.
|
||||
issues.map do |issue|
|
||||
issue.to_reference(@project)
|
||||
end.sort.to_sentence
|
||||
end
|
||||
|
||||
def mr_change_branches_path(merge_request)
|
||||
|
||||
@@ -28,7 +28,9 @@ module MilestonesHelper
|
||||
Milestone.where(project_id: @projects)
|
||||
end.active
|
||||
|
||||
epoch = DateTime.parse('1970-01-01')
|
||||
grouped_milestones = GlobalMilestone.build_collection(milestones)
|
||||
grouped_milestones = grouped_milestones.sort_by { |x| x.due_date.nil? ? epoch : x.due_date }
|
||||
grouped_milestones.unshift(Milestone::None)
|
||||
grouped_milestones.unshift(Milestone::Any)
|
||||
|
||||
|
||||
@@ -43,12 +43,12 @@ class ApplicationSetting < ActiveRecord::Base
|
||||
|
||||
validates :home_page_url,
|
||||
allow_blank: true,
|
||||
format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" },
|
||||
url: true,
|
||||
if: :home_page_url_column_exist
|
||||
|
||||
validates :after_sign_out_path,
|
||||
allow_blank: true,
|
||||
format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" }
|
||||
url: true
|
||||
|
||||
validates :admin_notification_email,
|
||||
allow_blank: true,
|
||||
|
||||
@@ -16,12 +16,12 @@
|
||||
class BroadcastMessage < ActiveRecord::Base
|
||||
include Sortable
|
||||
|
||||
validates :message, presence: true
|
||||
validates :message, presence: true
|
||||
validates :starts_at, presence: true
|
||||
validates :ends_at, presence: true
|
||||
validates :ends_at, presence: true
|
||||
|
||||
validates :color, format: { with: /\A\#[0-9A-Fa-f]{3}{1,2}+\Z/ }, allow_blank: true
|
||||
validates :font, format: { with: /\A\#[0-9A-Fa-f]{3}{1,2}+\Z/ }, allow_blank: true
|
||||
validates :color, allow_blank: true, color: true
|
||||
validates :font, allow_blank: true, color: true
|
||||
|
||||
def self.current
|
||||
where("ends_at > :now AND starts_at < :now", now: Time.zone.now).last
|
||||
|
||||
@@ -20,8 +20,7 @@ module Ci
|
||||
# HTTParty timeout
|
||||
default_timeout 10
|
||||
|
||||
validates :url, presence: true,
|
||||
format: { with: URI::regexp(%w(http https)), message: "should be a valid url" }
|
||||
validates :url, presence: true, url: true
|
||||
|
||||
def execute(data)
|
||||
parsed_url = URI.parse(url)
|
||||
|
||||
+40
-4
@@ -78,11 +78,23 @@ class Commit
|
||||
}x
|
||||
end
|
||||
|
||||
def self.link_reference_pattern
|
||||
super("commit", /(?<commit>\h{6,40})/)
|
||||
end
|
||||
|
||||
def to_reference(from_project = nil)
|
||||
if cross_project_reference?(from_project)
|
||||
"#{project.to_reference}@#{id}"
|
||||
project.to_reference + self.class.reference_prefix + self.id
|
||||
else
|
||||
id
|
||||
self.id
|
||||
end
|
||||
end
|
||||
|
||||
def reference_link_text(from_project = nil)
|
||||
if cross_project_reference?(from_project)
|
||||
project.to_reference + self.class.reference_prefix + self.short_id
|
||||
else
|
||||
self.short_id
|
||||
end
|
||||
end
|
||||
|
||||
@@ -135,10 +147,10 @@ class Commit
|
||||
description.present?
|
||||
end
|
||||
|
||||
def hook_attrs
|
||||
def hook_attrs(with_changed_files: false)
|
||||
path_with_namespace = project.path_with_namespace
|
||||
|
||||
{
|
||||
data = {
|
||||
id: id,
|
||||
message: safe_message,
|
||||
timestamp: committed_date.xmlschema,
|
||||
@@ -148,6 +160,12 @@ class Commit
|
||||
email: author_email
|
||||
}
|
||||
}
|
||||
|
||||
if with_changed_files
|
||||
data.merge!(repo_changes)
|
||||
end
|
||||
|
||||
data
|
||||
end
|
||||
|
||||
# Discover issues should be closed when this commit is pushed to a project's
|
||||
@@ -196,4 +214,22 @@ class Commit
|
||||
def status
|
||||
ci_commit.try(:status) || :not_found
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def repo_changes
|
||||
changes = { added: [], modified: [], removed: [] }
|
||||
|
||||
diffs.each do |diff|
|
||||
if diff.deleted_file
|
||||
changes[:removed] << diff.old_path
|
||||
elsif diff.renamed_file || diff.new_file
|
||||
changes[:added] << diff.new_path
|
||||
else
|
||||
changes[:modified] << diff.new_path
|
||||
end
|
||||
end
|
||||
|
||||
changes
|
||||
end
|
||||
end
|
||||
|
||||
+73
-37
@@ -2,36 +2,38 @@
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# range = CommitRange.new('f3f85602...e86e1013')
|
||||
# range = CommitRange.new('f3f85602...e86e1013', project)
|
||||
# range.exclude_start? # => false
|
||||
# range.reference_title # => "Commits f3f85602 through e86e1013"
|
||||
# range.to_s # => "f3f85602...e86e1013"
|
||||
#
|
||||
# range = CommitRange.new('f3f856029bc5f966c5a7ee24cf7efefdd20e6019..e86e1013709735be5bb767e2b228930c543f25ae')
|
||||
# range = CommitRange.new('f3f856029bc5f966c5a7ee24cf7efefdd20e6019..e86e1013709735be5bb767e2b228930c543f25ae', project)
|
||||
# range.exclude_start? # => true
|
||||
# range.reference_title # => "Commits f3f85602^ through e86e1013"
|
||||
# range.to_param # => {from: "f3f856029bc5f966c5a7ee24cf7efefdd20e6019^", to: "e86e1013709735be5bb767e2b228930c543f25ae"}
|
||||
# range.to_s # => "f3f85602..e86e1013"
|
||||
#
|
||||
# # Assuming `project` is a Project with a repository containing both commits:
|
||||
# range.project = project
|
||||
# # Assuming the specified project has a repository containing both commits:
|
||||
# range.valid_commits? # => true
|
||||
#
|
||||
class CommitRange
|
||||
include ActiveModel::Conversion
|
||||
include Referable
|
||||
|
||||
attr_reader :sha_from, :notation, :sha_to
|
||||
attr_reader :commit_from, :notation, :commit_to
|
||||
attr_reader :ref_from, :ref_to
|
||||
|
||||
# Optional Project model
|
||||
attr_accessor :project
|
||||
|
||||
# See `exclude_start?`
|
||||
attr_reader :exclude_start
|
||||
|
||||
# The beginning and ending SHAs can be between 6 and 40 hex characters, and
|
||||
# The beginning and ending refs can be named or SHAs, and
|
||||
# the range notation can be double- or triple-dot.
|
||||
PATTERN = /\h{6,40}\.{2,3}\h{6,40}/
|
||||
REF_PATTERN = /[0-9a-zA-Z][0-9a-zA-Z_.-]*[0-9a-zA-Z\^]/
|
||||
PATTERN = /#{REF_PATTERN}\.{2,3}#{REF_PATTERN}/
|
||||
|
||||
# In text references, the beginning and ending refs can only be SHAs
|
||||
# between 6 and 40 hex characters.
|
||||
STRICT_PATTERN = /\h{6,40}\.{2,3}\h{6,40}/
|
||||
|
||||
def self.reference_prefix
|
||||
'@'
|
||||
@@ -43,27 +45,40 @@ class CommitRange
|
||||
def self.reference_pattern
|
||||
%r{
|
||||
(?:#{Project.reference_pattern}#{reference_prefix})?
|
||||
(?<commit_range>#{PATTERN})
|
||||
(?<commit_range>#{STRICT_PATTERN})
|
||||
}x
|
||||
end
|
||||
|
||||
def self.link_reference_pattern
|
||||
super("compare", /(?<commit_range>#{PATTERN})/)
|
||||
end
|
||||
|
||||
# Initialize a CommitRange
|
||||
#
|
||||
# range_string - The String commit range.
|
||||
# project - An optional Project model.
|
||||
#
|
||||
# Raises ArgumentError if `range_string` does not match `PATTERN`.
|
||||
def initialize(range_string, project = nil)
|
||||
def initialize(range_string, project)
|
||||
@project = project
|
||||
|
||||
range_string.strip!
|
||||
|
||||
unless range_string.match(/\A#{PATTERN}\z/)
|
||||
unless range_string =~ /\A#{PATTERN}\z/
|
||||
raise ArgumentError, "invalid CommitRange string format: #{range_string}"
|
||||
end
|
||||
|
||||
@exclude_start = !range_string.include?('...')
|
||||
@sha_from, @notation, @sha_to = range_string.split(/(\.{2,3})/, 2)
|
||||
@ref_from, @notation, @ref_to = range_string.split(/(\.{2,3})/, 2)
|
||||
|
||||
@project = project
|
||||
if project.valid_repo?
|
||||
@commit_from = project.commit(@ref_from)
|
||||
@commit_to = project.commit(@ref_to)
|
||||
end
|
||||
|
||||
if valid_commits?
|
||||
@ref_from = Commit.truncate_sha(sha_from) if sha_from.start_with?(@ref_from)
|
||||
@ref_to = Commit.truncate_sha(sha_to) if sha_to.start_with?(@ref_to)
|
||||
end
|
||||
end
|
||||
|
||||
def inspect
|
||||
@@ -71,15 +86,24 @@ class CommitRange
|
||||
end
|
||||
|
||||
def to_s
|
||||
"#{sha_from[0..7]}#{notation}#{sha_to[0..7]}"
|
||||
sha_from + notation + sha_to
|
||||
end
|
||||
|
||||
alias_method :id, :to_s
|
||||
|
||||
def to_reference(from_project = nil)
|
||||
# Not using to_s because we want the full SHAs
|
||||
reference = sha_from + notation + sha_to
|
||||
if cross_project_reference?(from_project)
|
||||
project.to_reference + self.class.reference_prefix + self.id
|
||||
else
|
||||
self.id
|
||||
end
|
||||
end
|
||||
|
||||
def reference_link_text(from_project = nil)
|
||||
reference = ref_from + notation + ref_to
|
||||
|
||||
if cross_project_reference?(from_project)
|
||||
reference = project.to_reference + '@' + reference
|
||||
reference = project.to_reference + self.class.reference_prefix + reference
|
||||
end
|
||||
|
||||
reference
|
||||
@@ -87,46 +111,58 @@ class CommitRange
|
||||
|
||||
# Returns a String for use in a link's title attribute
|
||||
def reference_title
|
||||
"Commits #{suffixed_sha_from} through #{sha_to}"
|
||||
"Commits #{sha_start} through #{sha_to}"
|
||||
end
|
||||
|
||||
# Return a Hash of parameters for passing to a URL helper
|
||||
#
|
||||
# See `namespace_project_compare_url`
|
||||
def to_param
|
||||
{ from: suffixed_sha_from, to: sha_to }
|
||||
{ from: sha_start, to: sha_to }
|
||||
end
|
||||
|
||||
def exclude_start?
|
||||
exclude_start
|
||||
@notation == '..'
|
||||
end
|
||||
|
||||
# Check if both the starting and ending commit IDs exist in a project's
|
||||
# repository
|
||||
#
|
||||
# project - An optional Project to check (default: `project`)
|
||||
def valid_commits?(project = project)
|
||||
return nil unless project.present?
|
||||
return false unless project.valid_repo?
|
||||
|
||||
commit_from.present? && commit_to.present?
|
||||
def valid_commits?
|
||||
commit_start.present? && commit_end.present?
|
||||
end
|
||||
|
||||
def persisted?
|
||||
true
|
||||
end
|
||||
|
||||
def commit_from
|
||||
@commit_from ||= project.repository.commit(suffixed_sha_from)
|
||||
def sha_from
|
||||
return nil unless @commit_from
|
||||
|
||||
@commit_from.id
|
||||
end
|
||||
|
||||
def commit_to
|
||||
@commit_to ||= project.repository.commit(sha_to)
|
||||
def sha_to
|
||||
return nil unless @commit_to
|
||||
|
||||
@commit_to.id
|
||||
end
|
||||
|
||||
private
|
||||
def sha_start
|
||||
return nil unless sha_from
|
||||
|
||||
def suffixed_sha_from
|
||||
sha_from + (exclude_start? ? '^' : '')
|
||||
exclude_start? ? sha_from + '^' : sha_from
|
||||
end
|
||||
|
||||
def commit_start
|
||||
return nil unless sha_start
|
||||
|
||||
if exclude_start?
|
||||
@commit_start ||= project.commit(sha_start)
|
||||
else
|
||||
commit_from
|
||||
end
|
||||
end
|
||||
|
||||
alias_method :sha_end, :sha_to
|
||||
alias_method :commit_end, :commit_to
|
||||
end
|
||||
|
||||
@@ -62,13 +62,18 @@ module Mentionable
|
||||
return [] if text.blank?
|
||||
|
||||
refs = all_references(current_user, text, load_lazy_references: load_lazy_references)
|
||||
(refs.issues + refs.merge_requests + refs.commits) - [local_reference]
|
||||
refs = (refs.issues + refs.merge_requests + refs.commits)
|
||||
|
||||
# We're using this method instead of Array diffing because that requires
|
||||
# both of the object's `hash` values to be the same, which may not be the
|
||||
# case for otherwise identical Commit objects.
|
||||
refs.reject { |ref| ref == local_reference }
|
||||
end
|
||||
|
||||
# Create a cross-reference Note for each GFM reference to another Mentionable found in +mentionable_text+.
|
||||
def create_cross_references!(author = self.author, without = [], text = self.mentionable_text)
|
||||
refs = referenced_mentionables(author, text)
|
||||
|
||||
|
||||
# We're using this method instead of Array diffing because that requires
|
||||
# both of the object's `hash` values to be the same, which may not be the
|
||||
# case for otherwise identical Commit objects.
|
||||
@@ -111,7 +116,7 @@ module Mentionable
|
||||
# Only include changed fields that are mentionable
|
||||
source.select { |key, val| mentionable.include?(key) }
|
||||
end
|
||||
|
||||
|
||||
# Determine whether or not a cross-reference Note has already been created between this Mentionable and
|
||||
# the specified target.
|
||||
def cross_reference_exists?(target)
|
||||
|
||||
@@ -21,6 +21,10 @@ module Referable
|
||||
''
|
||||
end
|
||||
|
||||
def reference_link_text(from_project = nil)
|
||||
to_reference(from_project)
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# The character that prefixes the actual reference identifier
|
||||
#
|
||||
@@ -44,6 +48,25 @@ module Referable
|
||||
def reference_pattern
|
||||
raise NotImplementedError, "#{self} does not implement #{__method__}"
|
||||
end
|
||||
|
||||
def link_reference_pattern(route, pattern)
|
||||
%r{
|
||||
(?<url>
|
||||
#{Regexp.escape(Gitlab.config.gitlab.url)}
|
||||
\/#{Project.reference_pattern}
|
||||
\/#{Regexp.escape(route)}
|
||||
\/#{pattern}
|
||||
(?<path>
|
||||
(\/[a-z0-9_=-]+)*
|
||||
)?
|
||||
(?<query>
|
||||
\?[a-z0-9_=-]+
|
||||
(&[a-z0-9_=-]+)*
|
||||
)?
|
||||
(?<anchor>\#[a-z0-9_-]+)?
|
||||
)
|
||||
}x
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -16,7 +16,15 @@ class GlobalMilestone
|
||||
end
|
||||
|
||||
def safe_title
|
||||
@title.parameterize
|
||||
@title.to_slug.to_s
|
||||
end
|
||||
|
||||
def expired?
|
||||
if due_date
|
||||
due_date.past?
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def projects
|
||||
@@ -98,4 +106,25 @@ class GlobalMilestone
|
||||
def complete?
|
||||
total_items_count == closed_items_count
|
||||
end
|
||||
|
||||
def due_date
|
||||
return @due_date if defined?(@due_date)
|
||||
|
||||
@due_date =
|
||||
if @milestones.all? { |x| x.due_date == @milestones.first.due_date }
|
||||
@milestones.first.due_date
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def expires_at
|
||||
if due_date
|
||||
if due_date.past?
|
||||
"expired at #{due_date.stamp("Aug 21, 2011")}"
|
||||
else
|
||||
"expires at #{due_date.stamp("Aug 21, 2011")}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -31,37 +31,38 @@ class WebHook < ActiveRecord::Base
|
||||
# HTTParty timeout
|
||||
default_timeout Gitlab.config.gitlab.webhook_timeout
|
||||
|
||||
validates :url, presence: true,
|
||||
format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" }
|
||||
validates :url, presence: true, url: true
|
||||
|
||||
def execute(data, hook_name)
|
||||
parsed_url = URI.parse(url)
|
||||
if parsed_url.userinfo.blank?
|
||||
WebHook.post(url,
|
||||
body: data.to_json,
|
||||
headers: {
|
||||
"Content-Type" => "application/json",
|
||||
"X-Gitlab-Event" => hook_name.singularize.titleize
|
||||
},
|
||||
verify: enable_ssl_verification)
|
||||
response = WebHook.post(url,
|
||||
body: data.to_json,
|
||||
headers: {
|
||||
"Content-Type" => "application/json",
|
||||
"X-Gitlab-Event" => hook_name.singularize.titleize
|
||||
},
|
||||
verify: enable_ssl_verification)
|
||||
else
|
||||
post_url = url.gsub("#{parsed_url.userinfo}@", "")
|
||||
auth = {
|
||||
username: URI.decode(parsed_url.user),
|
||||
password: URI.decode(parsed_url.password),
|
||||
}
|
||||
WebHook.post(post_url,
|
||||
body: data.to_json,
|
||||
headers: {
|
||||
"Content-Type" => "application/json",
|
||||
"X-Gitlab-Event" => hook_name.singularize.titleize
|
||||
},
|
||||
verify: enable_ssl_verification,
|
||||
basic_auth: auth)
|
||||
response = WebHook.post(post_url,
|
||||
body: data.to_json,
|
||||
headers: {
|
||||
"Content-Type" => "application/json",
|
||||
"X-Gitlab-Event" => hook_name.singularize.titleize
|
||||
},
|
||||
verify: enable_ssl_verification,
|
||||
basic_auth: auth)
|
||||
end
|
||||
rescue SocketError, Errno::ECONNRESET, Errno::ECONNREFUSED, Net::OpenTimeout => e
|
||||
|
||||
[response.code == 200, ActionView::Base.full_sanitizer.sanitize(response.to_s)]
|
||||
rescue SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Net::OpenTimeout => e
|
||||
logger.error("WebHook Error => #{e}")
|
||||
false
|
||||
[false, e.to_s]
|
||||
end
|
||||
|
||||
def async_execute(data, hook_name)
|
||||
|
||||
@@ -69,6 +69,10 @@ class Issue < ActiveRecord::Base
|
||||
}x
|
||||
end
|
||||
|
||||
def self.link_reference_pattern
|
||||
super("issues", /(?<issue>\d+)/)
|
||||
end
|
||||
|
||||
def to_reference(from_project = nil)
|
||||
reference = "#{self.class.reference_prefix}#{iid}"
|
||||
|
||||
|
||||
+1
-3
@@ -27,9 +27,7 @@ class Label < ActiveRecord::Base
|
||||
has_many :label_links, dependent: :destroy
|
||||
has_many :issues, through: :label_links, source: :target, source_type: 'Issue'
|
||||
|
||||
validates :color,
|
||||
format: { with: /\A#[0-9A-Fa-f]{6}\Z/ },
|
||||
allow_blank: false
|
||||
validates :color, color: true, allow_blank: false
|
||||
validates :project, presence: true, unless: Proc.new { |service| service.template? }
|
||||
|
||||
# Don't allow '?', '&', and ',' for label titles
|
||||
|
||||
@@ -151,6 +151,10 @@ class MergeRequest < ActiveRecord::Base
|
||||
}x
|
||||
end
|
||||
|
||||
def self.link_reference_pattern
|
||||
super("merge_requests", /(?<merge_request>\d+)/)
|
||||
end
|
||||
|
||||
def to_reference(from_project = nil)
|
||||
reference = "#{self.class.reference_prefix}#{iid}"
|
||||
|
||||
@@ -291,7 +295,7 @@ class MergeRequest < ActiveRecord::Base
|
||||
work_in_progress: work_in_progress?
|
||||
}
|
||||
|
||||
unless last_commit.nil?
|
||||
if last_commit
|
||||
attrs.merge!(last_commit: last_commit.hook_attrs)
|
||||
end
|
||||
|
||||
@@ -316,7 +320,7 @@ class MergeRequest < ActiveRecord::Base
|
||||
issues = commits.flat_map { |c| c.closes_issues(current_user) }
|
||||
issues.push(*Gitlab::ClosingIssueExtractor.new(project, current_user).
|
||||
closed_by_message(description))
|
||||
issues.uniq.sort_by(&:id)
|
||||
issues.uniq
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
@@ -23,19 +23,17 @@ class Namespace < ActiveRecord::Base
|
||||
|
||||
validates :owner, presence: true, unless: ->(n) { n.type == "Group" }
|
||||
validates :name,
|
||||
presence: true, uniqueness: true,
|
||||
length: { within: 0..255 },
|
||||
format: { with: Gitlab::Regex.namespace_name_regex,
|
||||
message: Gitlab::Regex.namespace_name_regex_message }
|
||||
namespace_name: true,
|
||||
presence: true,
|
||||
uniqueness: true
|
||||
|
||||
validates :description, length: { within: 0..255 }
|
||||
validates :path,
|
||||
uniqueness: { case_sensitive: false },
|
||||
presence: true,
|
||||
length: { within: 1..255 },
|
||||
exclusion: { in: Gitlab::Blacklist.path },
|
||||
format: { with: Gitlab::Regex.namespace_regex,
|
||||
message: Gitlab::Regex.namespace_regex_message }
|
||||
namespace: true,
|
||||
presence: true,
|
||||
uniqueness: { case_sensitive: false }
|
||||
|
||||
delegate :name, to: :owner, allow_nil: true, prefix: true
|
||||
|
||||
|
||||
+31
-1
@@ -39,9 +39,12 @@ class Note < ActiveRecord::Base
|
||||
delegate :name, to: :project, prefix: true
|
||||
delegate :name, :email, to: :author, prefix: true
|
||||
|
||||
before_validation :set_award!
|
||||
|
||||
validates :note, :project, presence: true
|
||||
validates :note, uniqueness: { scope: [:author, :noteable_type, :noteable_id] }, if: ->(n) { n.is_award }
|
||||
validates :line_code, format: { with: /\A[a-z0-9]+_\d+_\d+\Z/ }, allow_blank: true
|
||||
validates :note, inclusion: { in: Emoji.emojis_names }, if: ->(n) { n.is_award }
|
||||
validates :line_code, line_code: true, allow_blank: true
|
||||
# Attachments are deprecated and are handled by Markdown uploader
|
||||
validates :attachment, file_size: { maximum: :max_attachment_size }
|
||||
|
||||
@@ -348,4 +351,31 @@ class Note < ActiveRecord::Base
|
||||
def editable?
|
||||
!system?
|
||||
end
|
||||
|
||||
# Checks if note is an award added as a comment
|
||||
#
|
||||
# If note is an award, this method sets is_award to true
|
||||
# and changes content of the note to award name.
|
||||
#
|
||||
# Method is executed as a before_validation callback.
|
||||
#
|
||||
def set_award!
|
||||
return unless awards_supported? && contains_emoji_only?
|
||||
self.is_award = true
|
||||
self.note = award_emoji_name
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def awards_supported?
|
||||
noteable.kind_of?(Issue) || noteable.is_a?(MergeRequest)
|
||||
end
|
||||
|
||||
def contains_emoji_only?
|
||||
note =~ /\A#{Gitlab::Markdown::EmojiFilter.emoji_pattern}\s?\Z/
|
||||
end
|
||||
|
||||
def award_emoji_name
|
||||
note.match(Gitlab::Markdown::EmojiFilter.emoji_pattern)[1]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -152,7 +152,7 @@ class Project < ActiveRecord::Base
|
||||
validates_uniqueness_of :name, scope: :namespace_id
|
||||
validates_uniqueness_of :path, scope: :namespace_id
|
||||
validates :import_url,
|
||||
format: { with: /\A#{URI.regexp(%w(ssh git http https))}\z/, message: 'should be a valid url' },
|
||||
url: { protocols: %w(ssh git http https) },
|
||||
if: :external_import?
|
||||
validates :star_count, numericality: { greater_than_or_equal_to: 0 }
|
||||
validate :check_limit, on: :create
|
||||
|
||||
@@ -23,10 +23,7 @@ class BambooService < CiService
|
||||
|
||||
prop_accessor :bamboo_url, :build_key, :username, :password
|
||||
|
||||
validates :bamboo_url,
|
||||
presence: true,
|
||||
format: { with: /\A#{URI.regexp}\z/ },
|
||||
if: :activated?
|
||||
validates :bamboo_url, presence: true, url: true, if: :activated?
|
||||
validates :build_key, presence: true, if: :activated?
|
||||
validates :username,
|
||||
presence: true,
|
||||
@@ -84,7 +81,7 @@ class BambooService < CiService
|
||||
def supported_events
|
||||
%w(push)
|
||||
end
|
||||
|
||||
|
||||
def build_info(sha)
|
||||
url = URI.parse("#{bamboo_url}/rest/api/latest/result?label=#{sha}")
|
||||
|
||||
|
||||
@@ -19,14 +19,11 @@
|
||||
#
|
||||
|
||||
class DroneCiService < CiService
|
||||
|
||||
|
||||
prop_accessor :drone_url, :token, :enable_ssl_verification
|
||||
validates :drone_url,
|
||||
presence: true,
|
||||
format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" }, if: :activated?
|
||||
validates :token,
|
||||
presence: true,
|
||||
if: :activated?
|
||||
|
||||
validates :drone_url, presence: true, url: true, if: :activated?
|
||||
validates :token, presence: true, if: :activated?
|
||||
|
||||
after_save :compose_service_hook, if: :activated?
|
||||
|
||||
@@ -58,16 +55,16 @@ class DroneCiService < CiService
|
||||
end
|
||||
|
||||
def merge_request_status_path(iid, sha = nil, ref = nil)
|
||||
url = [drone_url,
|
||||
"gitlab/#{project.namespace.path}/#{project.path}/pulls/#{iid}",
|
||||
url = [drone_url,
|
||||
"gitlab/#{project.namespace.path}/#{project.path}/pulls/#{iid}",
|
||||
"?access_token=#{token}"]
|
||||
|
||||
URI.join(*url).to_s
|
||||
end
|
||||
|
||||
def commit_status_path(sha, ref)
|
||||
url = [drone_url,
|
||||
"gitlab/#{project.namespace.path}/#{project.path}/commits/#{sha}",
|
||||
url = [drone_url,
|
||||
"gitlab/#{project.namespace.path}/#{project.path}/commits/#{sha}",
|
||||
"?branch=#{URI::encode(ref.to_s)}&access_token=#{token}"]
|
||||
|
||||
URI.join(*url).to_s
|
||||
@@ -114,15 +111,15 @@ class DroneCiService < CiService
|
||||
end
|
||||
|
||||
def merge_request_page(iid, sha, ref)
|
||||
url = [drone_url,
|
||||
url = [drone_url,
|
||||
"gitlab/#{project.namespace.path}/#{project.path}/redirect/pulls/#{iid}"]
|
||||
|
||||
URI.join(*url).to_s
|
||||
end
|
||||
|
||||
def commit_page(sha, ref)
|
||||
url = [drone_url,
|
||||
"gitlab/#{project.namespace.path}/#{project.path}/redirect/commits/#{sha}",
|
||||
url = [drone_url,
|
||||
"gitlab/#{project.namespace.path}/#{project.path}/redirect/commits/#{sha}",
|
||||
"?branch=#{URI::encode(ref.to_s)}"]
|
||||
|
||||
URI.join(*url).to_s
|
||||
@@ -163,10 +160,10 @@ class DroneCiService < CiService
|
||||
end
|
||||
|
||||
def push_valid?(data)
|
||||
opened_merge_requests = project.merge_requests.opened.where(source_project_id: project.id,
|
||||
opened_merge_requests = project.merge_requests.opened.where(source_project_id: project.id,
|
||||
source_branch: Gitlab::Git.ref_name(data[:ref]))
|
||||
|
||||
opened_merge_requests.empty? && data[:total_commits_count] > 0 &&
|
||||
opened_merge_requests.empty? && data[:total_commits_count] > 0 &&
|
||||
!Gitlab::Git.blank_ref?(data[:after])
|
||||
end
|
||||
|
||||
|
||||
@@ -22,10 +22,8 @@ class ExternalWikiService < Service
|
||||
include HTTParty
|
||||
|
||||
prop_accessor :external_wiki_url
|
||||
validates :external_wiki_url,
|
||||
presence: true,
|
||||
format: { with: /\A#{URI.regexp}\z/ },
|
||||
if: :activated?
|
||||
|
||||
validates :external_wiki_url, presence: true, url: true, if: :activated?
|
||||
|
||||
def title
|
||||
'External Wiki'
|
||||
|
||||
@@ -23,16 +23,16 @@ class TeamcityService < CiService
|
||||
|
||||
prop_accessor :teamcity_url, :build_type, :username, :password
|
||||
|
||||
validates :teamcity_url,
|
||||
presence: true,
|
||||
format: { with: /\A#{URI.regexp}\z/ }, if: :activated?
|
||||
validates :teamcity_url, presence: true, url: true, if: :activated?
|
||||
validates :build_type, presence: true, if: :activated?
|
||||
validates :username,
|
||||
presence: true,
|
||||
if: ->(service) { service.password? }, if: :activated?
|
||||
if: ->(service) { service.password? },
|
||||
if: :activated?
|
||||
validates :password,
|
||||
presence: true,
|
||||
if: ->(service) { service.username? }, if: :activated?
|
||||
if: ->(service) { service.username? },
|
||||
if: :activated?
|
||||
|
||||
attr_accessor :response
|
||||
|
||||
|
||||
+31
-27
@@ -1,7 +1,6 @@
|
||||
require 'securerandom'
|
||||
|
||||
class Repository
|
||||
class PreReceiveError < StandardError; end
|
||||
class CommitError < StandardError; end
|
||||
|
||||
include Gitlab::ShellAdapter
|
||||
@@ -101,17 +100,26 @@ class Repository
|
||||
end
|
||||
|
||||
def find_branch(name)
|
||||
branches.find { |branch| branch.name == name }
|
||||
raw_repository.branches.find { |branch| branch.name == name }
|
||||
end
|
||||
|
||||
def find_tag(name)
|
||||
tags.find { |tag| tag.name == name }
|
||||
raw_repository.tags.find { |tag| tag.name == name }
|
||||
end
|
||||
|
||||
def add_branch(branch_name, ref)
|
||||
expire_branches_cache
|
||||
def add_branch(user, branch_name, target)
|
||||
oldrev = Gitlab::Git::BLANK_SHA
|
||||
ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
|
||||
target = commit(target).try(:id)
|
||||
|
||||
gitlab_shell.add_branch(path_with_namespace, branch_name, ref)
|
||||
return false unless target
|
||||
|
||||
GitHooksService.new.execute(user, path_to_repo, oldrev, target, ref) do
|
||||
rugged.branches.create(branch_name, target)
|
||||
end
|
||||
|
||||
expire_branches_cache
|
||||
find_branch(branch_name)
|
||||
end
|
||||
|
||||
def add_tag(tag_name, ref, message = nil)
|
||||
@@ -120,10 +128,20 @@ class Repository
|
||||
gitlab_shell.add_tag(path_with_namespace, tag_name, ref, message)
|
||||
end
|
||||
|
||||
def rm_branch(branch_name)
|
||||
def rm_branch(user, branch_name)
|
||||
expire_branches_cache
|
||||
|
||||
gitlab_shell.rm_branch(path_with_namespace, branch_name)
|
||||
branch = find_branch(branch_name)
|
||||
oldrev = branch.try(:target)
|
||||
newrev = Gitlab::Git::BLANK_SHA
|
||||
ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
|
||||
|
||||
GitHooksService.new.execute(user, path_to_repo, oldrev, newrev, ref) do
|
||||
rugged.branches.delete(branch_name)
|
||||
end
|
||||
|
||||
expire_branches_cache
|
||||
true
|
||||
end
|
||||
|
||||
def rm_tag(tag_name)
|
||||
@@ -550,7 +568,6 @@ class Repository
|
||||
def commit_with_hooks(current_user, branch)
|
||||
oldrev = Gitlab::Git::BLANK_SHA
|
||||
ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
|
||||
gl_id = Gitlab::ShellEnv.gl_id(current_user)
|
||||
was_empty = empty?
|
||||
|
||||
# Create temporary ref
|
||||
@@ -569,15 +586,7 @@ class Repository
|
||||
raise CommitError.new('Failed to create commit')
|
||||
end
|
||||
|
||||
# Run GitLab pre-receive hook
|
||||
pre_receive_hook = Gitlab::Git::Hook.new('pre-receive', path_to_repo)
|
||||
pre_receive_hook_status = pre_receive_hook.trigger(gl_id, oldrev, newrev, ref)
|
||||
|
||||
# Run GitLab update hook
|
||||
update_hook = Gitlab::Git::Hook.new('update', path_to_repo)
|
||||
update_hook_status = update_hook.trigger(gl_id, oldrev, newrev, ref)
|
||||
|
||||
if pre_receive_hook_status && update_hook_status
|
||||
GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do
|
||||
if was_empty
|
||||
# Create branch
|
||||
rugged.references.create(ref, newrev)
|
||||
@@ -592,16 +601,11 @@ class Repository
|
||||
raise CommitError.new('Commit was rejected because branch received new push')
|
||||
end
|
||||
end
|
||||
|
||||
# Run GitLab post receive hook
|
||||
post_receive_hook = Gitlab::Git::Hook.new('post-receive', path_to_repo)
|
||||
post_receive_hook.trigger(gl_id, oldrev, newrev, ref)
|
||||
else
|
||||
# Remove tmp ref and return error to user
|
||||
rugged.references.delete(tmp_ref)
|
||||
|
||||
raise PreReceiveError.new('Commit was rejected by git hook')
|
||||
end
|
||||
rescue GitHooksService::PreReceiveError
|
||||
# Remove tmp ref and return error to user
|
||||
rugged.references.delete(tmp_ref)
|
||||
raise
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -21,7 +21,7 @@ class SentNotification < ActiveRecord::Base
|
||||
validates :reply_key, uniqueness: true
|
||||
validates :noteable_id, presence: true, unless: :for_commit?
|
||||
validates :commit_id, presence: true, if: :for_commit?
|
||||
validates :line_code, format: { with: /\A[a-z0-9]+_\d+_\d+\Z/ }, allow_blank: true
|
||||
validates :line_code, line_code: true, allow_blank: true
|
||||
|
||||
class << self
|
||||
def reply_key
|
||||
|
||||
@@ -65,6 +65,10 @@ class Snippet < ActiveRecord::Base
|
||||
}x
|
||||
end
|
||||
|
||||
def self.link_reference_pattern
|
||||
super("snippets", /(?<snippet>\d+)/)
|
||||
end
|
||||
|
||||
def to_reference(from_project = nil)
|
||||
reference = "#{self.class.reference_prefix}#{id}"
|
||||
|
||||
|
||||
+2
-4
@@ -148,11 +148,9 @@ class User < ActiveRecord::Base
|
||||
validates :bio, length: { maximum: 255 }, allow_blank: true
|
||||
validates :projects_limit, presence: true, numericality: { greater_than_or_equal_to: 0 }
|
||||
validates :username,
|
||||
namespace: true,
|
||||
presence: true,
|
||||
uniqueness: { case_sensitive: false },
|
||||
exclusion: { in: Gitlab::Blacklist.path },
|
||||
format: { with: Gitlab::Regex.namespace_regex,
|
||||
message: Gitlab::Regex.namespace_regex_message }
|
||||
uniqueness: { case_sensitive: false }
|
||||
|
||||
validates :notification_level, inclusion: { in: Notification.notification_levels }, presence: true
|
||||
validate :namespace_uniq, if: ->(user) { user.username_changed? }
|
||||
|
||||
@@ -13,8 +13,7 @@ class CreateBranchService < BaseService
|
||||
return error('Branch already exists')
|
||||
end
|
||||
|
||||
repository.add_branch(branch_name, ref)
|
||||
new_branch = repository.find_branch(branch_name)
|
||||
new_branch = repository.add_branch(current_user, branch_name, ref)
|
||||
|
||||
if new_branch
|
||||
push_data = build_push_data(project, current_user, new_branch)
|
||||
@@ -27,6 +26,8 @@ class CreateBranchService < BaseService
|
||||
else
|
||||
error('Invalid reference name')
|
||||
end
|
||||
rescue GitHooksService::PreReceiveError
|
||||
error('Branch creation was rejected by Git hook')
|
||||
end
|
||||
|
||||
def success(branch)
|
||||
|
||||
@@ -24,7 +24,7 @@ class DeleteBranchService < BaseService
|
||||
return error('You dont have push access to repo', 405)
|
||||
end
|
||||
|
||||
if repository.rm_branch(branch_name)
|
||||
if repository.rm_branch(current_user, branch_name)
|
||||
push_data = build_push_data(branch)
|
||||
|
||||
EventCreateService.new.push(project, current_user, push_data)
|
||||
@@ -35,6 +35,8 @@ class DeleteBranchService < BaseService
|
||||
else
|
||||
error('Failed to remove branch')
|
||||
end
|
||||
rescue GitHooksService::PreReceiveError
|
||||
error('Branch deletion was rejected by Git hook')
|
||||
end
|
||||
|
||||
def error(message, return_code = 400)
|
||||
|
||||
@@ -26,7 +26,7 @@ module Files
|
||||
else
|
||||
error("Something went wrong. Your changes were not committed")
|
||||
end
|
||||
rescue Repository::CommitError, Repository::PreReceiveError, ValidationError => ex
|
||||
rescue Repository::CommitError, GitHooksService::PreReceiveError, ValidationError => ex
|
||||
error(ex.message)
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
class GitHooksService
|
||||
PreReceiveError = Class.new(StandardError)
|
||||
|
||||
def execute(user, repo_path, oldrev, newrev, ref)
|
||||
@repo_path = repo_path
|
||||
@user = Gitlab::ShellEnv.gl_id(user)
|
||||
@oldrev = oldrev
|
||||
@newrev = newrev
|
||||
@ref = ref
|
||||
|
||||
%w(pre-receive update).each do |hook_name|
|
||||
unless run_hook(hook_name)
|
||||
raise PreReceiveError.new("Git operation was rejected by #{hook_name} hook")
|
||||
end
|
||||
end
|
||||
|
||||
yield
|
||||
|
||||
run_hook('post-receive')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def run_hook(name)
|
||||
hook = Gitlab::Git::Hook.new(name, @repo_path)
|
||||
hook.trigger(@user, @oldrev, @newrev, @ref)
|
||||
end
|
||||
end
|
||||
@@ -5,11 +5,6 @@ module Notes
|
||||
note.author = current_user
|
||||
note.system = false
|
||||
|
||||
if contains_emoji_only?(params[:note])
|
||||
note.is_award = true
|
||||
note.note = emoji_name(params[:note])
|
||||
end
|
||||
|
||||
if note.save
|
||||
notification_service.new_note(note)
|
||||
|
||||
@@ -33,13 +28,5 @@ module Notes
|
||||
note.project.execute_hooks(note_data, :note_hooks)
|
||||
note.project.execute_services(note_data, :note_hooks)
|
||||
end
|
||||
|
||||
def contains_emoji_only?(note)
|
||||
note =~ /\A:[-_+[:alnum:]]*:\s?\z/
|
||||
end
|
||||
|
||||
def emoji_name(note)
|
||||
note.match(/\A:([-_+[:alnum:]]*):\s?/)[1]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -145,6 +145,7 @@ class NotificationService
|
||||
recipients = reject_unsubscribed_users(recipients, note.noteable)
|
||||
|
||||
recipients.delete(note.author)
|
||||
recipients = recipients.uniq
|
||||
|
||||
# build notify method like 'note_commit_email'
|
||||
notify_method = "note_#{note.noteable_type.underscore}_email".to_sym
|
||||
|
||||
@@ -125,7 +125,7 @@ class SystemNoteService
|
||||
# Returns the created Note object
|
||||
def self.change_status(noteable, project, author, status, source)
|
||||
body = "Status changed to #{status}"
|
||||
body += " by #{source.gfm_reference}" if source
|
||||
body += " by #{source.gfm_reference(project)}" if source
|
||||
|
||||
create_note(noteable: noteable, project: project, author: author, note: body)
|
||||
end
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# ColorValidator
|
||||
#
|
||||
# Custom validator for web color codes. It requires the leading hash symbol and
|
||||
# will accept RGB triplet or hexadecimal formats.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# class User < ActiveRecord::Base
|
||||
# validates :background_color, allow_blank: true, color: true
|
||||
# end
|
||||
#
|
||||
class ColorValidator < ActiveModel::EachValidator
|
||||
PATTERN = /\A\#[0-9A-Fa-f]{3}{1,2}+\Z/.freeze
|
||||
|
||||
def validate_each(record, attribute, value)
|
||||
unless value =~ PATTERN
|
||||
record.errors.add(attribute, "must be a valid color code")
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,3 +1,5 @@
|
||||
# EmailValidator
|
||||
#
|
||||
# Based on https://github.com/balexand/email_validator
|
||||
#
|
||||
# Extended to use only strict mode with following allowed characters:
|
||||
@@ -6,15 +8,10 @@
|
||||
# See http://www.remote.org/jochen/mail/info/chars.html
|
||||
#
|
||||
class EmailValidator < ActiveModel::EachValidator
|
||||
@@default_options = {}
|
||||
|
||||
def self.default_options
|
||||
@@default_options
|
||||
end
|
||||
PATTERN = /\A\s*([-a-z0-9+._']{1,64})@((?:[-a-z0-9]+\.)+[a-z]{2,})\s*\z/i.freeze
|
||||
|
||||
def validate_each(record, attribute, value)
|
||||
options = @@default_options.merge(self.options)
|
||||
unless value =~ /\A\s*([-a-z0-9+._']{1,64})@((?:[-a-z0-9]+\.)+[a-z]{2,})\s*\z/i
|
||||
unless value =~ PATTERN
|
||||
record.errors.add(attribute, options[:message] || :invalid)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,12 @@
|
||||
# LineCodeValidator
|
||||
#
|
||||
# Custom validator for GitLab line codes.
|
||||
class LineCodeValidator < ActiveModel::EachValidator
|
||||
PATTERN = /\A[a-z0-9]+_\d+_\d+\z/.freeze
|
||||
|
||||
def validate_each(record, attribute, value)
|
||||
unless value =~ PATTERN
|
||||
record.errors.add(attribute, "must be a valid line code")
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,10 @@
|
||||
# NamespaceNameValidator
|
||||
#
|
||||
# Custom validator for GitLab namespace name strings.
|
||||
class NamespaceNameValidator < ActiveModel::EachValidator
|
||||
def validate_each(record, attribute, value)
|
||||
unless value =~ Gitlab::Regex.namespace_name_regex
|
||||
record.errors.add(attribute, Gitlab::Regex.namespace_name_regex_message)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,50 @@
|
||||
# NamespaceValidator
|
||||
#
|
||||
# Custom validator for GitLab namespace values.
|
||||
#
|
||||
# Values are checked for formatting and exclusion from a list of reserved path
|
||||
# names.
|
||||
class NamespaceValidator < ActiveModel::EachValidator
|
||||
RESERVED = %w(
|
||||
admin
|
||||
all
|
||||
assets
|
||||
ci
|
||||
dashboard
|
||||
files
|
||||
groups
|
||||
help
|
||||
hooks
|
||||
issues
|
||||
merge_requests
|
||||
notes
|
||||
profile
|
||||
projects
|
||||
public
|
||||
repository
|
||||
s
|
||||
search
|
||||
services
|
||||
snippets
|
||||
teams
|
||||
u
|
||||
unsubscribes
|
||||
users
|
||||
).freeze
|
||||
|
||||
def validate_each(record, attribute, value)
|
||||
unless value =~ Gitlab::Regex.namespace_regex
|
||||
record.errors.add(attribute, Gitlab::Regex.namespace_regex_message)
|
||||
end
|
||||
|
||||
if reserved?(value)
|
||||
record.errors.add(attribute, "#{value} is a reserved name")
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def reserved?(value)
|
||||
RESERVED.include?(value)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,36 @@
|
||||
# UrlValidator
|
||||
#
|
||||
# Custom validator for URLs.
|
||||
#
|
||||
# By default, only URLs for the HTTP(S) protocols will be considered valid.
|
||||
# Provide a `:protocols` option to configure accepted protocols.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# class User < ActiveRecord::Base
|
||||
# validates :personal_url, url: true
|
||||
#
|
||||
# validates :ftp_url, url: { protocols: %w(ftp) }
|
||||
#
|
||||
# validates :git_url, url: { protocols: %w(http https ssh git) }
|
||||
# end
|
||||
#
|
||||
class UrlValidator < ActiveModel::EachValidator
|
||||
def validate_each(record, attribute, value)
|
||||
unless valid_url?(value)
|
||||
record.errors.add(attribute, "must be a valid URL")
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def default_options
|
||||
@default_options ||= { protocols: %w(http https) }
|
||||
end
|
||||
|
||||
def valid_url?(value)
|
||||
options = default_options.merge(self.options)
|
||||
|
||||
value =~ /\A#{URI.regexp(options[:protocols])}\z/
|
||||
end
|
||||
end
|
||||
@@ -1,3 +1,6 @@
|
||||
= content_for :flash_message do
|
||||
= render 'shared/project_limit'
|
||||
|
||||
%ul.center-top-menu
|
||||
= nav_link(path: ['projects#index', 'root#index']) do
|
||||
= link_to dashboard_projects_path, title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do
|
||||
|
||||
@@ -16,7 +16,10 @@
|
||||
= milestone_progress_bar(milestone)
|
||||
.row
|
||||
.col-sm-6
|
||||
- milestone.milestones.each do |milestone|
|
||||
= link_to milestone_path(milestone) do
|
||||
%span.label.label-gray
|
||||
= milestone.project.name_with_namespace
|
||||
.expiration
|
||||
= render 'shared/milestone_expired', milestone: milestone
|
||||
.projects
|
||||
- milestone.milestones.each do |milestone|
|
||||
= link_to milestone_path(milestone) do
|
||||
%span.label.label-gray
|
||||
= milestone.project.name_with_namespace
|
||||
|
||||
@@ -8,17 +8,18 @@
|
||||
%a.js-md-preview-button(href="#md-preview-holder" tabindex="-1")
|
||||
Preview
|
||||
|
||||
- if defined?(referenced_users) && referenced_users
|
||||
%span.referenced-users.pull-left.hide
|
||||
%div
|
||||
.md-write-holder
|
||||
= yield
|
||||
.md.md-preview-holder.hide
|
||||
.js-md-preview{class: (preview_class if defined?(preview_class))}
|
||||
|
||||
- if defined?(referenced_users) && referenced_users
|
||||
%div.referenced-users.hide
|
||||
%span
|
||||
= icon('exclamation-triangle')
|
||||
You are about to add
|
||||
%strong
|
||||
%span.js-referenced-users-count 0
|
||||
people
|
||||
to the discussion. Proceed with caution.
|
||||
|
||||
%div
|
||||
.md-write-holder
|
||||
= yield
|
||||
.md.md-preview-holder.hide
|
||||
.js-md-preview{class: (preview_class if defined?(preview_class))}
|
||||
|
||||
@@ -3,9 +3,8 @@
|
||||
- if diff_file.diff.submodule?
|
||||
%span
|
||||
= icon('archive fw')
|
||||
- submodule_item = project.repository.blob_at(@commit.id, diff_file.file_path)
|
||||
%strong
|
||||
= submodule_link(submodule_item, @commit.id, project.repository)
|
||||
= submodule_link(blob, @commit.id, project.repository)
|
||||
- else
|
||||
%span
|
||||
= blob_icon blob.mode, blob.name
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
.issue-closed-by-widget
|
||||
= icon('check')
|
||||
This issue will be closed automatically when merge request #{gfm(merge_requests_sentence(@closed_by_merge_requests.sort))} is accepted.
|
||||
This issue will be closed automatically when merge request #{gfm(merge_requests_sentence(@closed_by_merge_requests))} is accepted.
|
||||
|
||||
@@ -18,11 +18,7 @@
|
||||
|
||||
.row
|
||||
.col-sm-6
|
||||
- if milestone.expired? and not milestone.closed?
|
||||
%span.cred (Expired)
|
||||
- if milestone.expires_at
|
||||
%span
|
||||
= milestone.expires_at
|
||||
= render 'shared/milestone_expired', milestone: milestone
|
||||
.col-sm-6
|
||||
- if can?(current_user, :admin_milestone, milestone.project) and milestone.active?
|
||||
= link_to edit_namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), class: "btn btn-xs edit-milestone-link btn-grouped" do
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
- if milestone.expired? and not milestone.closed?
|
||||
%span.cred (Expired)
|
||||
- if milestone.expires_at
|
||||
%span
|
||||
= milestone.expires_at
|
||||
@@ -0,0 +1,8 @@
|
||||
- if cookies[:hide_project_limit_message].blank? && !current_user.hide_project_limit && !current_user.can_create_project?
|
||||
.project-limit-message.alert.alert-warning.hidden-xs
|
||||
You won't be able to create new projects because you have reached your project limit.
|
||||
|
||||
.pull-right
|
||||
= link_to "Don't show again", profile_path(user: {hide_project_limit: true}), method: :put, class: 'alert-link'
|
||||
|
|
||||
= link_to 'Remind later', '#', class: 'hide-project-limit-message alert-link'
|
||||
@@ -1,11 +1,8 @@
|
||||
class StuckCiBuildsWorker
|
||||
include Sidekiq::Worker
|
||||
include Sidetiq::Schedulable
|
||||
|
||||
BUILD_STUCK_TIMEOUT = 1.day
|
||||
|
||||
recurrence { daily }
|
||||
|
||||
def perform
|
||||
Rails.logger.info 'Cleaning stuck builds'
|
||||
|
||||
|
||||
@@ -164,7 +164,7 @@ Settings.gitlab['signin_enabled'] ||= true if Settings.gitlab['signin_enabled'].
|
||||
Settings.gitlab['twitter_sharing_enabled'] ||= true if Settings.gitlab['twitter_sharing_enabled'].nil?
|
||||
Settings.gitlab['restricted_visibility_levels'] = Settings.send(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], [])
|
||||
Settings.gitlab['username_changing_enabled'] = true if Settings.gitlab['username_changing_enabled'].nil?
|
||||
Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing)) +(?:(?:issues? +)?#\d+(?:(?:, *| +and +)?))+)' if Settings.gitlab['issue_closing_pattern'].nil?
|
||||
Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing)) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?))+)' if Settings.gitlab['issue_closing_pattern'].nil?
|
||||
Settings.gitlab['default_projects_features'] ||= {}
|
||||
Settings.gitlab['webhook_timeout'] ||= 10
|
||||
Settings.gitlab['max_attachment_size'] ||= 10
|
||||
|
||||
@@ -17,6 +17,12 @@ Sidekiq.configure_server do |config|
|
||||
chain.add Gitlab::SidekiqMiddleware::ArgumentsLogger if ENV['SIDEKIQ_LOG_ARGUMENTS']
|
||||
chain.add Gitlab::SidekiqMiddleware::MemoryKiller if ENV['SIDEKIQ_MEMORY_KILLER_MAX_RSS']
|
||||
end
|
||||
|
||||
# Sidekiq-cron: load recurring jobs from schedule.yml
|
||||
schedule_file = 'config/schedule.yml'
|
||||
if File.exists?(schedule_file)
|
||||
Sidekiq::Cron::Job.load_from_hash YAML.load_file(schedule_file)
|
||||
end
|
||||
end
|
||||
|
||||
Sidekiq.configure_client do |config|
|
||||
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
require 'sidekiq/web'
|
||||
require 'sidekiq/cron/web'
|
||||
require 'api/api'
|
||||
|
||||
Rails.application.routes.draw do
|
||||
@@ -368,7 +369,7 @@ Rails.application.routes.draw do
|
||||
end
|
||||
|
||||
resource :avatar, only: [:destroy]
|
||||
resources :milestones, only: [:index, :show, :update, :new, :create]
|
||||
resources :milestones, constraints: { id: /[^\/]+/ }, only: [:index, :show, :update, :new, :create]
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# Here is a list of jobs that are scheduled to run periodically.
|
||||
# We use a UNIX cron notation to specify execution schedule.
|
||||
#
|
||||
# Please read here for more information:
|
||||
# https://github.com/ondrejbartas/sidekiq-cron#adding-cron-job
|
||||
|
||||
stuck_ci_builds_worker:
|
||||
cron: "0 0 * * *"
|
||||
class: "StuckCiBuildsWorker"
|
||||
queue: "default"
|
||||
@@ -0,0 +1,5 @@
|
||||
class AddHideProjectLimitToUsers < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :users, :hide_project_limit, :boolean, default: false
|
||||
end
|
||||
end
|
||||
+2
-1
@@ -11,7 +11,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 20151118162244) do
|
||||
ActiveRecord::Schema.define(version: 20151203162133) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
@@ -814,6 +814,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do
|
||||
t.integer "project_view", default: 0
|
||||
t.integer "consumed_timestep"
|
||||
t.integer "layout", default: 0
|
||||
t.boolean "hide_project_limit", default: false
|
||||
end
|
||||
|
||||
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
|
||||
|
||||
@@ -190,7 +190,7 @@ This will create two service containers (MySQL and PostgreSQL).
|
||||
|
||||
1. Create a build container and execute script in its context:
|
||||
```
|
||||
$ cat build_script | docker run -n build -i -l mysql:service-mysql -l postgres:service-postgres ruby:2.1 /bin/bash
|
||||
$ docker run --name build -i --link=service-mysql:mysql --link=service-postgres:postgres ruby:2.1 /bin/bash < build_script
|
||||
```
|
||||
This will create build container that has two service containers linked.
|
||||
The build_script is piped using STDIN to bash interpreter which executes the build script in container.
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
GitLab has the following updates:
|
||||
## Release cycle
|
||||
|
||||
Since 2011 a minor or major version of GitLab is released on the 22nd of every month. Patch and security releases are published when needed. New features are detailed on the [blog](https://about.gitlab.com/blog/) and in the [changelog](CHANGELOG). Features that will likely be in the next releases can be found on the [direction page](https://about.gitlab.com/direction/).
|
||||
|
||||
## Release process documentation
|
||||
|
||||
- [Monthly release](monthly.md), every month on the 22nd.
|
||||
- [Patch release](patch.md), if there are serious regressions.
|
||||
|
||||
@@ -57,6 +57,9 @@ X-Gitlab-Event: Push Hook
|
||||
"name": "Jordi Mallach",
|
||||
"email": "jordi@softcatala.org"
|
||||
}
|
||||
"added": ["CHANGELOG"],
|
||||
"modified": ["app/controller/application.rb"],
|
||||
"removed": []
|
||||
},
|
||||
{
|
||||
"id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
|
||||
@@ -66,13 +69,14 @@ X-Gitlab-Event: Push Hook
|
||||
"author": {
|
||||
"name": "GitLab dev user",
|
||||
"email": "gitlabdev@dv6700.(none)"
|
||||
}
|
||||
},
|
||||
"added": ["CHANGELOG"],
|
||||
"modified": ["app/controller/application.rb"],
|
||||
"removed": []
|
||||
}
|
||||
],
|
||||
"total_commits_count": 4,
|
||||
"added": ["CHANGELOG"],
|
||||
"modified": ["app/controller/application.rb"],
|
||||
"removed": []
|
||||
"total_commits_count": 4
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -13,6 +13,12 @@ Feature: Project Commits Diff Comments
|
||||
Given I leave a diff comment like "Typo, please fix"
|
||||
Then I should see a diff comment saying "Typo, please fix"
|
||||
|
||||
@javascript
|
||||
Scenario: I can add a diff comment with a single emoji
|
||||
Given I open a diff comment form
|
||||
And I write a diff comment like ":smile:"
|
||||
Then I should see a diff comment with an emoji image
|
||||
|
||||
@javascript
|
||||
Scenario: I get a temporary form for the first comment on a diff line
|
||||
Given I open a diff comment form
|
||||
|
||||
@@ -11,4 +11,8 @@ Feature: Award Emoji
|
||||
And I click to emoji in the picker
|
||||
Then I have award added
|
||||
And I can remove it by clicking to icon
|
||||
|
||||
|
||||
@javascript
|
||||
Scenario: I add award emoji using regular comment
|
||||
Given I leave comment with a single emoji
|
||||
Then I have award added
|
||||
|
||||
@@ -8,10 +8,12 @@ Feature: Project Merge Requests Acceptance
|
||||
Given I am on the Merge Request detail page
|
||||
When I click on "Remove source branch" option
|
||||
And I click on Accept Merge Request
|
||||
Then I should not see the Remove Source Branch button
|
||||
Then I should see merge request merged
|
||||
And I should not see the Remove Source Branch button
|
||||
|
||||
@javascript
|
||||
Scenario: Accepting the Merge Request without removing the source branch
|
||||
Given I am on the Merge Request detail page
|
||||
When I click on Accept Merge Request
|
||||
Then I should see the Remove Source Branch button
|
||||
Then I should see merge request merged
|
||||
And I should see the Remove Source Branch button
|
||||
|
||||
@@ -71,7 +71,7 @@ class Spinach::Features::AdminIssuesLabels < Spinach::FeatureSteps
|
||||
|
||||
step 'I should see label color error message' do
|
||||
page.within '.label-form' do
|
||||
expect(page).to have_content 'Color is invalid'
|
||||
expect(page).to have_content 'Color must be a valid color code'
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -70,8 +70,6 @@ class Spinach::Features::ProjectHooks < Spinach::FeatureSteps
|
||||
|
||||
step 'I should see hook service down error message' do
|
||||
expect(page).to have_selector '.flash-alert',
|
||||
text: 'Hook execution failed. '\
|
||||
'Ensure hook URL is correct and '\
|
||||
'service is up.'
|
||||
text: 'Hook execution failed: Exception from'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -9,33 +9,40 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps
|
||||
end
|
||||
|
||||
step 'I click to emoji-picker' do
|
||||
page.within ".awards-controls" do
|
||||
page.find(".add-award").click
|
||||
page.within '.awards-controls' do
|
||||
page.find('.add-award').click
|
||||
end
|
||||
end
|
||||
|
||||
step 'I click to emoji in the picker' do
|
||||
page.within ".awards-menu" do
|
||||
page.first("img").click
|
||||
page.within '.awards-menu' do
|
||||
page.first('img').click
|
||||
end
|
||||
end
|
||||
|
||||
step 'I can remove it by clicking to icon' do
|
||||
page.within ".awards" do
|
||||
page.first(".award").click
|
||||
expect(page).to_not have_selector ".award"
|
||||
page.within '.awards' do
|
||||
page.first('.award').click
|
||||
expect(page).to_not have_selector '.award'
|
||||
end
|
||||
end
|
||||
|
||||
step 'I have award added' do
|
||||
page.within ".awards" do
|
||||
expect(page).to have_selector ".award"
|
||||
expect(page.find(".award .counter")).to have_content "1"
|
||||
page.within '.awards' do
|
||||
expect(page).to have_selector '.award'
|
||||
expect(page.find('.award .counter')).to have_content '1'
|
||||
end
|
||||
end
|
||||
|
||||
step 'project "Shop" has issue "Bugfix"' do
|
||||
@project = Project.find_by(name: "Shop")
|
||||
@issue = create(:issue, title: "Bugfix", project: project)
|
||||
@project = Project.find_by(name: 'Shop')
|
||||
@issue = create(:issue, title: 'Bugfix', project: project)
|
||||
end
|
||||
|
||||
step 'I leave comment with a single emoji' do
|
||||
page.within('.js-main-target-form') do
|
||||
fill_in 'note[note]', with: ':smile:'
|
||||
click_button 'Add Comment'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -55,7 +55,7 @@ class Spinach::Features::ProjectIssuesLabels < Spinach::FeatureSteps
|
||||
|
||||
step 'I should see label color error message' do
|
||||
page.within '.label-form' do
|
||||
expect(page).to have_content 'Color is invalid'
|
||||
expect(page).to have_content 'Color must be a valid color code'
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -32,4 +32,8 @@ class Spinach::Features::ProjectMergeRequestsAcceptance < Spinach::FeatureSteps
|
||||
step 'I am signed in as a developer of the project' do
|
||||
login_as(@user)
|
||||
end
|
||||
|
||||
step 'I should see merge request merged' do
|
||||
expect(page).to have_content('The changes were merged into')
|
||||
end
|
||||
end
|
||||
|
||||
@@ -87,6 +87,17 @@ module SharedDiffNote
|
||||
end
|
||||
end
|
||||
|
||||
step 'I write a diff comment like ":smile:"' do
|
||||
page.within(diff_file_selector) do
|
||||
click_diff_line(sample_commit.line_code)
|
||||
|
||||
page.within("form[rel$='#{sample_commit.line_code}']") do
|
||||
fill_in 'note[note]', with: ':smile:'
|
||||
click_button('Add Comment')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
step 'I submit the diff comment' do
|
||||
page.within(diff_file_selector) do
|
||||
click_button("Add Comment")
|
||||
@@ -197,6 +208,12 @@ module SharedDiffNote
|
||||
end
|
||||
end
|
||||
|
||||
step 'I should see a diff comment with an emoji image' do
|
||||
page.within("#{diff_file_selector} .note") do
|
||||
expect(page).to have_xpath("//img[@alt=':smile:']")
|
||||
end
|
||||
end
|
||||
|
||||
step 'I click side-by-side diff button' do
|
||||
find('#parallel-diff-btn').trigger('click')
|
||||
end
|
||||
|
||||
+6
-2
@@ -7,8 +7,12 @@ module API
|
||||
helpers do
|
||||
def map_public_to_visibility_level(attrs)
|
||||
publik = attrs.delete(:public)
|
||||
publik = parse_boolean(publik)
|
||||
attrs[:visibility_level] = Gitlab::VisibilityLevel::PUBLIC if !attrs[:visibility_level].present? && publik == true
|
||||
if publik.present? && !attrs[:visibility_level].present?
|
||||
publik = parse_boolean(publik)
|
||||
# Since setting the public attribute to private could mean either
|
||||
# private or internal, use the more conservative option, private.
|
||||
attrs[:visibility_level] = (publik == true) ? Gitlab::VisibilityLevel::PUBLIC : Gitlab::VisibilityLevel::PRIVATE
|
||||
end
|
||||
attrs
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
module Gitlab
|
||||
module Blacklist
|
||||
extend self
|
||||
|
||||
def path
|
||||
%w(
|
||||
admin
|
||||
dashboard
|
||||
files
|
||||
groups
|
||||
help
|
||||
profile
|
||||
projects
|
||||
search
|
||||
public
|
||||
assets
|
||||
u
|
||||
s
|
||||
teams
|
||||
merge_requests
|
||||
issues
|
||||
users
|
||||
snippets
|
||||
services
|
||||
repository
|
||||
hooks
|
||||
notes
|
||||
unsubscribes
|
||||
all
|
||||
ci
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,6 +1,12 @@
|
||||
module Gitlab
|
||||
class ClosingIssueExtractor
|
||||
ISSUE_CLOSING_REGEX = Regexp.new(Gitlab.config.gitlab.issue_closing_pattern)
|
||||
ISSUE_CLOSING_REGEX = begin
|
||||
link_pattern = URI.regexp(%w(http https))
|
||||
|
||||
pattern = Gitlab.config.gitlab.issue_closing_pattern
|
||||
pattern = pattern.sub('%{issue_ref}', "(?:(?:#{link_pattern})|(?:#{Issue.reference_pattern}))")
|
||||
Regexp.new(pattern).freeze
|
||||
end
|
||||
|
||||
def initialize(project, current_user = nil)
|
||||
@extractor = Gitlab::ReferenceExtractor.new(project, current_user)
|
||||
@@ -9,10 +15,12 @@ module Gitlab
|
||||
def closed_by_message(message)
|
||||
return [] if message.nil?
|
||||
|
||||
closing_statements = message.scan(ISSUE_CLOSING_REGEX).
|
||||
map { |ref| ref[0] }.join(" ")
|
||||
closing_statements = []
|
||||
message.scan(ISSUE_CLOSING_REGEX) do
|
||||
closing_statements << Regexp.last_match[0]
|
||||
end
|
||||
|
||||
@extractor.analyze(closing_statements)
|
||||
@extractor.analyze(closing_statements.join(" "))
|
||||
|
||||
@extractor.issues
|
||||
end
|
||||
|
||||
@@ -178,7 +178,6 @@ module Gitlab
|
||||
Gitlab::Markdown::SanitizationFilter,
|
||||
|
||||
Gitlab::Markdown::UploadLinkFilter,
|
||||
Gitlab::Markdown::RelativeLinkFilter,
|
||||
Gitlab::Markdown::EmojiFilter,
|
||||
Gitlab::Markdown::TableOfContentsFilter,
|
||||
Gitlab::Markdown::AutolinkFilter,
|
||||
@@ -193,6 +192,8 @@ module Gitlab
|
||||
Gitlab::Markdown::CommitReferenceFilter,
|
||||
Gitlab::Markdown::LabelReferenceFilter,
|
||||
|
||||
Gitlab::Markdown::RelativeLinkFilter,
|
||||
|
||||
Gitlab::Markdown::TaskListFilter
|
||||
]
|
||||
end
|
||||
|
||||
@@ -2,8 +2,8 @@ require 'gitlab/markdown'
|
||||
|
||||
module Gitlab
|
||||
module Markdown
|
||||
# Issues, Snippets and Merge Requests shares similar functionality in refernce filtering.
|
||||
# All this functionality moved to this class
|
||||
# Issues, Merge Requests, Snippets, Commits and Commit Ranges share
|
||||
# similar functionality in reference filtering.
|
||||
class AbstractReferenceFilter < ReferenceFilter
|
||||
include CrossProjectReference
|
||||
|
||||
@@ -26,21 +26,20 @@ module Gitlab
|
||||
|
||||
# Public: Find references in text (like `!123` for merge requests)
|
||||
#
|
||||
# AnyReferenceFilter.references_in(text) do |match, object|
|
||||
# "<a href=...>PREFIX#{object}</a>"
|
||||
# AnyReferenceFilter.references_in(text) do |match, id, project_ref, matches|
|
||||
# object = find_object(project_ref, id)
|
||||
# "<a href=...>#{object.to_reference}</a>"
|
||||
# end
|
||||
#
|
||||
# PREFIX - symbol that detects reference (like ! for merge requests)
|
||||
# object - reference object (snippet, merget request etc)
|
||||
# text - String text to search.
|
||||
#
|
||||
# Yields the String match, the Integer referenced object ID, and an optional String
|
||||
# of the external project reference.
|
||||
# Yields the String match, the Integer referenced object ID, an optional String
|
||||
# of the external project reference, and all of the matchdata.
|
||||
#
|
||||
# Returns a String replaced with the return of the block.
|
||||
def self.references_in(text)
|
||||
text.gsub(object_class.reference_pattern) do |match|
|
||||
yield match, $~[object_sym].to_i, $~[:project]
|
||||
def self.references_in(text, pattern = object_class.reference_pattern)
|
||||
text.gsub(pattern) do |match|
|
||||
yield match, $~[object_sym].to_i, $~[:project], $~
|
||||
end
|
||||
end
|
||||
|
||||
@@ -61,8 +60,27 @@ module Gitlab
|
||||
end
|
||||
|
||||
def call
|
||||
# `#123`
|
||||
replace_text_nodes_matching(object_class.reference_pattern) do |content|
|
||||
object_link_filter(content)
|
||||
object_link_filter(content, object_class.reference_pattern)
|
||||
end
|
||||
|
||||
# `[Issue](#123)`, which is turned into
|
||||
# `<a href="#123">Issue</a>`
|
||||
replace_link_nodes_with_href(object_class.reference_pattern) do |link, text|
|
||||
object_link_filter(link, object_class.reference_pattern, link_text: text)
|
||||
end
|
||||
|
||||
# `http://gitlab.example.com/namespace/project/issues/123`, which is turned into
|
||||
# `<a href="http://gitlab.example.com/namespace/project/issues/123">http://gitlab.example.com/namespace/project/issues/123</a>`
|
||||
replace_link_nodes_with_text(object_class.link_reference_pattern) do |text|
|
||||
object_link_filter(text, object_class.link_reference_pattern)
|
||||
end
|
||||
|
||||
# `[Issue](http://gitlab.example.com/namespace/project/issues/123)`, which is turned into
|
||||
# `<a href="http://gitlab.example.com/namespace/project/issues/123">Issue</a>`
|
||||
replace_link_nodes_with_href(object_class.link_reference_pattern) do |link, text|
|
||||
object_link_filter(link, object_class.link_reference_pattern, link_text: text)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -70,30 +88,57 @@ module Gitlab
|
||||
# to the referenced object's details page.
|
||||
#
|
||||
# text - String text to replace references in.
|
||||
# pattern - Reference pattern to match against.
|
||||
# link_text - Original content of the link being replaced.
|
||||
#
|
||||
# Returns a String with references replaced with links. All links
|
||||
# have `gfm` and `gfm-OBJECT_NAME` class names attached for styling.
|
||||
def object_link_filter(text)
|
||||
references_in(text) do |match, id, project_ref|
|
||||
def object_link_filter(text, pattern, link_text: nil)
|
||||
references_in(text, pattern) do |match, id, project_ref, matches|
|
||||
project = project_from_ref(project_ref)
|
||||
|
||||
if project && object = find_object(project, id)
|
||||
title = escape_once("#{object_title}: #{object.title}")
|
||||
title = escape_once(object_link_title(object))
|
||||
klass = reference_class(object_sym)
|
||||
data = data_attribute(project: project.id, object_sym => object.id)
|
||||
url = url_for_object(object, project)
|
||||
|
||||
data = data_attribute(
|
||||
original: link_text || match,
|
||||
project: project.id,
|
||||
object_sym => object.id
|
||||
)
|
||||
|
||||
url = matches[:url] if matches.names.include?("url")
|
||||
url ||= url_for_object(object, project)
|
||||
|
||||
text = link_text
|
||||
unless text
|
||||
text = object.reference_link_text(context[:project])
|
||||
|
||||
extras = object_link_text_extras(object, matches)
|
||||
text += " (#{extras.join(", ")})" if extras.any?
|
||||
end
|
||||
|
||||
%(<a href="#{url}" #{data}
|
||||
title="#{title}"
|
||||
class="#{klass}">#{match}</a>)
|
||||
class="#{klass}">#{text}</a>)
|
||||
else
|
||||
match
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def object_title
|
||||
object_class.name.titleize
|
||||
def object_link_text_extras(object, matches)
|
||||
extras = []
|
||||
|
||||
if matches.names.include?("anchor") && matches[:anchor] && matches[:anchor] =~ /\A\#note_(\d+)\z/
|
||||
extras << "comment #{$1}"
|
||||
end
|
||||
|
||||
extras
|
||||
end
|
||||
|
||||
def object_link_title(object)
|
||||
"#{object_class.name.titleize}: #{object.title}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,24 +5,14 @@ module Gitlab
|
||||
# HTML filter that replaces commit range references with links.
|
||||
#
|
||||
# This filter supports cross-project references.
|
||||
class CommitRangeReferenceFilter < ReferenceFilter
|
||||
include CrossProjectReference
|
||||
class CommitRangeReferenceFilter < AbstractReferenceFilter
|
||||
def self.object_class
|
||||
CommitRange
|
||||
end
|
||||
|
||||
# Public: Find commit range references in text
|
||||
#
|
||||
# CommitRangeReferenceFilter.references_in(text) do |match, commit_range, project_ref|
|
||||
# "<a href=...>#{commit_range}</a>"
|
||||
# end
|
||||
#
|
||||
# text - String text to search.
|
||||
#
|
||||
# Yields the String match, the String commit range, and an optional String
|
||||
# of the external project reference.
|
||||
#
|
||||
# Returns a String replaced with the return of the block.
|
||||
def self.references_in(text)
|
||||
text.gsub(CommitRange.reference_pattern) do |match|
|
||||
yield match, $~[:commit_range], $~[:project]
|
||||
def self.references_in(text, pattern = CommitRange.reference_pattern)
|
||||
text.gsub(pattern) do |match|
|
||||
yield match, $~[:commit_range], $~[:project], $~
|
||||
end
|
||||
end
|
||||
|
||||
@@ -31,9 +21,9 @@ module Gitlab
|
||||
return unless project
|
||||
|
||||
id = node.attr("data-commit-range")
|
||||
range = CommitRange.new(id, project)
|
||||
range = find_object(project, id)
|
||||
|
||||
return unless range.valid_commits?
|
||||
return unless range
|
||||
|
||||
{ commit_range: range }
|
||||
end
|
||||
@@ -44,49 +34,25 @@ module Gitlab
|
||||
@commit_map = {}
|
||||
end
|
||||
|
||||
def call
|
||||
replace_text_nodes_matching(CommitRange.reference_pattern) do |content|
|
||||
commit_range_link_filter(content)
|
||||
end
|
||||
def self.find_object(project, id)
|
||||
range = CommitRange.new(id, project)
|
||||
|
||||
range.valid_commits? ? range : nil
|
||||
end
|
||||
|
||||
# Replace commit range references in text with links to compare the commit
|
||||
# ranges.
|
||||
#
|
||||
# text - String text to replace references in.
|
||||
#
|
||||
# Returns a String with commit range references replaced with links. All
|
||||
# links have `gfm` and `gfm-commit_range` class names attached for
|
||||
# styling.
|
||||
def commit_range_link_filter(text)
|
||||
self.class.references_in(text) do |match, id, project_ref|
|
||||
project = self.project_from_ref(project_ref)
|
||||
|
||||
range = CommitRange.new(id, project)
|
||||
|
||||
if range.valid_commits?
|
||||
url = url_for_commit_range(project, range)
|
||||
|
||||
title = range.reference_title
|
||||
klass = reference_class(:commit_range)
|
||||
data = data_attribute(project: project.id, commit_range: id)
|
||||
|
||||
project_ref += '@' if project_ref
|
||||
|
||||
%(<a href="#{url}" #{data}
|
||||
title="#{title}"
|
||||
class="#{klass}">#{project_ref}#{range}</a>)
|
||||
else
|
||||
match
|
||||
end
|
||||
end
|
||||
def find_object(*args)
|
||||
self.class.find_object(*args)
|
||||
end
|
||||
|
||||
def url_for_commit_range(project, range)
|
||||
def url_for_object(range, project)
|
||||
h = Gitlab::Application.routes.url_helpers
|
||||
h.namespace_project_compare_url(project.namespace, project,
|
||||
range.to_param.merge(only_path: context[:only_path]))
|
||||
end
|
||||
|
||||
def object_link_title(range)
|
||||
range.reference_title
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,24 +5,14 @@ module Gitlab
|
||||
# HTML filter that replaces commit references with links.
|
||||
#
|
||||
# This filter supports cross-project references.
|
||||
class CommitReferenceFilter < ReferenceFilter
|
||||
include CrossProjectReference
|
||||
class CommitReferenceFilter < AbstractReferenceFilter
|
||||
def self.object_class
|
||||
Commit
|
||||
end
|
||||
|
||||
# Public: Find commit references in text
|
||||
#
|
||||
# CommitReferenceFilter.references_in(text) do |match, commit, project_ref|
|
||||
# "<a href=...>#{commit}</a>"
|
||||
# end
|
||||
#
|
||||
# text - String text to search.
|
||||
#
|
||||
# Yields the String match, the String commit identifier, and an optional
|
||||
# String of the external project reference.
|
||||
#
|
||||
# Returns a String replaced with the return of the block.
|
||||
def self.references_in(text)
|
||||
text.gsub(Commit.reference_pattern) do |match|
|
||||
yield match, $~[:commit], $~[:project]
|
||||
def self.references_in(text, pattern = Commit.reference_pattern)
|
||||
text.gsub(pattern) do |match|
|
||||
yield match, $~[:commit], $~[:project], $~
|
||||
end
|
||||
end
|
||||
|
||||
@@ -31,58 +21,32 @@ module Gitlab
|
||||
return unless project
|
||||
|
||||
id = node.attr("data-commit")
|
||||
commit = commit_from_ref(project, id)
|
||||
commit = find_object(project, id)
|
||||
|
||||
return unless commit
|
||||
|
||||
{ commit: commit }
|
||||
end
|
||||
|
||||
def call
|
||||
replace_text_nodes_matching(Commit.reference_pattern) do |content|
|
||||
commit_link_filter(content)
|
||||
end
|
||||
end
|
||||
|
||||
# Replace commit references in text with links to the commit specified.
|
||||
#
|
||||
# text - String text to replace references in.
|
||||
#
|
||||
# Returns a String with commit references replaced with links. All links
|
||||
# have `gfm` and `gfm-commit` class names attached for styling.
|
||||
def commit_link_filter(text)
|
||||
self.class.references_in(text) do |match, id, project_ref|
|
||||
project = self.project_from_ref(project_ref)
|
||||
|
||||
if commit = self.class.commit_from_ref(project, id)
|
||||
url = url_for_commit(project, commit)
|
||||
|
||||
title = escape_once(commit.link_title)
|
||||
klass = reference_class(:commit)
|
||||
data = data_attribute(project: project.id, commit: id)
|
||||
|
||||
project_ref += '@' if project_ref
|
||||
|
||||
%(<a href="#{url}" #{data}
|
||||
title="#{title}"
|
||||
class="#{klass}">#{project_ref}#{commit.short_id}</a>)
|
||||
else
|
||||
match
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.commit_from_ref(project, id)
|
||||
def self.find_object(project, id)
|
||||
if project && project.valid_repo?
|
||||
project.commit(id)
|
||||
end
|
||||
end
|
||||
|
||||
def url_for_commit(project, commit)
|
||||
def find_object(*args)
|
||||
self.class.find_object(*args)
|
||||
end
|
||||
|
||||
def url_for_object(commit, project)
|
||||
h = Gitlab::Application.routes.url_helpers
|
||||
h.namespace_project_commit_url(project.namespace, project, commit,
|
||||
only_path: context[:only_path])
|
||||
end
|
||||
|
||||
def object_link_title(commit)
|
||||
commit.link_title
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -30,6 +30,10 @@ module Gitlab
|
||||
replace_text_nodes_matching(ExternalIssue.reference_pattern) do |content|
|
||||
issue_link_filter(content)
|
||||
end
|
||||
|
||||
replace_link_nodes_with_href(ExternalIssue.reference_pattern) do |link, text|
|
||||
issue_link_filter(link, link_text: text)
|
||||
end
|
||||
end
|
||||
|
||||
# Replace `JIRA-123` issue references in text with links to the referenced
|
||||
@@ -39,7 +43,7 @@ module Gitlab
|
||||
#
|
||||
# Returns a String with `JIRA-123` references replaced with links. All
|
||||
# links have `gfm` and `gfm-issue` class names attached for styling.
|
||||
def issue_link_filter(text)
|
||||
def issue_link_filter(text, link_text: nil)
|
||||
project = context[:project]
|
||||
|
||||
self.class.references_in(text) do |match, issue|
|
||||
@@ -49,9 +53,11 @@ module Gitlab
|
||||
klass = reference_class(:issue)
|
||||
data = data_attribute(project: project.id)
|
||||
|
||||
text = link_text || match
|
||||
|
||||
%(<a href="#{url}" #{data}
|
||||
title="#{title}"
|
||||
class="#{klass}">#{match}</a>)
|
||||
class="#{klass}">#{text}</a>)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@ module Gitlab
|
||||
class ExternalLinkFilter < HTML::Pipeline::Filter
|
||||
def call
|
||||
doc.search('a').each do |node|
|
||||
next unless node.has_attribute?('href')
|
||||
link = node.attr('href')
|
||||
|
||||
link = node.attribute('href').value
|
||||
next unless link
|
||||
|
||||
# Skip non-HTTP(S) links
|
||||
next unless link.start_with?('http')
|
||||
|
||||
@@ -30,6 +30,10 @@ module Gitlab
|
||||
replace_text_nodes_matching(Label.reference_pattern) do |content|
|
||||
label_link_filter(content)
|
||||
end
|
||||
|
||||
replace_link_nodes_with_href(Label.reference_pattern) do |link, text|
|
||||
label_link_filter(link, link_text: text)
|
||||
end
|
||||
end
|
||||
|
||||
# Replace label references in text with links to the label specified.
|
||||
@@ -38,7 +42,7 @@ module Gitlab
|
||||
#
|
||||
# Returns a String with label references replaced with links. All links
|
||||
# have `gfm` and `gfm-label` class names attached for styling.
|
||||
def label_link_filter(text)
|
||||
def label_link_filter(text, link_text: nil)
|
||||
project = context[:project]
|
||||
|
||||
self.class.references_in(text) do |match, id, name|
|
||||
@@ -47,10 +51,16 @@ module Gitlab
|
||||
if label = project.labels.find_by(params)
|
||||
url = url_for_label(project, label)
|
||||
klass = reference_class(:label)
|
||||
data = data_attribute(project: project.id, label: label.id)
|
||||
data = data_attribute(
|
||||
original: link_text || match,
|
||||
project: project.id,
|
||||
label: label.id
|
||||
)
|
||||
|
||||
text = link_text || render_colored_label(label)
|
||||
|
||||
%(<a href="#{url}" #{data}
|
||||
class="#{klass}">#{render_colored_label(label)}</a>)
|
||||
class="#{klass}">#{text}</a>)
|
||||
else
|
||||
match
|
||||
end
|
||||
@@ -59,8 +69,8 @@ module Gitlab
|
||||
|
||||
def url_for_label(project, label)
|
||||
h = Gitlab::Application.routes.url_helpers
|
||||
h.namespace_project_issues_path(project.namespace, project,
|
||||
label_name: label.name)
|
||||
h.namespace_project_issues_url( project.namespace, project, label_name: label.name,
|
||||
only_path: context[:only_path])
|
||||
end
|
||||
|
||||
def render_colored_label(label)
|
||||
|
||||
@@ -20,6 +20,16 @@ module Gitlab
|
||||
h.namespace_project_merge_request_url(project.namespace, project, mr,
|
||||
only_path: context[:only_path])
|
||||
end
|
||||
|
||||
def object_link_text_extras(object, matches)
|
||||
extras = super
|
||||
|
||||
if matches.names.include?("path") && matches[:path] && matches[:path] == '/diffs'
|
||||
extras.unshift "diffs"
|
||||
end
|
||||
|
||||
extras
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -12,7 +12,10 @@ module Gitlab
|
||||
def call
|
||||
doc.css('a.gfm').each do |node|
|
||||
unless user_can_reference?(node)
|
||||
node.replace(node.text)
|
||||
# The reference should be replaced by the original text,
|
||||
# which is not always the same as the rendered text.
|
||||
text = node.attr('data-original') || node.text
|
||||
node.replace(text)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -122,6 +122,80 @@ module Gitlab
|
||||
doc
|
||||
end
|
||||
|
||||
# Iterate through the document's link nodes, yielding the current node's
|
||||
# content if:
|
||||
#
|
||||
# * The `project` context value is present AND
|
||||
# * The node's content matches `pattern`
|
||||
#
|
||||
# pattern - Regex pattern against which to match the node's content
|
||||
#
|
||||
# Yields the current node's String contents. The result of the block will
|
||||
# replace the node and update the current document.
|
||||
#
|
||||
# Returns the updated Nokogiri::HTML::DocumentFragment object.
|
||||
def replace_link_nodes_with_text(pattern)
|
||||
return doc if project.nil?
|
||||
|
||||
doc.search('a').each do |node|
|
||||
klass = node.attr('class')
|
||||
next if klass && klass.include?('gfm')
|
||||
|
||||
link = node.attr('href')
|
||||
text = node.text
|
||||
|
||||
next unless link && text
|
||||
|
||||
link = URI.decode(link)
|
||||
# Ignore ending punctionation like periods or commas
|
||||
next unless link == text && text =~ /\A#{pattern}/
|
||||
|
||||
html = yield text
|
||||
|
||||
next if html == text
|
||||
|
||||
node.replace(html)
|
||||
end
|
||||
|
||||
doc
|
||||
end
|
||||
|
||||
# Iterate through the document's link nodes, yielding the current node's
|
||||
# content if:
|
||||
#
|
||||
# * The `project` context value is present AND
|
||||
# * The node's HREF matches `pattern`
|
||||
#
|
||||
# pattern - Regex pattern against which to match the node's HREF
|
||||
#
|
||||
# Yields the current node's String HREF and String content.
|
||||
# The result of the block will replace the node and update the current document.
|
||||
#
|
||||
# Returns the updated Nokogiri::HTML::DocumentFragment object.
|
||||
def replace_link_nodes_with_href(pattern)
|
||||
return doc if project.nil?
|
||||
|
||||
doc.search('a').each do |node|
|
||||
klass = node.attr('class')
|
||||
next if klass && klass.include?('gfm')
|
||||
|
||||
link = node.attr('href')
|
||||
text = node.text
|
||||
|
||||
next unless link && text
|
||||
link = URI.decode(link)
|
||||
next unless link && link =~ /\A#{pattern}\z/
|
||||
|
||||
html = yield link, text
|
||||
|
||||
next if html == link
|
||||
|
||||
node.replace(html)
|
||||
end
|
||||
|
||||
doc
|
||||
end
|
||||
|
||||
# Ensure that a :project key exists in context
|
||||
#
|
||||
# Note that while the key might exist, its value could be nil!
|
||||
|
||||
@@ -17,6 +17,9 @@ module Gitlab
|
||||
return doc unless linkable_files?
|
||||
|
||||
doc.search('a').each do |el|
|
||||
klass = el.attr('class')
|
||||
next if klass && klass.include?('gfm')
|
||||
|
||||
process_link_attr el.attribute('href')
|
||||
end
|
||||
|
||||
|
||||
@@ -52,6 +52,10 @@ module Gitlab
|
||||
replace_text_nodes_matching(User.reference_pattern) do |content|
|
||||
user_link_filter(content)
|
||||
end
|
||||
|
||||
replace_link_nodes_with_href(User.reference_pattern) do |link, text|
|
||||
user_link_filter(link, link_text: text)
|
||||
end
|
||||
end
|
||||
|
||||
# Replace `@user` user references in text with links to the referenced
|
||||
@@ -61,12 +65,12 @@ module Gitlab
|
||||
#
|
||||
# Returns a String with `@user` references replaced with links. All links
|
||||
# have `gfm` and `gfm-project_member` class names attached for styling.
|
||||
def user_link_filter(text)
|
||||
def user_link_filter(text, link_text: nil)
|
||||
self.class.references_in(text) do |match, username|
|
||||
if username == 'all'
|
||||
link_to_all
|
||||
link_to_all(link_text: link_text)
|
||||
elsif namespace = Namespace.find_by(path: username)
|
||||
link_to_namespace(namespace) || match
|
||||
link_to_namespace(namespace, link_text: link_text) || match
|
||||
else
|
||||
match
|
||||
end
|
||||
@@ -83,36 +87,36 @@ module Gitlab
|
||||
reference_class(:project_member)
|
||||
end
|
||||
|
||||
def link_to_all
|
||||
def link_to_all(link_text: nil)
|
||||
project = context[:project]
|
||||
url = urls.namespace_project_url(project.namespace, project,
|
||||
only_path: context[:only_path])
|
||||
data = data_attribute(project: project.id)
|
||||
text = User.reference_prefix + 'all'
|
||||
text = link_text || User.reference_prefix + 'all'
|
||||
|
||||
link_tag(url, data, text)
|
||||
end
|
||||
|
||||
def link_to_namespace(namespace)
|
||||
def link_to_namespace(namespace, link_text: nil)
|
||||
if namespace.is_a?(Group)
|
||||
link_to_group(namespace.path, namespace)
|
||||
link_to_group(namespace.path, namespace, link_text: link_text)
|
||||
else
|
||||
link_to_user(namespace.path, namespace)
|
||||
link_to_user(namespace.path, namespace, link_text: link_text)
|
||||
end
|
||||
end
|
||||
|
||||
def link_to_group(group, namespace)
|
||||
def link_to_group(group, namespace, link_text: nil)
|
||||
url = urls.group_url(group, only_path: context[:only_path])
|
||||
data = data_attribute(group: namespace.id)
|
||||
text = Group.reference_prefix + group
|
||||
text = link_text || Group.reference_prefix + group
|
||||
|
||||
link_tag(url, data, text)
|
||||
end
|
||||
|
||||
def link_to_user(user, namespace)
|
||||
def link_to_user(user, namespace, link_text: nil)
|
||||
url = urls.user_url(user, only_path: context[:only_path])
|
||||
data = data_attribute(user: namespace.owner_id)
|
||||
text = User.reference_prefix + user
|
||||
text = link_text || User.reference_prefix + user
|
||||
|
||||
link_tag(url, data, text)
|
||||
end
|
||||
|
||||
@@ -18,10 +18,7 @@ module Gitlab
|
||||
# homepage: String,
|
||||
# },
|
||||
# commits: Array,
|
||||
# total_commits_count: Fixnum,
|
||||
# added: ["CHANGELOG"],
|
||||
# modified: [],
|
||||
# removed: ["tmp/file.txt"]
|
||||
# total_commits_count: Fixnum
|
||||
# }
|
||||
#
|
||||
def build(project, user, oldrev, newrev, ref, commits = [], message = nil)
|
||||
@@ -33,11 +30,12 @@ module Gitlab
|
||||
|
||||
# For performance purposes maximum 20 latest commits
|
||||
# will be passed as post receive hook data.
|
||||
commit_attrs = commits_limited.map(&:hook_attrs)
|
||||
commit_attrs = commits_limited.map do |commit|
|
||||
commit.hook_attrs(with_changed_files: true)
|
||||
end
|
||||
|
||||
type = Gitlab::Git.tag_ref?(ref) ? "tag_push" : "push"
|
||||
|
||||
repo_changes = repo_changes(project, newrev, oldrev)
|
||||
# Hash to be passed as post_receive_data
|
||||
data = {
|
||||
object_kind: type,
|
||||
@@ -60,10 +58,7 @@ module Gitlab
|
||||
visibility_level: project.visibility_level
|
||||
},
|
||||
commits: commit_attrs,
|
||||
total_commits_count: commits_count,
|
||||
added: repo_changes[:added],
|
||||
modified: repo_changes[:modified],
|
||||
removed: repo_changes[:removed]
|
||||
total_commits_count: commits_count
|
||||
}
|
||||
|
||||
data
|
||||
@@ -94,27 +89,6 @@ module Gitlab
|
||||
newrev
|
||||
end
|
||||
end
|
||||
|
||||
def repo_changes(project, newrev, oldrev)
|
||||
changes = { added: [], modified: [], removed: [] }
|
||||
compare_result = CompareService.new.
|
||||
execute(project, newrev, project, oldrev)
|
||||
|
||||
if compare_result
|
||||
compare_result.diffs.each do |diff|
|
||||
case true
|
||||
when diff.deleted_file
|
||||
changes[:removed] << diff.old_path
|
||||
when diff.renamed_file, diff.new_file
|
||||
changes[:added] << diff.new_path
|
||||
else
|
||||
changes[:modified] << diff.new_path
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
changes
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user