Commit 7658a644 authored by Jeroen Nijhof's avatar Jeroen Nijhof

Merge branch 'master' of https://gitlab.com/gitlab-org/gitlab-ce into sentry-integration

Conflicts:
	db/schema.rb
parents 85e0fce9 a382ad99
...@@ -8,7 +8,7 @@ before_script: ...@@ -8,7 +8,7 @@ before_script:
- touch log/application.log - touch log/application.log
- touch log/test.log - touch log/test.log
- bundle install --without postgres production --jobs $(nproc) "${FLAGS[@]}" - bundle install --without postgres production --jobs $(nproc) "${FLAGS[@]}"
- bundle exec rake db:reset db:create RAILS_ENV=test - RAILS_ENV=test bundle exec rake db:drop db:create db:schema:load db:migrate
spec:feature: spec:feature:
script: script:
......
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
v 8.5.0 (unreleased)
- Add "visibility" flag to GET /projects api endpoint
v 8.4.0 (unreleased) v 8.4.0 (unreleased)
- Allow LDAP users to change their email if it was not set by the LDAP server
- Ensure Gravatar host looks like an actual host
- Consider re-assign as a mention from a notification point of view
- Add pagination headers to already paginated API resources - Add pagination headers to already paginated API resources
- Properly generate diff of orphan commits, like the first commit in a repository - Properly generate diff of orphan commits, like the first commit in a repository
- Improve the consistency of commit titles, branch names, tag names, issue/MR titles, on their respective project pages - Improve the consistency of commit titles, branch names, tag names, issue/MR titles, on their respective project pages
- Autocomplete data is now always loaded, instead of when focusing a comment text area (Yorick Peterse) - Autocomplete data is now always loaded, instead of when focusing a comment text area
- Improved performance of finding issues for an entire group (Yorick Peterse) - Improved performance of finding issues for an entire group
- Added custom application performance measuring system powered by InfluxDB (Yorick Peterse) - Added custom application performance measuring system powered by InfluxDB
- Bump fog to 1.36.0 (Stan Hu) - Bump fog to 1.36.0 (Stan Hu)
- Add user's last used IP addresses to admin page (Stan Hu) - Add user's last used IP addresses to admin page (Stan Hu)
- Add housekeeping function to project settings page - Add housekeeping function to project settings page
...@@ -57,11 +63,17 @@ v 8.4.0 (unreleased) ...@@ -57,11 +63,17 @@ v 8.4.0 (unreleased)
- Autosize Markdown textareas - Autosize Markdown textareas
- Import GitHub wiki into GitLab - Import GitHub wiki into GitLab
- Add reporters ability to download and browse build artifacts (Andrew Johnson) - Add reporters ability to download and browse build artifacts (Andrew Johnson)
- Autofill referring url in message box when reporting user abuse. (Josh Frye) - Autofill referring url in message box when reporting user abuse.
- Remove leading comma on award emoji when the user is the first to award the emoji (Zeger-Jan van de Weg)
- Add build artifacts browser
- Improve UX in builds artifacts browser
- Increase default size of `data` column in `events` table when using MySQL
- Expose button to CI Lint tool on project builds page
- Fix: Creator should be added as a master of the project on creation
- Added X-GitLab-... headers to emails from CI and Email On Push services (Anton Baklanov)
v 8.3.4 v 8.3.4
- Use gitlab-workhorse 0.5.4 (fixes API routing bug) - Use gitlab-workhorse 0.5.4 (fixes API routing bug)
- Add build artifacts browser
v 8.3.3 v 8.3.3
- Preserve CE behavior with JIRA integration by only calling API if URL is set - Preserve CE behavior with JIRA integration by only calling API if URL is set
...@@ -109,6 +121,7 @@ v 8.3.0 ...@@ -109,6 +121,7 @@ v 8.3.0
- Fix: Assignee selector is empty when 'Unassigned' is selected (Jose Corcuera) - Fix: Assignee selector is empty when 'Unassigned' is selected (Jose Corcuera)
- Add rake tasks for git repository maintainance (Zeger-Jan van de Weg) - Add rake tasks for git repository maintainance (Zeger-Jan van de Weg)
- Fix 500 error when update group member permission - Fix 500 error when update group member permission
- Fix: As an admin, cannot add oneself as a member to a group/project
- Trim leading and trailing whitespace of milestone and issueable titles (Jose Corcuera) - Trim leading and trailing whitespace of milestone and issueable titles (Jose Corcuera)
- Recognize issue/MR/snippet/commit links as references - Recognize issue/MR/snippet/commit links as references
- Backport JIRA features from EE to CE - Backport JIRA features from EE to CE
...@@ -170,7 +183,6 @@ v 8.2.2 ...@@ -170,7 +183,6 @@ v 8.2.2
- Fix Error 500 when viewing user's personal projects from admin page (Stan Hu) - Fix Error 500 when viewing user's personal projects from admin page (Stan Hu)
- Fix: Raw private snippets access workflow - Fix: Raw private snippets access workflow
- Prevent "413 Request entity too large" errors when pushing large files with LFS - Prevent "413 Request entity too large" errors when pushing large files with LFS
- Fix: As an admin, cannot add oneself as a member to a group/project
- Fix invalid links within projects dashboard header - Fix invalid links within projects dashboard header
- Make current user the first user in assignee dropdown in issues detail page (Stan Hu) - Make current user the first user in assignee dropdown in issues detail page (Stan Hu)
- Fix: duplicate email notifications on issue comments - Fix: duplicate email notifications on issue comments
......
...@@ -18,7 +18,7 @@ gem "mysql2", '~> 0.3.16', group: :mysql ...@@ -18,7 +18,7 @@ gem "mysql2", '~> 0.3.16', group: :mysql
gem "pg", '~> 0.18.2', group: :postgres gem "pg", '~> 0.18.2', group: :postgres
# Authentication libraries # Authentication libraries
gem 'devise', '~> 3.5.3' gem 'devise', '~> 3.5.4'
gem 'devise-async', '~> 0.9.0' gem 'devise-async', '~> 0.9.0'
gem 'doorkeeper', '~> 2.2.0' gem 'doorkeeper', '~> 2.2.0'
gem 'omniauth', '~> 1.2.2' gem 'omniauth', '~> 1.2.2'
......
...@@ -157,7 +157,7 @@ GEM ...@@ -157,7 +157,7 @@ GEM
activerecord (>= 3.2.0, < 5.0) activerecord (>= 3.2.0, < 5.0)
descendants_tracker (0.0.4) descendants_tracker (0.0.4)
thread_safe (~> 0.3, >= 0.3.1) thread_safe (~> 0.3, >= 0.3.1)
devise (3.5.3) devise (3.5.4)
bcrypt (~> 3.0) bcrypt (~> 3.0)
orm_adapter (~> 0.1) orm_adapter (~> 0.1)
railties (>= 3.2.6, < 5) railties (>= 3.2.6, < 5)
...@@ -614,7 +614,7 @@ GEM ...@@ -614,7 +614,7 @@ GEM
thor (>= 0.18.1, < 2.0) thor (>= 0.18.1, < 2.0)
rainbow (2.0.0) rainbow (2.0.0)
raindrops (0.15.0) raindrops (0.15.0)
rake (10.4.2) rake (10.5.0)
raphael-rails (2.1.2) raphael-rails (2.1.2)
rb-fsevent (0.9.6) rb-fsevent (0.9.6)
rb-inotify (0.9.5) rb-inotify (0.9.5)
...@@ -648,8 +648,8 @@ GEM ...@@ -648,8 +648,8 @@ GEM
request_store (1.2.1) request_store (1.2.1)
rerun (0.11.0) rerun (0.11.0)
listen (~> 3.0) listen (~> 3.0)
responders (2.1.0) responders (2.1.1)
railties (>= 4.2.0, < 5) railties (>= 4.2.0, < 5.1)
rest-client (1.8.0) rest-client (1.8.0)
http-cookie (>= 1.0.2, < 2.0) http-cookie (>= 1.0.2, < 2.0)
mime-types (>= 1.16, < 3.0) mime-types (>= 1.16, < 3.0)
...@@ -913,7 +913,7 @@ DEPENDENCIES ...@@ -913,7 +913,7 @@ DEPENDENCIES
d3_rails (~> 3.5.0) d3_rails (~> 3.5.0)
database_cleaner (~> 1.4.0) database_cleaner (~> 1.4.0)
default_value_for (~> 3.0.0) default_value_for (~> 3.0.0)
devise (~> 3.5.3) devise (~> 3.5.4)
devise-async (~> 0.9.0) devise-async (~> 0.9.0)
devise-two-factor (~> 2.0.0) devise-two-factor (~> 2.0.0)
diffy (~> 3.0.3) diffy (~> 3.0.3)
......
8.4.0.pre 8.5.0-pre
...@@ -66,9 +66,14 @@ class @AwardsHandler ...@@ -66,9 +66,14 @@ class @AwardsHandler
addMeToAuthorList: (emoji) -> addMeToAuthorList: (emoji) ->
award_block = @findEmojiIcon(emoji).parent() award_block = @findEmojiIcon(emoji).parent()
authors = award_block.attr("data-original-title").split(", ") authors = _.compact(award_block.attr("data-original-title").split(", "))
authors.push("me") authors.push("me")
if authors.length == 1
award_block.attr("title", "me")
else
award_block.attr("title", authors.join(", ")) award_block.attr("title", authors.join(", "))
@resetTooltip(award_block) @resetTooltip(award_block)
resetTooltip: (award) -> resetTooltip: (award) ->
......
...@@ -6,7 +6,7 @@ class @Star ...@@ -6,7 +6,7 @@ class @Star
$starIcon = $this.find('i') $starIcon = $this.find('i')
toggleStar = (isStarred) -> toggleStar = (isStarred) ->
$this.parent().find('span.count').text data.star_count $this.parent().find('.star-count').text data.star_count
if isStarred if isStarred
$starSpan.removeClass('starred').text 'Star' $starSpan.removeClass('starred').text 'Star'
$starIcon.removeClass('fa-star').addClass 'fa-star-o' $starIcon.removeClass('fa-star').addClass 'fa-star-o'
......
.filter-item { .filter-item {
margin-right: 15px; margin-right: 6px;
} }
@media (min-width: 800px) { @media (min-width: 800px) {
......
...@@ -26,6 +26,7 @@ $gl-vert-padding: 6px; ...@@ -26,6 +26,7 @@ $gl-vert-padding: 6px;
$gl-padding-top:10px; $gl-padding-top:10px;
$gl-avatar-size: 46px; $gl-avatar-size: 46px;
$secondary-text: #7f8fa4; $secondary-text: #7f8fa4;
$error-exclamation-point: #E62958;
/* /*
* Color schema * Color schema
......
...@@ -36,6 +36,10 @@ li.commit { ...@@ -36,6 +36,10 @@ li.commit {
line-height: 20px; line-height: 20px;
margin-bottom: 2px; margin-bottom: 2px;
.btn-clipboard {
margin-top: -1px;
}
.notes_count { .notes_count {
float: right; float: right;
margin-right: 10px; margin-right: 10px;
......
...@@ -558,3 +558,9 @@ pre.light-well { ...@@ -558,3 +558,9 @@ pre.light-well {
width: 101%; width: 101%;
} }
} }
.cannot-be-merged,
.cannot-be-merged:hover {
color: #E62958;
margin-top: 2px;
}
...@@ -3,6 +3,10 @@ ...@@ -3,6 +3,10 @@
border-bottom: 1px solid #DDD; border-bottom: 1px solid #DDD;
padding-bottom: 15px; padding-bottom: 15px;
margin-bottom: 15px; margin-bottom: 15px;
.term {
height: 22px;
}
} }
} }
......
...@@ -3,13 +3,26 @@ module Emails ...@@ -3,13 +3,26 @@ module Emails
def build_fail_email(build_id, to) def build_fail_email(build_id, to)
@build = Ci::Build.find(build_id) @build = Ci::Build.find(build_id)
@project = @build.project @project = @build.project
add_project_headers
add_build_headers
headers['X-GitLab-Build-Status'] = "failed"
mail(to: to, subject: subject("Build failed for #{@project.name}", @build.short_sha)) mail(to: to, subject: subject("Build failed for #{@project.name}", @build.short_sha))
end end
def build_success_email(build_id, to) def build_success_email(build_id, to)
@build = Ci::Build.find(build_id) @build = Ci::Build.find(build_id)
@project = @build.project @project = @build.project
add_project_headers
add_build_headers
headers['X-GitLab-Build-Status'] = "success"
mail(to: to, subject: subject("Build success for #{@project.name}", @build.short_sha)) mail(to: to, subject: subject("Build success for #{@project.name}", @build.short_sha))
end end
private
def add_build_headers
headers['X-GitLab-Build-Id'] = @build.id
headers['X-GitLab-Build-Ref'] = @build.ref
end
end end
end end
...@@ -65,6 +65,10 @@ module Emails ...@@ -65,6 +65,10 @@ module Emails
# used in notify layout # used in notify layout
@target_url = @message.target_url @target_url = @message.target_url
@project = Project.find project_id
add_project_headers
headers['X-GitLab-Author'] = @message.author_username
mail(from: sender(@message.author_id, @message.send_from_committer_email?), mail(from: sender(@message.author_id, @message.send_from_committer_email?),
reply_to: @message.reply_to, reply_to: @message.reply_to,
......
...@@ -100,12 +100,7 @@ class Notify < BaseMailer ...@@ -100,12 +100,7 @@ class Notify < BaseMailer
end end
def mail_thread(model, headers = {}) def mail_thread(model, headers = {})
if @project add_project_headers
headers['X-GitLab-Project'] = @project.name
headers['X-GitLab-Project-Id'] = @project.id
headers['X-GitLab-Project-Path'] = @project.path_with_namespace
end
headers["X-GitLab-#{model.class.name}-ID"] = model.id headers["X-GitLab-#{model.class.name}-ID"] = model.id
headers['X-GitLab-Reply-Key'] = reply_key headers['X-GitLab-Reply-Key'] = reply_key
...@@ -152,4 +147,12 @@ class Notify < BaseMailer ...@@ -152,4 +147,12 @@ class Notify < BaseMailer
def reply_key def reply_key
@reply_key ||= SentNotification.reply_key @reply_key ||= SentNotification.reply_key
end end
def add_project_headers
return unless @project
headers['X-GitLab-Project'] = @project.name
headers['X-GitLab-Project-Id'] = @project.id
headers['X-GitLab-Project-Path'] = @project.path_with_namespace
end
end end
...@@ -272,6 +272,10 @@ class Project < ActiveRecord::Base ...@@ -272,6 +272,10 @@ class Project < ActiveRecord::Base
query: "%#{query.try(:downcase)}%") query: "%#{query.try(:downcase)}%")
end end
def search_by_visibility(level)
where(visibility_level: Gitlab::VisibilityLevel.const_get(level.upcase))
end
def search_by_title(query) def search_by_title(query)
where('projects.archived = ?', false).where('LOWER(projects.name) LIKE :query', query: "%#{query.downcase}%") where('projects.archived = ?', false).where('LOWER(projects.name) LIKE :query', query: "%#{query.downcase}%")
end end
...@@ -468,12 +472,9 @@ class Project < ActiveRecord::Base ...@@ -468,12 +472,9 @@ class Project < ActiveRecord::Base
!external_issue_tracker !external_issue_tracker
end end
def external_issues_trackers
services.select(&:issue_tracker?).reject(&:default?)
end
def external_issue_tracker def external_issue_tracker
@external_issues_tracker ||= external_issues_trackers.find(&:activated?) @external_issue_tracker ||=
services.issue_trackers.active.without_defaults.first
end end
def can_have_issues_tracker_id? def can_have_issues_tracker_id?
......
...@@ -23,9 +23,7 @@ ...@@ -23,9 +23,7 @@
# List methods you need to implement to get your CI service # List methods you need to implement to get your CI service
# working with GitLab Merge Requests # working with GitLab Merge Requests
class CiService < Service class CiService < Service
def category default_value_for :category, 'ci'
:ci
end
def valid_token?(token) def valid_token?(token)
self.respond_to?(:token) && self.token.present? && self.token == token self.respond_to?(:token) && self.token.present? && self.token == token
......
...@@ -24,9 +24,7 @@ class GitlabIssueTrackerService < IssueTrackerService ...@@ -24,9 +24,7 @@ class GitlabIssueTrackerService < IssueTrackerService
prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url
def default? default_value_for :default, true
true
end
def to_param def to_param
'gitlab' 'gitlab'
......
...@@ -23,12 +23,10 @@ class IssueTrackerService < Service ...@@ -23,12 +23,10 @@ class IssueTrackerService < Service
validates :project_url, :issues_url, :new_issue_url, presence: true, if: :activated? validates :project_url, :issues_url, :new_issue_url, presence: true, if: :activated?
def category default_value_for :category, 'issue_tracker'
:issue_tracker
end
def default? def default?
false default
end end
def issue_url(iid) def issue_url(iid)
......
...@@ -43,6 +43,9 @@ class Service < ActiveRecord::Base ...@@ -43,6 +43,9 @@ class Service < ActiveRecord::Base
validates :project_id, presence: true, unless: Proc.new { |service| service.template? } validates :project_id, presence: true, unless: Proc.new { |service| service.template? }
scope :visible, -> { where.not(type: ['GitlabIssueTrackerService', 'GitlabCiService']) } scope :visible, -> { where.not(type: ['GitlabIssueTrackerService', 'GitlabCiService']) }
scope :issue_trackers, -> { where(category: 'issue_tracker') }
scope :active, -> { where(active: true) }
scope :without_defaults, -> { where(default: false) }
scope :push_hooks, -> { where(push_events: true, active: true) } scope :push_hooks, -> { where(push_events: true, active: true) }
scope :tag_push_hooks, -> { where(tag_push_events: true, active: true) } scope :tag_push_hooks, -> { where(tag_push_events: true, active: true) }
...@@ -51,6 +54,8 @@ class Service < ActiveRecord::Base ...@@ -51,6 +54,8 @@ class Service < ActiveRecord::Base
scope :note_hooks, -> { where(note_events: true, active: true) } scope :note_hooks, -> { where(note_events: true, active: true) }
scope :build_hooks, -> { where(build_events: true, active: true) } scope :build_hooks, -> { where(build_events: true, active: true) }
default_value_for :category, 'common'
def activated? def activated?
active active
end end
...@@ -60,7 +65,7 @@ class Service < ActiveRecord::Base ...@@ -60,7 +65,7 @@ class Service < ActiveRecord::Base
end end
def category def category
:common read_attribute(:category).to_sym
end end
def initialize_properties def initialize_properties
......
...@@ -664,7 +664,10 @@ class User < ActiveRecord::Base ...@@ -664,7 +664,10 @@ class User < ActiveRecord::Base
end end
def all_emails def all_emails
[self.email, *self.emails.map(&:email)] all_emails = []
all_emails << self.email unless self.temp_oauth_email?
all_emails.concat(self.emails.map(&:email))
all_emails
end end
def hook_attrs def hook_attrs
......
...@@ -376,10 +376,10 @@ class NotificationService ...@@ -376,10 +376,10 @@ class NotificationService
end end
def reassign_resource_email(target, project, current_user, method) def reassign_resource_email(target, project, current_user, method)
previous_assignee_id = previous_record(target, "assignee_id") previous_assignee_id = previous_record(target, 'assignee_id')
previous_assignee = User.find_by(id: previous_assignee_id) if previous_assignee_id previous_assignee = User.find_by(id: previous_assignee_id) if previous_assignee_id
recipients = build_recipients(target, project, current_user, [previous_assignee]) recipients = build_recipients(target, project, current_user, action: :reassign, previous_assignee: previous_assignee)
recipients.each do |recipient| recipients.each do |recipient|
mailer.send( mailer.send(
...@@ -400,22 +400,27 @@ class NotificationService ...@@ -400,22 +400,27 @@ class NotificationService
end end
end end
def build_recipients(target, project, current_user, extra_recipients = nil) def build_recipients(target, project, current_user, action: nil, previous_assignee: nil)
recipients = target.participants(current_user) recipients = target.participants(current_user)
recipients = recipients.concat(extra_recipients).compact.uniq if extra_recipients
recipients = add_project_watchers(recipients, project) recipients = add_project_watchers(recipients, project)
recipients = reject_mention_users(recipients, project) recipients = reject_mention_users(recipients, project)
recipients = reject_muted_users(recipients, project)
# Re-assign is considered as a mention of the new assignee so we add the
# new assignee to the list of recipients after we rejected users with
# the "on mention" notification level
if action == :reassign
recipients << previous_assignee if previous_assignee
recipients << target.assignee
end
recipients = reject_muted_users(recipients, project)
recipients = add_subscribed_users(recipients, target) recipients = add_subscribed_users(recipients, target)
recipients = reject_unsubscribed_users(recipients, target) recipients = reject_unsubscribed_users(recipients, target)
recipients.delete(current_user) recipients.delete(current_user)
recipients = recipients.uniq
recipients recipients.uniq
end end
def mailer def mailer
......
...@@ -95,7 +95,7 @@ module Projects ...@@ -95,7 +95,7 @@ module Projects
system_hook_service.execute_hooks_for(@project, :create) system_hook_service.execute_hooks_for(@project, :create)
unless @project.group unless @project.group
@project.team << [current_user, :master, current_user] @project.team << [current_user, :master]
end end
@project.import_start if @project.import? @project.import_start if @project.import?
......
...@@ -21,10 +21,10 @@ ...@@ -21,10 +21,10 @@
.form-group .form-group
= f.label :email, class: "control-label" = f.label :email, class: "control-label"
.col-sm-10 .col-sm-10
- if @user.ldap_user? - if @user.ldap_user? && @user.ldap_email?
= f.text_field :email, class: "form-control", required: true, readonly: true = f.text_field :email, class: "form-control", required: true, readonly: true
%span.help-block.light %span.help-block.light
Email is read-only for LDAP user Your email address was automatically set based on the LDAP server.
- else - else
- if @user.temp_oauth_email? - if @user.temp_oauth_email?
= f.text_field :email, class: "form-control", required: true, value: nil = f.text_field :email, class: "form-control", required: true, value: nil
......
%tr{ class: 'tree-item' } - path_to_directory = browse_namespace_project_build_artifacts_path(@project.namespace, @project, @build, path: directory.path)
%tr.tree-item{ 'data-link' => path_to_directory}
%td.tree-item-file-name %td.tree-item-file-name
= tree_icon('folder', '755', directory.name) = tree_icon('folder', '755', directory.name)
%span.str-truncated %span.str-truncated
= link_to directory.name, browse_namespace_project_build_artifacts_path(@project.namespace, @project, @build, path: directory.path) = link_to directory.name, path_to_directory
%td %td
%td %td
%tr{ class: 'tree-item' } - path_to_file = file_namespace_project_build_artifacts_path(@project.namespace, @project, @build, path: file.path)
%tr.tree-item{ 'data-link' => path_to_file }
%td.tree-item-file-name %td.tree-item-file-name
= tree_icon('file', '664', file.name) = tree_icon('file', '664', file.name)
%span.str-truncated %span.str-truncated
= file.name = link_to file.name, path_to_file
%td %td
= number_to_human_size(file.metadata[:size], precision: 2) = number_to_human_size(file.metadata[:size], precision: 2)
%td %td
= link_to file_namespace_project_build_artifacts_path(@project.namespace, @project, @build, path: file.path), = number_to_human_size(file.metadata[:zipped], precision: 2)
class: 'btn btn-xs btn-default artifact-download' do
= icon('download')
- page_title 'Artifacts', "#{@build.name} (##{@build.id})", 'Builds' - page_title 'Artifacts', "#{@build.name} (##{@build.id})", 'Builds'
= render 'projects/builds/header_title' = render 'projects/builds/header_title'
#tree-holder.tree-holder .top-block.gray-content-block.clearfix
.gray-content-block.top-block.clearfix
.pull-right .pull-right
= link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, @build), = link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, @build),
class: 'btn btn-default' do class: 'btn btn-default' do
= icon('download') = icon('download')
Download artifacts archive Download artifacts archive
%div.tree-content-holder .tree-holder
.table-holder %div.tree-content-holder
%table.table.tree-table.table-striped %table.table.tree-table
%thead %thead
%tr %tr
%th Name %th Name
%th Size %th Size
%th Download %th Compressed to
= render partial: 'tree_directory', collection: @entry.directories(parent: true), as: :directory = render partial: 'tree_directory', collection: @entry.directories(parent: true), as: :directory
= render partial: 'tree_file', collection: @entry.files, as: :file = render partial: 'tree_file', collection: @entry.files, as: :file
- if @entry.empty? - if @entry.empty?
.center Empty .center Empty
:javascript
$('.tree-holder').on('click', 'tr[data-link] a', function(e) {
e.stopImmediatePropagation();
});
$('.tree-holder').on('click', 'tr[data-link]', function(e) {
window.location = this.dataset.link;
});
...@@ -6,7 +6,12 @@ ...@@ -6,7 +6,12 @@
- if can?(current_user, :manage_builds, @project) - if can?(current_user, :manage_builds, @project)
.pull-left.hidden-xs .pull-left.hidden-xs
- if @all_builds.running_or_pending.any? - if @all_builds.running_or_pending.any?
= link_to 'Cancel running', cancel_all_namespace_project_builds_path(@project.namespace, @project), data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post = link_to 'Cancel running', cancel_all_namespace_project_builds_path(@project.namespace, @project),
data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
= link_to ci_lint_path, class: 'btn btn-default' do
= icon('wrench')
%span CI Lint
%ul.nav-links %ul.nav-links
%li{class: ('active' if @scope.nil?)} %li{class: ('active' if @scope.nil?)}
......
...@@ -10,6 +10,9 @@ ...@@ -10,6 +10,9 @@
.value .value
- if issuable.assignee - if issuable.assignee
%strong= link_to_member(@project, issuable.assignee, size: 24) %strong= link_to_member(@project, issuable.assignee, size: 24)
- if issuable.instance_of?(MergeRequest) && !issuable.can_be_merged_by?(issuable.assignee)
%a.pull-right.cannot-be-merged{href: '#', data: {toggle: 'tooltip'}, title: 'Not allowed to merge'}
= icon('exclamation-triangle')
- else - else
.light None .light None
......
...@@ -16,6 +16,9 @@ Rails.application.configure do ...@@ -16,6 +16,9 @@ Rails.application.configure do
# Print deprecation notices to the Rails logger # Print deprecation notices to the Rails logger
config.active_support.deprecation = :log config.active_support.deprecation = :log
# Raise an error on page load if there are pending migrations
config.active_record.migration_error = :page_load
# Only use best-standards-support built into browsers # Only use best-standards-support built into browsers
config.action_dispatch.best_standards_support = :builtin config.action_dispatch.best_standards_support = :builtin
......
...@@ -9,13 +9,8 @@ class Settings < Settingslogic ...@@ -9,13 +9,8 @@ class Settings < Settingslogic
gitlab.port.to_i == (gitlab.https ? 443 : 80) gitlab.port.to_i == (gitlab.https ? 443 : 80)
end end
# get host without www, thanks to http://stackoverflow.com/a/6674363/1233435 def host_without_www(url)
def get_host_without_www(url) host(url).sub('www.', '')
url = CGI.escape(url)
uri = URI.parse(url)
uri = URI.parse("http://#{url}") if uri.scheme.nil?
host = uri.host.downcase
host.start_with?('www.') ? host[4..-1] : host
end end
def build_gitlab_ci_url def build_gitlab_ci_url
...@@ -87,6 +82,17 @@ class Settings < Settingslogic ...@@ -87,6 +82,17 @@ class Settings < Settingslogic
custom_port custom_port
] ]
end end
# Extract the host part of the given +url+.
def host(url)
url = url.downcase
url = "http://#{url}" unless url.start_with?('http')
# Get rid of the path so that we don't even have to encode it
url_without_path = url.sub(%r{(https?://[^\/]+)/?.*}, '\1')
URI.parse(url_without_path).host
end
end end
end end
...@@ -228,7 +234,7 @@ Settings['gravatar'] ||= Settingslogic.new({}) ...@@ -228,7 +234,7 @@ Settings['gravatar'] ||= Settingslogic.new({})
Settings.gravatar['enabled'] = true if Settings.gravatar['enabled'].nil? Settings.gravatar['enabled'] = true if Settings.gravatar['enabled'].nil?
Settings.gravatar['plain_url'] ||= 'http://www.gravatar.com/avatar/%{hash}?s=%{size}&d=identicon' Settings.gravatar['plain_url'] ||= 'http://www.gravatar.com/avatar/%{hash}?s=%{size}&d=identicon'
Settings.gravatar['ssl_url'] ||= 'https://secure.gravatar.com/avatar/%{hash}?s=%{size}&d=identicon' Settings.gravatar['ssl_url'] ||= 'https://secure.gravatar.com/avatar/%{hash}?s=%{size}&d=identicon'
Settings.gravatar['host'] = Settings.get_host_without_www(Settings.gravatar['plain_url']) Settings.gravatar['host'] = Settings.host_without_www(Settings.gravatar['plain_url'])
# #
# Cron Jobs # Cron Jobs
......
class AddServicesCategory < ActiveRecord::Migration
def up
add_column :services, :category, :string, default: 'common', null: false
category = quote_column_name('category')
type = quote_column_name('type')
execute <<-EOF
UPDATE services
SET #{category} = 'issue_tracker'
WHERE #{type} IN (
'CustomIssueTrackerService',
'GitlabIssueTrackerService',
'IssueTrackerService',
'JiraService',
'RedmineService'
);
EOF
execute <<-EOF
UPDATE services
SET #{category} = 'ci'
WHERE #{type} IN (
'BambooService',
'BuildkiteService',
'CiService',
'DroneCiService',
'GitlabCiService',
'TeamcityService'
);
EOF
add_index :services, :category
end
def down
remove_column :services, :category
end
end
class AddServicesDefault < ActiveRecord::Migration
def up
add_column :services, :default, :boolean, default: false
default = quote_column_name('default')
type = quote_column_name('type')
execute <<-EOF
UPDATE services
SET #{default} = true
WHERE #{type} = 'GitlabIssueTrackerService'
EOF
add_index :services, :default
end
def down
remove_column :services, :default
end
end
class AddLdapEmailToUsers < ActiveRecord::Migration
def up
add_column :users, :ldap_email, :boolean, default: false, null: false
if Gitlab::Database.mysql?
execute %{
UPDATE users, identities
SET users.ldap_email = TRUE
WHERE identities.user_id = users.id
AND users.email LIKE 'temp-email-for-oauth%'
AND identities.provider LIKE 'ldap%'
AND identities.extern_uid IS NOT NULL
}
else
execute %{
UPDATE users
SET ldap_email = TRUE
FROM identities
WHERE identities.user_id = users.id
AND users.email LIKE 'temp-email-for-oauth%'
AND identities.provider LIKE 'ldap%'
AND identities.extern_uid IS NOT NULL
}
end
end
def down
remove_column :users, :ldap_email
end
end
...@@ -6,5 +6,6 @@ class LimitsToMysql < ActiveRecord::Migration ...@@ -6,5 +6,6 @@ class LimitsToMysql < ActiveRecord::Migration
change_column :merge_request_diffs, :st_diffs, :text, limit: 2147483647 change_column :merge_request_diffs, :st_diffs, :text, limit: 2147483647
change_column :snippets, :content, :text, limit: 2147483647 change_column :snippets, :content, :text, limit: 2147483647
change_column :notes, :st_diff, :text, limit: 2147483647 change_column :notes, :st_diff, :text, limit: 2147483647
change_column :events, :data, :text, limit: 2147483647
end end
end end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20160118155830) do ActiveRecord::Schema.define(version: 20160119145451) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -727,9 +727,9 @@ ActiveRecord::Schema.define(version: 20160118155830) do ...@@ -727,9 +727,9 @@ ActiveRecord::Schema.define(version: 20160118155830) do
t.string "type" t.string "type"
t.string "title" t.string "title"
t.integer "project_id" t.integer "project_id"
t.datetime "created_at" t.datetime "created_at", null: false
t.datetime "updated_at" t.datetime "updated_at", null: false
t.boolean "active", default: false, null: false t.boolean "active", null: false
t.text "properties" t.text "properties"
t.boolean "template", default: false t.boolean "template", default: false
t.boolean "push_events", default: true t.boolean "push_events", default: true
...@@ -738,9 +738,13 @@ ActiveRecord::Schema.define(version: 20160118155830) do ...@@ -738,9 +738,13 @@ ActiveRecord::Schema.define(version: 20160118155830) do
t.boolean "tag_push_events", default: true t.boolean "tag_push_events", default: true
t.boolean "note_events", default: true, null: false t.boolean "note_events", default: true, null: false
t.boolean "build_events", default: false, null: false t.boolean "build_events", default: false, null: false
t.string "category", default: "common", null: false
t.boolean "default", default: false
end end
add_index "services", ["category"], name: "index_services_on_category", using: :btree
add_index "services", ["created_at", "id"], name: "index_services_on_created_at_and_id", using: :btree add_index "services", ["created_at", "id"], name: "index_services_on_created_at_and_id", using: :btree
add_index "services", ["default"], name: "index_services_on_default", using: :btree
add_index "services", ["project_id"], name: "index_services_on_project_id", using: :btree add_index "services", ["project_id"], name: "index_services_on_project_id", using: :btree
add_index "services", ["template"], name: "index_services_on_template", using: :btree add_index "services", ["template"], name: "index_services_on_template", using: :btree
...@@ -852,6 +856,7 @@ ActiveRecord::Schema.define(version: 20160118155830) do ...@@ -852,6 +856,7 @@ ActiveRecord::Schema.define(version: 20160118155830) do
t.boolean "hide_project_limit", default: false t.boolean "hide_project_limit", default: false
t.string "unlock_token" t.string "unlock_token"
t.datetime "otp_grace_period_started_at" t.datetime "otp_grace_period_started_at"
t.boolean "ldap_email", default: false, null: false
end end
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
......
...@@ -33,6 +33,7 @@ GET /groups/:id/projects ...@@ -33,6 +33,7 @@ GET /groups/:id/projects
Parameters: Parameters:
- `archived` (optional) - if passed, limit by archived status - `archived` (optional) - if passed, limit by archived status
- `visibility` (optional) - if passed, limit by visibility `public`, `internal`, `private`
- `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at` - `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at`
- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc` - `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
- `search` (optional) - Return list of authorized projects according to a search criteria - `search` (optional) - Return list of authorized projects according to a search criteria
......
...@@ -29,6 +29,7 @@ GET /projects ...@@ -29,6 +29,7 @@ GET /projects
Parameters: Parameters:
- `archived` (optional) - if passed, limit by archived status - `archived` (optional) - if passed, limit by archived status
- `visibility` (optional) - if passed, limit by visibility `public`, `internal`, `private`
- `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at` - `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at`
- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc` - `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
- `search` (optional) - Return list of authorized projects according to a search criteria - `search` (optional) - Return list of authorized projects according to a search criteria
...@@ -152,6 +153,7 @@ GET /projects/owned ...@@ -152,6 +153,7 @@ GET /projects/owned
Parameters: Parameters:
- `archived` (optional) - if passed, limit by archived status - `archived` (optional) - if passed, limit by archived status
- `visibility` (optional) - if passed, limit by visibility `public`, `internal`, `private`
- `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at` - `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at`
- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc` - `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
- `search` (optional) - Return list of authorized projects according to a search criteria - `search` (optional) - Return list of authorized projects according to a search criteria
...@@ -167,6 +169,7 @@ GET /projects/starred ...@@ -167,6 +169,7 @@ GET /projects/starred
Parameters: Parameters:
- `archived` (optional) - if passed, limit by archived status - `archived` (optional) - if passed, limit by archived status
- `visibility` (optional) - if passed, limit by visibility `public`, `internal`, `private`
- `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at` - `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at`
- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc` - `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
- `search` (optional) - Return list of authorized projects according to a search criteria - `search` (optional) - Return list of authorized projects according to a search criteria
...@@ -182,6 +185,7 @@ GET /projects/all ...@@ -182,6 +185,7 @@ GET /projects/all
Parameters: Parameters:
- `archived` (optional) - if passed, limit by archived status - `archived` (optional) - if passed, limit by archived status
- `visibility` (optional) - if passed, limit by visibility `public`, `internal`, `private`
- `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at` - `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at`
- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc` - `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
- `search` (optional) - Return list of authorized projects according to a search criteria - `search` (optional) - Return list of authorized projects according to a search criteria
......
...@@ -52,3 +52,25 @@ We do not recommend using MySQL due to various issues. For example, case [(in)se ...@@ -52,3 +52,25 @@ We do not recommend using MySQL due to various issues. For example, case [(in)se
mysql> \q mysql> \q
# You are done installing the database and can go back to the rest of the installation. # You are done installing the database and can go back to the rest of the installation.
## MySQL strings limits
After installation or upgrade, remember to run the `add_limits_mysql` Rake task:
```
bundle exec rake add_limits_mysql
```
The `text` type in MySQL has a different size limit than the `text` type in
PostgreSQL. In MySQL `text` columns are limited to ~65kB, whereas in PostgreSQL
`text` columns are limited up to ~1GB!
The `add_limits_mysql` Rake task converts some important `text` columns in the
GitLab database to `longtext` columns, which can persist values of up to 4GB
(sometimes less if the value contains multibyte characters).
Details can be found in the [PostgreSQL][postgres-text-type] and
[MySQL][mysql-text-types] manuals.
[postgres-text-type]: http://www.postgresql.org/docs/9.1/static/datatype-character.html
[mysql-text-types]: http://dev.mysql.com/doc/refman/5.7/en/string-type-overview.html
...@@ -48,7 +48,7 @@ which should already be on your system from GitLab 8.1. ...@@ -48,7 +48,7 @@ which should already be on your system from GitLab 8.1.
```bash ```bash
cd /home/git/gitlab-workhorse cd /home/git/gitlab-workhorse
sudo -u git -H git fetch --all sudo -u git -H git fetch --all
sudo -u git -H git checkout 0.5.4 sudo -u git -H git checkout 0.6.0
sudo -u git -H make sudo -u git -H make
``` ```
......
...@@ -14,3 +14,4 @@ Depending on the installation method and your GitLab version, there are multiple ...@@ -14,3 +14,4 @@ Depending on the installation method and your GitLab version, there are multiple
## Miscellaneous ## Miscellaneous
- [MySQL to PostgreSQL](mysql_to_postgresql.md) guides you through migrating your database from MySQL to PostgreSQL. - [MySQL to PostgreSQL](mysql_to_postgresql.md) guides you through migrating your database from MySQL to PostgreSQL.
- [MySQL installation guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/database_mysql.md) contains additional information about configuring GitLab to work with a MySQL database.
...@@ -7,21 +7,21 @@ Feature: Project Builds Artifacts ...@@ -7,21 +7,21 @@ Feature: Project Builds Artifacts
Scenario: I download build artifacts Scenario: I download build artifacts
Given recent build has artifacts available Given recent build has artifacts available
When I visit recent build summary page When I visit recent build details page
And I click artifacts download button And I click artifacts download button
Then download of build artifacts archive starts Then download of build artifacts archive starts
Scenario: I browse build artifacts Scenario: I browse build artifacts
Given recent build has artifacts available Given recent build has artifacts available
And recent build has artifacts metadata available And recent build has artifacts metadata available
When I visit recent build summary page When I visit recent build details page
And I click artifacts browse button And I click artifacts browse button
Then I should see content of artifacts archive Then I should see content of artifacts archive
Scenario: I browse subdirectory of build artifacts Scenario: I browse subdirectory of build artifacts
Given recent build has artifacts available Given recent build has artifacts available
And recent build has artifacts metadata available And recent build has artifacts metadata available
When I visit recent build summary page When I visit recent build details page
And I click artifacts browse button And I click artifacts browse button
And I click link to subdirectory within build artifacts And I click link to subdirectory within build artifacts
Then I should see content of subdirectory within artifacts archive Then I should see content of subdirectory within artifacts archive
...@@ -30,7 +30,7 @@ Feature: Project Builds Artifacts ...@@ -30,7 +30,7 @@ Feature: Project Builds Artifacts
Given recent build has artifacts available Given recent build has artifacts available
And recent build has artifacts metadata available And recent build has artifacts metadata available
And recent build artifacts contain directory with UTF-8 characters And recent build artifacts contain directory with UTF-8 characters
When I visit recent build summary page When I visit recent build details page
And I click artifacts browse button And I click artifacts browse button
And I navigate to directory with UTF-8 characters in name And I navigate to directory with UTF-8 characters in name
Then I should see content of directory with UTF-8 characters in name Then I should see content of directory with UTF-8 characters in name
...@@ -39,7 +39,7 @@ Feature: Project Builds Artifacts ...@@ -39,7 +39,7 @@ Feature: Project Builds Artifacts
Given recent build has artifacts available Given recent build has artifacts available
And recent build has artifacts metadata available And recent build has artifacts metadata available
And recent build artifacts contain directory with invalid UTF-8 characters And recent build artifacts contain directory with invalid UTF-8 characters
When I visit recent build summary page When I visit recent build details page
And I click artifacts browse button And I click artifacts browse button
And I navigate to parent directory of directory with invalid name And I navigate to parent directory of directory with invalid name
Then I should not see directory with invalid name on the list Then I should not see directory with invalid name on the list
...@@ -47,7 +47,7 @@ Feature: Project Builds Artifacts ...@@ -47,7 +47,7 @@ Feature: Project Builds Artifacts
Scenario: I download a single file from build artifacts Scenario: I download a single file from build artifacts
Given recent build has artifacts available Given recent build has artifacts available
And recent build has artifacts metadata available And recent build has artifacts metadata available
When I visit recent build summary page When I visit recent build details page
And I click artifacts browse button And I click artifacts browse button
And I click download button for a file within build artifacts And I click a link to file within build artifacts
Then download of a file extracted from build artifacts should start Then download of a file extracted from build artifacts should start
...@@ -5,7 +5,11 @@ Feature: Project Builds Summary ...@@ -5,7 +5,11 @@ Feature: Project Builds Summary
And project has CI enabled And project has CI enabled
And project has a recent build And project has a recent build
Scenario: I browse build summary page Scenario: I browse build details page
When I visit recent build summary page When I visit recent build details page
Then I see summary for build Then I see details of a build
And I see build trace And I see build trace
Scenario: I browse project builds page
When I visit project builds page
Then I see button to CI Lint
...@@ -63,8 +63,8 @@ class Spinach::Features::ProjectBuildsArtifacts < Spinach::FeatureSteps ...@@ -63,8 +63,8 @@ class Spinach::Features::ProjectBuildsArtifacts < Spinach::FeatureSteps
end end
end end
step 'I click download button for a file within build artifacts' do step 'I click a link to file within build artifacts' do
page.within('.tree-table') { first('.artifact-download').click } page.within('.tree-table') { find_link('ci_artifacts.txt').click }
end end
step 'download of a file extracted from build artifacts should start' do step 'download of a file extracted from build artifacts should start' do
......
...@@ -4,11 +4,18 @@ class Spinach::Features::ProjectBuildsSummary < Spinach::FeatureSteps ...@@ -4,11 +4,18 @@ class Spinach::Features::ProjectBuildsSummary < Spinach::FeatureSteps
include SharedBuilds include SharedBuilds
include RepoHelpers include RepoHelpers
step 'I see summary for build' do step 'I see details of a build' do
expect(page).to have_content "Build ##{@build.id}" expect(page).to have_content "Build ##{@build.id}"
end end
step 'I see build trace' do step 'I see build trace' do
expect(page).to have_css '#build-trace' expect(page).to have_css '#build-trace'
end end
step 'I see button to CI Lint' do
page.within('.controls') do
ci_lint_tool_link = page.find_link('CI Lint')
expect(ci_lint_tool_link[:href]).to eq ci_lint_path
end
end
end end
...@@ -10,10 +10,14 @@ module SharedBuilds ...@@ -10,10 +10,14 @@ module SharedBuilds
@build = create :ci_build, commit: ci_commit @build = create :ci_build, commit: ci_commit
end end
step 'I visit recent build summary page' do step 'I visit recent build details page' do
visit namespace_project_build_path(@project.namespace, @project, @build) visit namespace_project_build_path(@project.namespace, @project, @build)
end end
step 'I visit project builds page' do
visit namespace_project_builds_path(@project.namespace, @project)
end
step 'recent build has artifacts available' do step 'recent build has artifacts available' do
artifacts = Rails.root + 'spec/fixtures/ci_build_artifacts.zip' artifacts = Rails.root + 'spec/fixtures/ci_build_artifacts.zip'
archive = fixture_file_upload(artifacts, 'application/zip') archive = fixture_file_upload(artifacts, 'application/zip')
......
...@@ -264,6 +264,10 @@ module API ...@@ -264,6 +264,10 @@ module API
projects = projects.search(params[:search]) projects = projects.search(params[:search])
end end
if params[:visibility].present?
projects = projects.search_by_visibility(params[:visibility])
end
projects.reorder(project_order_by => project_sort) projects.reorder(project_order_by => project_sort)
end end
......
...@@ -39,7 +39,6 @@ module Gitlab ...@@ -39,7 +39,6 @@ module Gitlab
end end
use_db && ActiveRecord::Base.connection.active? && use_db && ActiveRecord::Base.connection.active? &&
!ActiveRecord::Migrator.needs_migration? &&
ActiveRecord::Base.connection.table_exists?('application_settings') ActiveRecord::Base.connection.table_exists?('application_settings')
rescue ActiveRecord::NoDatabaseError rescue ActiveRecord::NoDatabaseError
......
...@@ -9,6 +9,7 @@ module Gitlab ...@@ -9,6 +9,7 @@ module Gitlab
delegate :namespace, :name_with_namespace, to: :project, prefix: :project delegate :namespace, :name_with_namespace, to: :project, prefix: :project
delegate :name, to: :author, prefix: :author delegate :name, to: :author, prefix: :author
delegate :username, to: :author, prefix: :author
def initialize(notify, project_id, recipient, opts = {}) def initialize(notify, project_id, recipient, opts = {})
raise ArgumentError, 'Missing options: author_id, ref, action' unless raise ArgumentError, 'Missing options: author_id, ref, action' unless
......
...@@ -30,28 +30,31 @@ module Gitlab ...@@ -30,28 +30,31 @@ module Gitlab
end end
def find_by_uid_and_provider def find_by_uid_and_provider
self.class.find_by_uid_and_provider( self.class.find_by_uid_and_provider(auth_hash.uid, auth_hash.provider)
auth_hash.uid, auth_hash.provider)
end end
def find_by_email def find_by_email
::User.find_by(email: auth_hash.email.downcase) ::User.find_by(email: auth_hash.email.downcase) if auth_hash.has_email?
end end
def update_user_attributes def update_user_attributes
return unless persisted? if persisted?
if auth_hash.has_email?
gl_user.skip_reconfirmation! gl_user.skip_reconfirmation!
gl_user.email = auth_hash.email gl_user.email = auth_hash.email
end
# find_or_initialize_by doesn't update `gl_user.identities`, and isn't autosaved. # find_or_initialize_by doesn't update `gl_user.identities`, and isn't autosaved.
identity = gl_user.identities.find { |identity| identity.provider == auth_hash.provider } identity = gl_user.identities.find { |identity| identity.provider == auth_hash.provider }
identity ||= gl_user.identities.build(provider: auth_hash.provider) identity ||= gl_user.identities.build(provider: auth_hash.provider)
# For a new user set extern_uid to the LDAP DN # For a new identity set extern_uid to the LDAP DN
# For an existing user with matching email but changed DN, update the DN. # For an existing identity with matching email but changed DN, update the DN.
# For an existing user with no change in DN, this line changes nothing. # For an existing identity with no change in DN, this line changes nothing.
identity.extern_uid = auth_hash.uid identity.extern_uid = auth_hash.uid
end
gl_user.ldap_email = auth_hash.has_email?
gl_user gl_user
end end
......
...@@ -32,6 +32,10 @@ module Gitlab ...@@ -32,6 +32,10 @@ module Gitlab
@password ||= Gitlab::Utils.force_utf8(Devise.friendly_token[0, 8].downcase) @password ||= Gitlab::Utils.force_utf8(Devise.friendly_token[0, 8].downcase)
end end
def has_email?
get_info(:email).present?
end
private private
def info def info
...@@ -46,8 +50,8 @@ module Gitlab ...@@ -46,8 +50,8 @@ module Gitlab
def username_and_email def username_and_email
@username_and_email ||= begin @username_and_email ||= begin
username = get_info(:username) || get_info(:nickname) username = get_info(:username).presence || get_info(:nickname).presence
email = get_info(:email) email = get_info(:email).presence
username ||= generate_username(email) if email username ||= generate_username(email) if email
email ||= generate_temporarily_email(username) if username email ||= generate_temporarily_email(username) if username
......
...@@ -135,13 +135,13 @@ module Gitlab ...@@ -135,13 +135,13 @@ module Gitlab
def user_attributes def user_attributes
# Give preference to LDAP for sensitive information when creating a linked account # Give preference to LDAP for sensitive information when creating a linked account
if creating_linked_ldap_user? if creating_linked_ldap_user?
username = ldap_person.username username = ldap_person.username.presence
email = ldap_person.email.first email = ldap_person.email.first.presence
else
username = auth_hash.username
email = auth_hash.email
end end
username ||= auth_hash.username
email ||= auth_hash.email
name = auth_hash.name name = auth_hash.name
name = ::Namespace.clean_path(username) if name.strip.empty? name = ::Namespace.clean_path(username) if name.strip.empty?
......
require_relative '../../config/initializers/1_settings'
describe Settings, lib: true do
describe '#host_without_www' do
context 'URL with protocol' do
it 'returns the host' do
expect(Settings.host_without_www('http://foo.com')).to eq 'foo.com'
expect(Settings.host_without_www('http://www.foo.com')).to eq 'foo.com'
expect(Settings.host_without_www('http://secure.foo.com')).to eq 'secure.foo.com'
expect(Settings.host_without_www('http://www.gravatar.com/avatar/%{hash}?s=%{size}&d=identicon')).to eq 'gravatar.com'
expect(Settings.host_without_www('https://foo.com')).to eq 'foo.com'
expect(Settings.host_without_www('https://www.foo.com')).to eq 'foo.com'
expect(Settings.host_without_www('https://secure.foo.com')).to eq 'secure.foo.com'
expect(Settings.host_without_www('https://secure.gravatar.com/avatar/%{hash}?s=%{size}&d=identicon')).to eq 'secure.gravatar.com'
end
end
context 'URL without protocol' do
it 'returns the host' do
expect(Settings.host_without_www('foo.com')).to eq 'foo.com'
expect(Settings.host_without_www('www.foo.com')).to eq 'foo.com'
expect(Settings.host_without_www('secure.foo.com')).to eq 'secure.foo.com'
expect(Settings.host_without_www('www.gravatar.com/avatar/%{hash}?s=%{size}&d=identicon')).to eq 'gravatar.com'
end
context 'URL with user/port' do
it 'returns the host' do
expect(Settings.host_without_www('bob:pass@foo.com:8080')).to eq 'foo.com'
expect(Settings.host_without_www('bob:pass@www.foo.com:8080')).to eq 'foo.com'
expect(Settings.host_without_www('bob:pass@secure.foo.com:8080')).to eq 'secure.foo.com'
expect(Settings.host_without_www('bob:pass@www.gravatar.com:8080/avatar/%{hash}?s=%{size}&d=identicon')).to eq 'gravatar.com'
expect(Settings.host_without_www('http://bob:pass@foo.com:8080')).to eq 'foo.com'
expect(Settings.host_without_www('http://bob:pass@www.foo.com:8080')).to eq 'foo.com'
expect(Settings.host_without_www('http://bob:pass@secure.foo.com:8080')).to eq 'secure.foo.com'
expect(Settings.host_without_www('http://bob:pass@www.gravatar.com:8080/avatar/%{hash}?s=%{size}&d=identicon')).to eq 'gravatar.com'
end
end
end
end
end
...@@ -37,7 +37,7 @@ describe Gitlab::LDAP::User, lib: true do ...@@ -37,7 +37,7 @@ describe Gitlab::LDAP::User, lib: true do
end end
it "dont marks existing ldap user as changed" do it "dont marks existing ldap user as changed" do
create(:omniauth_user, email: 'john@example.com', extern_uid: 'my-uid', provider: 'ldapmain') create(:omniauth_user, email: 'john@example.com', extern_uid: 'my-uid', provider: 'ldapmain', ldap_email: true)
expect(ldap_user.changed?).to be_falsey expect(ldap_user.changed?).to be_falsey
end end
end end
...@@ -110,6 +110,32 @@ describe Gitlab::LDAP::User, lib: true do ...@@ -110,6 +110,32 @@ describe Gitlab::LDAP::User, lib: true do
end end
end end
describe 'updating email' do
context "when LDAP sets an email" do
it "has a real email" do
expect(ldap_user.gl_user.email).to eq(info[:email])
end
it "has ldap_email set to true" do
expect(ldap_user.gl_user.ldap_email?).to be(true)
end
end
context "when LDAP doesn't set an email" do
before do
info.delete(:email)
end
it "has a temp email" do
expect(ldap_user.gl_user.temp_oauth_email?).to be(true)
end
it "has ldap_email set to false" do
expect(ldap_user.gl_user.ldap_email?).to be(false)
end
end
end
describe 'blocking' do describe 'blocking' do
def configure_block(value) def configure_block(value)
allow_any_instance_of(Gitlab::LDAP::Config). allow_any_instance_of(Gitlab::LDAP::Config).
......
...@@ -40,14 +40,38 @@ describe Notify do ...@@ -40,14 +40,38 @@ describe Notify do
end end
end end
shared_examples 'an email with X-GitLab headers containing project details' do
it 'has X-GitLab-Project* headers' do
is_expected.to have_header 'X-GitLab-Project', /#{project.name}/
is_expected.to have_header 'X-GitLab-Project-Id', /#{project.id}/
is_expected.to have_header 'X-GitLab-Project-Path', /#{project.path_with_namespace}/
end
end
shared_examples 'an email with X-GitLab headers containing build details' do
it 'has X-GitLab-Build* headers' do
is_expected.to have_header 'X-GitLab-Build-Id', /#{build.id}/
is_expected.to have_header 'X-GitLab-Build-Ref', /#{build.ref}/
end
end
shared_examples 'an email that contains a header with author username' do
it 'has X-GitLab-Author header containing author\'s username' do
is_expected.to have_header 'X-GitLab-Author', user.username
end
end
shared_examples 'an email starting a new thread' do |message_id_prefix| shared_examples 'an email starting a new thread' do |message_id_prefix|
include_examples 'an email with X-GitLab headers containing project details'
it 'has a discussion identifier' do it 'has a discussion identifier' do
is_expected.to have_header 'Message-ID', /<#{message_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/ is_expected.to have_header 'Message-ID', /<#{message_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/
is_expected.to have_header 'X-GitLab-Project', /#{project.name}/
end end
end end
shared_examples 'an answer to an existing thread' do |thread_id_prefix| shared_examples 'an answer to an existing thread' do |thread_id_prefix|
include_examples 'an email with X-GitLab headers containing project details'
it 'has a subject that begins with Re: ' do it 'has a subject that begins with Re: ' do
is_expected.to have_subject /^Re: / is_expected.to have_subject /^Re: /
end end
...@@ -56,7 +80,6 @@ describe Notify do ...@@ -56,7 +80,6 @@ describe Notify do
is_expected.to have_header 'Message-ID', /<(.*)@#{Gitlab.config.gitlab.host}>/ is_expected.to have_header 'Message-ID', /<(.*)@#{Gitlab.config.gitlab.host}>/
is_expected.to have_header 'References', /<#{thread_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/ is_expected.to have_header 'References', /<#{thread_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/
is_expected.to have_header 'In-Reply-To', /<#{thread_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/ is_expected.to have_header 'In-Reply-To', /<#{thread_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/
is_expected.to have_header 'X-GitLab-Project', /#{project.name}/
end end
end end
...@@ -656,6 +679,8 @@ describe Notify do ...@@ -656,6 +679,8 @@ describe Notify do
it_behaves_like 'it should not have Gmail Actions links' it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link" it_behaves_like "a user cannot unsubscribe through footer link"
it_behaves_like 'an email with X-GitLab headers containing project details'
it_behaves_like 'an email that contains a header with author username'
it 'is sent as the author' do it 'is sent as the author' do
sender = subject.header[:from].addrs[0] sender = subject.header[:from].addrs[0]
...@@ -685,6 +710,8 @@ describe Notify do ...@@ -685,6 +710,8 @@ describe Notify do
it_behaves_like 'it should not have Gmail Actions links' it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link" it_behaves_like "a user cannot unsubscribe through footer link"
it_behaves_like 'an email with X-GitLab headers containing project details'
it_behaves_like 'an email that contains a header with author username'
it 'is sent as the author' do it 'is sent as the author' do
sender = subject.header[:from].addrs[0] sender = subject.header[:from].addrs[0]
...@@ -713,6 +740,8 @@ describe Notify do ...@@ -713,6 +740,8 @@ describe Notify do
it_behaves_like 'it should not have Gmail Actions links' it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link" it_behaves_like "a user cannot unsubscribe through footer link"
it_behaves_like 'an email with X-GitLab headers containing project details'
it_behaves_like 'an email that contains a header with author username'
it 'is sent as the author' do it 'is sent as the author' do
sender = subject.header[:from].addrs[0] sender = subject.header[:from].addrs[0]
...@@ -737,6 +766,8 @@ describe Notify do ...@@ -737,6 +766,8 @@ describe Notify do
it_behaves_like 'it should not have Gmail Actions links' it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link" it_behaves_like "a user cannot unsubscribe through footer link"
it_behaves_like 'an email with X-GitLab headers containing project details'
it_behaves_like 'an email that contains a header with author username'
it 'is sent as the author' do it 'is sent as the author' do
sender = subject.header[:from].addrs[0] sender = subject.header[:from].addrs[0]
...@@ -765,6 +796,8 @@ describe Notify do ...@@ -765,6 +796,8 @@ describe Notify do
it_behaves_like 'it should not have Gmail Actions links' it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link" it_behaves_like "a user cannot unsubscribe through footer link"
it_behaves_like 'an email with X-GitLab headers containing project details'
it_behaves_like 'an email that contains a header with author username'
it 'is sent as the author' do it 'is sent as the author' do
sender = subject.header[:from].addrs[0] sender = subject.header[:from].addrs[0]
...@@ -871,6 +904,8 @@ describe Notify do ...@@ -871,6 +904,8 @@ describe Notify do
it_behaves_like 'it should show Gmail Actions View Commit link' it_behaves_like 'it should show Gmail Actions View Commit link'
it_behaves_like "a user cannot unsubscribe through footer link" it_behaves_like "a user cannot unsubscribe through footer link"
it_behaves_like 'an email with X-GitLab headers containing project details'
it_behaves_like 'an email that contains a header with author username'
it 'is sent as the author' do it 'is sent as the author' do
sender = subject.header[:from].addrs[0] sender = subject.header[:from].addrs[0]
...@@ -904,6 +939,15 @@ describe Notify do ...@@ -904,6 +939,15 @@ describe Notify do
subject { Notify.build_success_email(build.id, 'wow@example.com') } subject { Notify.build_success_email(build.id, 'wow@example.com') }
it_behaves_like 'an email with X-GitLab headers containing build details'
it_behaves_like 'an email with X-GitLab headers containing project details' do
let(:project) { build.project }
end
it 'has header indicating build status' do
is_expected.to have_header 'X-GitLab-Build-Status', 'success'
end
it 'has the correct subject' do it 'has the correct subject' do
should have_subject /Build success for/ should have_subject /Build success for/
end end
...@@ -918,6 +962,15 @@ describe Notify do ...@@ -918,6 +962,15 @@ describe Notify do
subject { Notify.build_fail_email(build.id, 'wow@example.com') } subject { Notify.build_fail_email(build.id, 'wow@example.com') }
it_behaves_like 'an email with X-GitLab headers containing build details'
it_behaves_like 'an email with X-GitLab headers containing project details' do
let(:project) { build.project }
end
it 'has header indicating build status' do
is_expected.to have_header 'X-GitLab-Build-Status', 'failed'
end
it 'has the correct subject' do it 'has the correct subject' do
should have_subject /Build failed for/ should have_subject /Build failed for/
end end
......
...@@ -90,6 +90,29 @@ describe API::API, api: true do ...@@ -90,6 +90,29 @@ describe API::API, api: true do
end end
end end
context 'and using the visibility filter' do
it 'should filter based on private visibility param' do
get api('/projects', user), { visibility: 'private' }
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(user.namespace.projects.where(visibility_level: Gitlab::VisibilityLevel::PRIVATE).count)
end
it 'should filter based on internal visibility param' do
get api('/projects', user), { visibility: 'internal' }
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(user.namespace.projects.where(visibility_level: Gitlab::VisibilityLevel::INTERNAL).count)
end
it 'should filter based on public visibility param' do
get api('/projects', user), { visibility: 'public' }
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(user.namespace.projects.where(visibility_level: Gitlab::VisibilityLevel::PUBLIC).count)
end
end
context 'and using sorting' do context 'and using sorting' do
before do before do
project2 project2
......
...@@ -227,7 +227,7 @@ describe NotificationService, services: true do ...@@ -227,7 +227,7 @@ describe NotificationService, services: true do
end end
describe :reassigned_issue do describe :reassigned_issue do
it 'should email new assignee' do it 'emails new assignee' do
notification.reassigned_issue(issue, @u_disabled) notification.reassigned_issue(issue, @u_disabled)
should_email(issue.assignee) should_email(issue.assignee)
...@@ -238,6 +238,62 @@ describe NotificationService, services: true do ...@@ -238,6 +238,62 @@ describe NotificationService, services: true do
should_not_email(@u_participating) should_not_email(@u_participating)
should_not_email(@u_disabled) should_not_email(@u_disabled)
end end
it 'emails previous assignee even if he has the "on mention" notif level' do
issue.update_attribute(:assignee, @u_mentioned)
issue.update_attributes(assignee: @u_watcher)
notification.reassigned_issue(issue, @u_disabled)
should_email(@u_mentioned)
should_email(@u_watcher)
should_email(@u_participant_mentioned)
should_email(@subscriber)
should_not_email(@unsubscriber)
should_not_email(@u_participating)
should_not_email(@u_disabled)
end
it 'emails new assignee even if he has the "on mention" notif level' do
issue.update_attributes(assignee: @u_mentioned)
notification.reassigned_issue(issue, @u_disabled)
expect(issue.assignee).to be @u_mentioned
should_email(issue.assignee)
should_email(@u_watcher)
should_email(@u_participant_mentioned)
should_email(@subscriber)
should_not_email(@unsubscriber)
should_not_email(@u_participating)
should_not_email(@u_disabled)
end
it 'emails new assignee' do
issue.update_attribute(:assignee, @u_mentioned)
notification.reassigned_issue(issue, @u_disabled)
expect(issue.assignee).to be @u_mentioned
should_email(issue.assignee)
should_email(@u_watcher)
should_email(@u_participant_mentioned)
should_email(@subscriber)
should_not_email(@unsubscriber)
should_not_email(@u_participating)
should_not_email(@u_disabled)
end
it 'does not email new assignee if they are the current user' do
issue.update_attribute(:assignee, @u_mentioned)
notification.reassigned_issue(issue, @u_mentioned)
expect(issue.assignee).to be @u_mentioned
should_email(@u_watcher)
should_email(@u_participant_mentioned)
should_email(@subscriber)
should_not_email(issue.assignee)
should_not_email(@unsubscriber)
should_not_email(@u_participating)
should_not_email(@u_disabled)
end
end end
describe :close_issue do describe :close_issue do
......
...@@ -32,6 +32,7 @@ describe Projects::CreateService, services: true do ...@@ -32,6 +32,7 @@ describe Projects::CreateService, services: true do
it { expect(@project).to be_valid } it { expect(@project).to be_valid }
it { expect(@project.owner).to eq(@user) } it { expect(@project.owner).to eq(@user) }
it { expect(@project.team.masters).to include(@user) }
it { expect(@project.namespace).to eq(@user.namespace) } it { expect(@project.namespace).to eq(@user.namespace) }
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment