Commit c23ca0b0 authored by Douwe Maan's avatar Douwe Maan

Merge branch 'master' into issue_3945

parents 701513dc 481644ca
......@@ -8,7 +8,7 @@ before_script:
- touch log/application.log
- touch log/test.log
- 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:
script:
......@@ -118,7 +118,7 @@ flay:
- mysql
bundler:audit:
script:
script:
- "bundle exec bundle-audit update"
- "bundle exec bundle-audit check"
tags:
......
Please view this file on the master branch, on stable branches it's out of date.
v 8.5.0 (unreleased)
- Remove gray background from layout in UI
- Add "visibility" flag to GET /projects api endpoint
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
- 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
- Autocomplete data is now always loaded, instead of when focusing a comment text area (Yorick Peterse)
- Improved performance of finding issues for an entire group (Yorick Peterse)
- Added custom application performance measuring system powered by InfluxDB (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
- Added custom application performance measuring system powered by InfluxDB
- Bump fog to 1.36.0 (Stan Hu)
- Add user's last used IP addresses to admin page (Stan Hu)
- Add housekeeping function to project settings page
......@@ -19,8 +22,11 @@ v 8.4.0 (unreleased)
- Fix missing date of month in network graph when commits span a month (Stan Hu)
- Expire view caches when application settings change (e.g. Gravatar disabled) (Stan Hu)
- Don't notify users twice if they are both project watchers and subscribers (Stan Hu)
- Remove gray background from layout in UI
- Fix signup for OAuth providers that don't provide a name
- Implement new UI for group page
- Implement search inside emoji picker
- Let the CI runner know about builds that this build depends on
- Add API support for looking up a user by username (Stan Hu)
- Add project permissions to all project API endpoints (Stan Hu)
- Link to milestone in "Milestone changed" system note
......@@ -56,10 +62,18 @@ v 8.4.0 (unreleased)
- Allow broadcast messages to be edited
- Autosize Markdown textareas
- Import GitHub wiki into GitLab
- Add reporters ability to download and browse build artifacts (Andrew Johnson)
- 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
- Use gitlab-workhorse 0.5.4 (fixes API routing bug)
- Add build artifacts browser
v 8.3.3
- Preserve CE behavior with JIRA integration by only calling API if URL is set
......@@ -107,6 +121,7 @@ v 8.3.0
- Fix: Assignee selector is empty when 'Unassigned' is selected (Jose Corcuera)
- Add rake tasks for git repository maintainance (Zeger-Jan van de Weg)
- 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)
- Recognize issue/MR/snippet/commit links as references
- Backport JIRA features from EE to CE
......@@ -168,7 +183,6 @@ v 8.2.2
- Fix Error 500 when viewing user's personal projects from admin page (Stan Hu)
- Fix: Raw private snippets access workflow
- 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
- Make current user the first user in assignee dropdown in issues detail page (Stan Hu)
- Fix: duplicate email notifications on issue comments
......
......@@ -18,7 +18,7 @@ gem "mysql2", '~> 0.3.16', group: :mysql
gem "pg", '~> 0.18.2', group: :postgres
# Authentication libraries
gem 'devise', '~> 3.5.3'
gem 'devise', '~> 3.5.4'
gem 'devise-async', '~> 0.9.0'
gem 'doorkeeper', '~> 2.2.0'
gem 'omniauth', '~> 1.2.2'
......
......@@ -157,7 +157,7 @@ GEM
activerecord (>= 3.2.0, < 5.0)
descendants_tracker (0.0.4)
thread_safe (~> 0.3, >= 0.3.1)
devise (3.5.3)
devise (3.5.4)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 3.2.6, < 5)
......@@ -614,7 +614,7 @@ GEM
thor (>= 0.18.1, < 2.0)
rainbow (2.0.0)
raindrops (0.15.0)
rake (10.4.2)
rake (10.5.0)
raphael-rails (2.1.2)
rb-fsevent (0.9.6)
rb-inotify (0.9.5)
......@@ -648,8 +648,8 @@ GEM
request_store (1.2.1)
rerun (0.11.0)
listen (~> 3.0)
responders (2.1.0)
railties (>= 4.2.0, < 5)
responders (2.1.1)
railties (>= 4.2.0, < 5.1)
rest-client (1.8.0)
http-cookie (>= 1.0.2, < 2.0)
mime-types (>= 1.16, < 3.0)
......@@ -911,7 +911,7 @@ DEPENDENCIES
d3_rails (~> 3.5.0)
database_cleaner (~> 1.4.0)
default_value_for (~> 3.0.0)
devise (~> 3.5.3)
devise (~> 3.5.4)
devise-async (~> 0.9.0)
devise-two-factor (~> 2.0.0)
diffy (~> 3.0.3)
......
......@@ -2,6 +2,6 @@
# https://gitlab.com/gitlab-org/omnibus-gitlab or the init scripts in
# lib/support/init.d, which call scripts in bin/ .
#
web: bundle exec unicorn_rails -p ${PORT:="3000"} -E ${RAILS_ENV:="development"} -c ${UNICORN_CONFIG:="config/unicorn.rb"}
worker: bundle exec sidekiq -q post_receive -q mailers -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q runner -q common -q default
web: RAILS_ENV=development bin/web start_foreground
worker: RAILS_ENV=development bin/background_jobs start_foreground
# mail_room: bundle exec mail_room -q -c config/mail_room.yml
8.4.0.pre
8.5.0-pre
......@@ -5,7 +5,7 @@ class @AwardsHandler
event.preventDefault()
$(".emoji-menu").show()
$("html").click ->
$("html").on 'click', (event) ->
if !$(event.target).closest(".emoji-menu").length
if $(".emoji-menu").is(":visible")
$(".emoji-menu").hide()
......@@ -19,7 +19,7 @@ class @AwardsHandler
@addAwardToEmojiBar(emoji)
$(".emoji-menu").hide()
addAwardToEmojiBar: (emoji) ->
@addEmojiToFrequentlyUsedList(emoji)
......@@ -66,9 +66,14 @@ class @AwardsHandler
addMeToAuthorList: (emoji) ->
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")
award_block.attr("title", authors.join(", "))
if authors.length == 1
award_block.attr("title", "me")
else
award_block.attr("title", authors.join(", "))
@resetTooltip(award_block)
resetTooltip: (award) ->
......@@ -78,7 +83,7 @@ class @AwardsHandler
setTimeout (->
award.tooltip()
), 200
createEmoji: (emoji) ->
emojiCssClass = @resolveNameToCssClass(emoji)
......
......@@ -6,11 +6,25 @@ class @Issue
constructor: ->
# Prevent duplicate event bindings
@disableTaskList()
@fixAffixScroll()
if $('a.btn-close').length
@initTaskList()
@initIssueBtnEventListeners()
fixAffixScroll: ->
fixAffix = ->
$discussion = $('.issuable-discussion')
$sidebar = $('.issuable-sidebar')
if $sidebar.hasClass('no-affix')
$sidebar.removeClass(['affix-top','affix'])
discussionHeight = $discussion.height()
sidebarHeight = $sidebar.height()
if sidebarHeight > discussionHeight
$discussion.height(sidebarHeight + 50)
$sidebar.addClass('no-affix')
$(window).on('resize', fixAffix)
fixAffix()
initTaskList: ->
$('.detail-page-description .js-task-list-container').taskList('enable')
$(document).on 'tasklist:changed', '.detail-page-description .js-task-list-container', @updateTaskList
......
......@@ -15,6 +15,8 @@ class @MergeRequest
this.$('.show-all-commits').on 'click', =>
this.showAllCommits()
@fixAffixScroll();
@initTabs()
# Prevent duplicate event bindings
......@@ -28,6 +30,20 @@ class @MergeRequest
$: (selector) ->
this.$el.find(selector)
fixAffixScroll: ->
fixAffix = ->
$discussion = $('.issuable-discussion')
$sidebar = $('.issuable-sidebar')
if $sidebar.hasClass('no-affix')
$sidebar.removeClass(['affix-top','affix'])
discussionHeight = $discussion.height()
sidebarHeight = $sidebar.height()
if sidebarHeight > discussionHeight
$discussion.height(sidebarHeight + 50)
$sidebar.addClass('no-affix')
$(window).on('resize', fixAffix)
fixAffix()
initTabs: ->
if @opts.action != 'new'
# `MergeRequests#new` has no tab-persisting or lazy-loading behavior
......
......@@ -320,6 +320,7 @@ class @Notes
form.show()
textarea = form.find("textarea")
textarea.focus()
autosize(textarea)
# HACK (rspeicher/DouweM): Work around a Chrome 43 bug(?).
# The textarea has the correct value, Chrome just won't show it unless we
......@@ -355,7 +356,7 @@ class @Notes
$('.note[id="' + note_id + '"]').each ->
note = $(this)
notes = note.closest(".notes")
count = notes.closest(".notes_holder").find(".discussion-notes-count")
count = notes.closest(".issuable-details").find(".notes-tab .badge")
# check if this is the last note for this line
if notes.find(".note").length is 1
......@@ -365,9 +366,10 @@ class @Notes
# for diff lines
notes.closest("tr").remove()
else
# update notes count
count.get(0).lastChild.nodeValue = " #{notes.children().length - 1}"
# update notes count
oldNum = parseInt(count.text())
count.text(oldNum - 1)
note.remove()
......
......@@ -6,7 +6,7 @@ class @Star
$starIcon = $this.find('i')
toggleStar = (isStarred) ->
$this.parent().find('span.count').text data.star_count
$this.parent().find('.star-count').text data.star_count
if isStarred
$starSpan.removeClass('starred').text 'Star'
$starIcon.removeClass('fa-star').addClass 'fa-star-o'
......@@ -19,4 +19,4 @@ class @Star
return
).on 'ajax:error', (e, xhr, status, error) ->
new Flash('Star toggle failed. Try again later.', 'alert')
return
\ No newline at end of file
return
......@@ -75,6 +75,8 @@ hr {
@include str-truncated;
}
.item-title { font-weight: 600; }
/** FLASH message **/
.author_link {
color: $gl-link-color;
......
.filter-item {
margin-right: 15px;
margin-right: 6px;
}
@media (min-width: 800px) {
......
......@@ -3,23 +3,39 @@
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 300;
src: local('Source Sans Pro Light'), local('SourceSansPro-Light'), font-url('SourceSansPro-Light.ttf.woff');
src:
local('Source Sans Pro Light'),
local('SourceSansPro-Light'),
font-url('SourceSansPro-Light.ttf.woff2') format('woff2'),
font-url('SourceSansPro-Light.ttf.woff') format('woff');
}
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 400;
src: local('Source Sans Pro'), local('SourceSansPro-Regular'), font-url('SourceSansPro-Regular.ttf.woff');
src:
local('Source Sans Pro'),
local('SourceSansPro-Regular'),
font-url('SourceSansPro-Regular.ttf.woff2') format('woff2'),
font-url('SourceSansPro-Regular.ttf.woff') format('woff');
}
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 600;
src: local('Source Sans Pro Semibold'), local('SourceSansPro-Semibold'), font-url('SourceSansPro-Semibold.ttf.woff');
src:
local('Source Sans Pro Semibold'),
local('SourceSansPro-Semibold'),
font-url('SourceSansPro-Semibold.ttf.woff2') format('woff2'),
font-url('SourceSansPro-Semibold.ttf.woff') format('woff');
}
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 700;
src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), font-url('SourceSansPro-Bold.ttf.woff');
src:
local('Source Sans Pro Bold'),
local('SourceSansPro-Bold'),
font-url('SourceSansPro-Bold.ttf.woff2') format('woff2'),
font-url('SourceSansPro-Bold.ttf.woff') format('woff');
}
......@@ -26,6 +26,7 @@ $gl-vert-padding: 6px;
$gl-padding-top:10px;
$gl-avatar-size: 46px;
$secondary-text: #7f8fa4;
$error-exclamation-point: #E62958;
/*
* Color schema
......
......@@ -2,10 +2,6 @@
display: block;
}
.commit-row-title .commit-title {
font-weight: 600;
}
.commit-author, .commit-committer{
display: block;
color: #999;
......
......@@ -36,6 +36,10 @@ li.commit {
line-height: 20px;
margin-bottom: 2px;
.btn-clipboard {
margin-top: -1px;
}
.notes_count {
float: right;
margin-right: 10px;
......
......@@ -66,6 +66,7 @@
width: 100%;
font-family: $monospace_font;
border: none;
border-collapse: separate;
margin: 0px;
padding: 0px;
.line_holder td {
......
......@@ -11,8 +11,3 @@
height: 42px;
}
}
.content-list .group-name {
font-weight: 600;
color: #4c4e54;
}
......@@ -20,6 +20,11 @@
position: fixed;
top: 70px;
margin-right: 35px;
&.no-affix {
position: relative;
top: 0;
}
}
}
}
......
......@@ -92,12 +92,12 @@
}
.project-repo-buttons {
margin-top: 12px;
margin-top: 20px;
margin-bottom: 0px;
.count-buttons {
display: block;
margin-bottom: 12px;
margin-bottom: 20px;
}
.clone-row {
......@@ -163,7 +163,7 @@
line-height: 13px;
padding: $gl-vert-padding $gl-padding;
letter-spacing: .4px;
padding: 10px;
padding: 10px 14px;
text-align: center;
vertical-align: middle;
touch-action: manipulation;
......@@ -558,3 +558,9 @@ pre.light-well {
width: 101%;
}
}
.cannot-be-merged,
.cannot-be-merged:hover {
color: #E62958;
margin-top: 2px;
}
......@@ -3,6 +3,10 @@
border-bottom: 1px solid #DDD;
padding-bottom: 15px;
margin-bottom: 15px;
.term {
height: 22px;
}
}
}
......
......@@ -2,6 +2,7 @@ class AbuseReportsController < ApplicationController
def new
@abuse_report = AbuseReport.new
@abuse_report.user_id = params[:user_id]
@ref_url = params.fetch(:ref_url, '')
end
def create
......
......@@ -116,7 +116,7 @@ class ApplicationController < ActionController::Base
# localhost/group/project
#
if id =~ /\.git\Z/
redirect_to request.original_url.gsub(/\.git\Z/, '') and return
redirect_to request.original_url.gsub(/\.git\/?\Z/, '') and return
end
project_path = "#{namespace}/#{id}"
......
......@@ -8,7 +8,7 @@ class Projects::ArtifactsController < Projects::ApplicationController
end
unless artifacts_file.exists?
return not_found!
return render_404
end
send_file artifacts_file.path, disposition: 'attachment'
......
......@@ -42,7 +42,7 @@ class Projects::BuildsController < Projects::ApplicationController
def retry
unless @build.retryable?
return page_404
return render_404
end
build = Ci::Build.retry(@build)
......@@ -72,7 +72,7 @@ class Projects::BuildsController < Projects::ApplicationController
def authorize_manage_builds!
unless can?(current_user, :manage_builds, project)
return page_404
return render_404
end
end
end
......@@ -80,7 +80,7 @@ class Projects::CommitController < Projects::ApplicationController
def authorize_manage_builds!
unless can?(current_user, :manage_builds, project)
return page_404
return render_404
end
end
end
......@@ -49,7 +49,7 @@ class Projects::IssuesController < Projects::ApplicationController
assignee_id: ""
)
@issue = @project.issues.new(issue_params)
@issue = @noteable = @project.issues.new(issue_params)
respond_with(@issue)
end
......
......@@ -90,6 +90,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def new
params[:merge_request] ||= ActionController::Parameters.new(source_project: @project)
@merge_request = MergeRequests::BuildService.new(project, current_user, merge_request_params).execute
@noteable = @merge_request
@target_branches = if @merge_request.target_project
@merge_request.target_project.repository.branch_names
......
......@@ -25,7 +25,7 @@ class Projects::SnippetsController < Projects::ApplicationController
end
def new
@snippet = @project.snippets.build
@snippet = @noteable = @project.snippets.build
end
def create
......
......@@ -81,7 +81,8 @@ class IssuableFinder
elsif current_user && params[:authorized_only].presence && !current_user_related?
@projects = current_user.authorized_projects.reorder(nil)
else
@projects = ProjectsFinder.new.execute(current_user).reorder(nil)
@projects = ProjectsFinder.new.execute(current_user, group: group).
reorder(nil)
end
end
......
......@@ -3,13 +3,26 @@ module Emails
def build_fail_email(build_id, to)
@build = Ci::Build.find(build_id)
@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))
end
def build_success_email(build_id, to)
@build = Ci::Build.find(build_id)
@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))
end
private
def add_build_headers
headers['X-GitLab-Build-Id'] = @build.id
headers['X-GitLab-Build-Ref'] = @build.ref
end
end
end
......@@ -43,7 +43,7 @@ module Emails
@current_user = @created_by = User.find(created_by_id)
@access_level = access_level
@invite_email = invite_email
@target_url = namespace_project_url(@project.namespace, @project)
mail(to: @created_by.notification_email,
......@@ -65,6 +65,10 @@ module Emails
# used in notify layout
@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?),
reply_to: @message.reply_to,
......
......@@ -100,12 +100,7 @@ class Notify < BaseMailer
end
def mail_thread(model, headers = {})
if @project
headers['X-GitLab-Project'] = @project.name
headers['X-GitLab-Project-Id'] = @project.id
headers['X-GitLab-Project-Path'] = @project.path_with_namespace
end
add_project_headers
headers["X-GitLab-#{model.class.name}-ID"] = model.id
headers['X-GitLab-Reply-Key'] = reply_key
......@@ -152,4 +147,12 @@ class Notify < BaseMailer
def reply_key
@reply_key ||= SentNotification.reply_key
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
......@@ -160,6 +160,7 @@ class Ability
@project_report_rules ||= project_guest_rules + [
:create_commit_status,
:read_commit_statuses,
:read_build_artifacts,
:download_code,
:fork_project,
:create_project_snippet,
......@@ -175,7 +176,6 @@ class Ability
:create_merge_request,
:create_wiki,
:manage_builds,
:read_build_artifacts,
:push_code
]
end
......
......@@ -17,7 +17,7 @@ class AbuseReport < ActiveRecord::Base
validates :reporter, presence: true
validates :user, presence: true
validates :message, presence: true
validates :user_id, uniqueness: true
validates :user_id, uniqueness: { message: 'has already been reported' }
def remove_user
user.block
......
......@@ -128,6 +128,14 @@ module Ci
!self.commit.latest_builds_for_ref(self.ref).include?(self)
end
def depends_on_builds
# Get builds of the same type
latest_builds = self.commit.builds.similar(self).latest
# Return builds from previous stages
latest_builds.where('stage_idx < ?', stage_idx)
end
def trace_html
html = Ci::Ansi2html::convert(trace) if trace.present?
html || ''
......
......@@ -3,11 +3,11 @@
# Table name: web_hooks
#
# id :integer not null, primary key
# url :string(255)
# url :string(2000)
# project_id :integer
# created_at :datetime
# updated_at :datetime
# type :string(255) default("ProjectHook")
# type :string default("ProjectHook")
# service_id :integer
# push_events :boolean default(TRUE), not null
# issues_events :boolean default(FALSE), not null
......
......@@ -3,11 +3,11 @@
# Table name: web_hooks
#
# id :integer not null, primary key
# url :string(255)
# url :string(2000)
# project_id :integer
# created_at :datetime
# updated_at :datetime
# type :string(255) default("ProjectHook")
# type :string default("ProjectHook")
# service_id :integer
# push_events :boolean default(TRUE), not null
# issues_events :boolean default(FALSE), not null
......
......@@ -3,11 +3,11 @@
# Table name: web_hooks
#
# id :integer not null, primary key
# url :string(255)
# url :string(2000)
# project_id :integer
# created_at :datetime
# updated_at :datetime
# type :string(255) default("ProjectHook")
# type :string default("ProjectHook")
# service_id :integer
# push_events :boolean default(TRUE), not null
# issues_events :boolean default(FALSE), not null
......
......@@ -3,11 +3,11 @@
# Table name: web_hooks
#
# id :integer not null, primary key
# url :string(255)
# url :string(2000)
# project_id :integer
# created_at :datetime
# updated_at :datetime
# type :string(255) default("ProjectHook")
# type :string default("ProjectHook")
# service_id :integer
# push_events :boolean default(TRUE), not null
# issues_events :boolean default(FALSE), not null
......
......@@ -272,6 +272,10 @@ class Project < ActiveRecord::Base
query: "%#{query.try(:downcase)}%")
end
def search_by_visibility(level)
where(visibility_level: Gitlab::VisibilityLevel.const_get(level.upcase))
end
def search_by_title(query)
where('projects.archived = ?', false).where('LOWER(projects.name) LIKE :query', query: "%#{query.downcase}%")
end
......@@ -468,12 +472,9 @@ class Project < ActiveRecord::Base
!external_issue_tracker
end
def external_issues_trackers
services.select(&:issue_tracker?).reject(&:default?)
end
def external_issue_tracker
@external_issues_tracker ||= external_issues_trackers.find(&:activated?)
@external_issue_tracker ||=
services.issue_trackers.active.without_defaults.first
end
def can_have_issues_tracker_id?
......
......@@ -23,14 +23,12 @@
# List methods you need to implement to get your CI service
# working with GitLab Merge Requests
class CiService < Service
def category
:ci
end
default_value_for :category, 'ci'
def valid_token?(token)
self.respond_to?(:token) && self.token.present? && self.token == token
end
def supported_events
%w(push)
end
......
......@@ -24,9 +24,7 @@ class GitlabIssueTrackerService < IssueTrackerService
prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url
def default?
true
end
default_value_for :default, true
def to_param
'gitlab'
......
......@@ -23,12 +23,10 @@ class IssueTrackerService < Service
validates :project_url, :issues_url, :new_issue_url, presence: true, if: :activated?
def category
:issue_tracker
end
default_value_for :category, 'issue_tracker'
def default?
false
default
end
def issue_url(iid)
......
......@@ -43,6 +43,9 @@ class Service < ActiveRecord::Base
validates :project_id, presence: true, unless: Proc.new { |service| service.template? }
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 :tag_push_hooks, -> { where(tag_push_events: true, active: true) }
......@@ -51,6 +54,8 @@ class Service < ActiveRecord::Base
scope :note_hooks, -> { where(note_events: true, active: true) }
scope :build_hooks, -> { where(build_events: true, active: true) }
default_value_for :category, 'common'
def activated?
active
end
......@@ -60,7 +65,7 @@ class Service < ActiveRecord::Base
end
def category
:common
read_attribute(:category).to_sym
end
def initialize_properties
......@@ -153,7 +158,7 @@ class Service < ActiveRecord::Base
# Returns a hash of the properties that have been assigned a new value since last save,
# indicating their original values (attr => original value).
# ActiveRecord does not provide a mechanism to track changes in serialized keys,
# ActiveRecord does not provide a mechanism to track changes in serialized keys,
# so we need a specific implementation for service properties.
# This allows to track changes to properties set with the accessor methods,
# but not direct manipulation of properties hash.
......@@ -164,7 +169,7 @@ class Service < ActiveRecord::Base
def reset_updated_properties
@updated_properties = nil
end
def async_execute(data)
return unless supported_events.include?(data[:object_kind])
......
......@@ -664,7 +664,10 @@ class User < ActiveRecord::Base
end
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
def hook_attrs
......
......@@ -376,10 +376,10 @@ class NotificationService
end
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
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|
mailer.send(
......@@ -400,22 +400,27 @@ class NotificationService
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 = recipients.concat(extra_recipients).compact.uniq if extra_recipients
recipients = add_project_watchers(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 = reject_unsubscribed_users(recipients, target)
recipients.delete(current_user)
recipients = recipients.uniq
recipients
recipients.uniq
end
def mailer
......
......@@ -95,7 +95,7 @@ module Projects
system_hook_service.execute_hooks_for(@project, :create)
unless @project.group
@project.team << [current_user, :master, current_user]
@project.team << [current_user, :master]
end
@project.import_start if @project.import?
......
......@@ -32,6 +32,10 @@ class ArtifactUploader < CarrierWave::Uploader::Base
self.class.storage == CarrierWave::Storage::File
end
def filename
file.try(:filename)
end
def exists?
file.try(:exists?)
end
......
......@@ -16,7 +16,7 @@
.form-group
= f.label :message, class: 'control-label'
.col-sm-10
= f.text_area :message, class: "form-control js-quick-submit", rows: 2, required: true
= f.text_area :message, class: "form-control js-quick-submit", rows: 2, required: true, value: sanitize(@ref_url)
.help-block
Explain the problem with this user. If appropriate, provide a link to the relevant issue or comment.
......
......@@ -21,10 +21,10 @@
.form-group
= f.label :email, class: "control-label"
.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
%span.help-block.light
Email is read-only for LDAP user
Your email address was automatically set based on the LDAP server.
- else
- if @user.temp_oauth_email?
= 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
= tree_icon('folder', '755', directory.name)
%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
%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
= tree_icon('file', '664', file.name)
%span.str-truncated
= file.name
= link_to file.name, path_to_file
%td
= number_to_human_size(file.metadata[:size], precision: 2)
%td
= link_to file_namespace_project_build_artifacts_path(@project.namespace, @project, @build, path: file.path),
class: 'btn btn-xs btn-default artifact-download' do
= icon('download')
= number_to_human_size(file.metadata[:zipped], precision: 2)
- page_title 'Artifacts', "#{@build.name} (##{@build.id})", 'Builds'
= render 'projects/builds/header_title'
#tree-holder.tree-holder
.gray-content-block.top-block.clearfix
.pull-right
= link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, @build),
class: 'btn btn-default' do
= icon('download')
Download artifacts archive
.top-block.gray-content-block.clearfix
.pull-right
= link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, @build),
class: 'btn btn-default' do
= icon('download')
Download artifacts archive
%div.tree-content-holder
.table-holder
%table.table.tree-table.table-striped
.tree-holder
%div.tree-content-holder
%table.table.tree-table
%thead
%tr
%th Name
%th Size
%th Download
%th Compressed to
= render partial: 'tree_directory', collection: @entry.directories(parent: true), as: :directory
= render partial: 'tree_file', collection: @entry.files, as: :file
- if @entry.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;
});
- commit = @repository.commit(branch.target)
- bar_graph_width_factor = @max_commits > 0 ? 100.0/@max_commits : 0
- diverging_commit_counts = @repository.diverging_commit_counts(branch)
- diverging_commit_counts = @repository.diverging_commit_counts(branch)
- number_commits_behind = diverging_commit_counts[:behind]
- number_commits_ahead = diverging_commit_counts[:ahead]
%li(class="js-branch-#{branch.name}")
%div
= link_to namespace_project_tree_path(@project.namespace, @project, branch.name) do
.branch-name.str-truncated= branch.name
%span.item-title.str-truncated= branch.name
&nbsp;
- if branch.name == @repository.root_ref
%span.label.label-primary default
......
......@@ -6,7 +6,12 @@
- if can?(current_user, :manage_builds, @project)
.pull-left.hidden-xs
- 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
%li{class: ('active' if @scope.nil?)}
......
......@@ -11,7 +11,7 @@
= cache(cache_key) do
%li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" }
.commit-row-title
.commit-title.str-truncated
%span.item-title.str-truncated
= link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message"
- if commit.description?
%a.text-expander.js-toggle-button ...
......
......@@ -3,7 +3,7 @@
%li
%div
= link_to namespace_project_tag_path(@project.namespace, @project, tag.name) do
.tag-name
%span.item-title
= icon('tag')
= tag.name
- if tag.message.present?
......
......@@ -17,8 +17,8 @@
.pull-right
= link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: 'btn btn-remove remove-row grouped has_tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{@tag.name}' tag cannot be undone. Are you sure?" } do
%i.fa.fa-trash-o
.tag-name.title
= @tag.name
.title
%span.item-title= @tag.name
- if @tag.message.present?
%span.light
&nbsp;
......
......@@ -10,7 +10,8 @@
%i.fa.fa-sign-out
= image_tag group_icon(group), class: "avatar s46 hidden-xs"
= link_to group.name, group, class: 'group-name'
= link_to group, class: 'group-name' do
%span.item-title= group.name
- if group_member
as
......@@ -18,4 +19,3 @@
%div.light
#{pluralize(group.projects.count, "project")}, #{pluralize(group.users.count, "user")}
......@@ -10,6 +10,9 @@
.value
- if issuable.assignee
%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
.light None
......
......@@ -8,7 +8,7 @@
%tr
%th= t('sherlock.time')
%th= t('sherlock.query')
%td
%th
%tbody
- @transaction.sorted_queries.each do |query|
%tr
......
......@@ -20,7 +20,7 @@
data: { toggle: 'tooltip', placement: 'left', container: 'body' }}
= icon('exclamation-circle')
- else
= link_to new_abuse_report_path(user_id: @user.id), class: 'btn btn-gray',
= link_to new_abuse_report_path(user_id: @user.id, ref_url: request.referrer), class: 'btn btn-gray',
title: 'Report abuse', data: {toggle: 'tooltip', placement: 'left', container: 'body'} do
= icon('exclamation-circle')
- if current_user
......
......@@ -27,17 +27,17 @@ restart()
stop
fi
killall
start_sidekiq -d -L $sidekiq_logfile
start_sidekiq -d -L $sidekiq_logfile >> $sidekiq_logfile 2>&1
}
start_no_deamonize()
{
start_sidekiq
start_sidekiq >> $sidekiq_logfile 2>&1
}
start_sidekiq()
{
bundle exec sidekiq -q post_receive -q mailers -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q runner -q common -q default -e $RAILS_ENV -P $sidekiq_pidfile $@ >> $sidekiq_logfile 2>&1
bundle exec sidekiq -q post_receive -q mailers -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q runner -q common -q default -e $RAILS_ENV -P $sidekiq_pidfile "$@"
}
load_ok()
......@@ -66,6 +66,9 @@ case "$1" in
start_no_deamonize)
start_no_deamonize
;;
start_foreground)
start_sidekiq
;;
restart)
restart
;;
......
......@@ -5,6 +5,7 @@ app_root=$(pwd)
unicorn_pidfile="$app_root/tmp/pids/unicorn.pid"
unicorn_config="$app_root/config/unicorn.rb"
unicorn_cmd="bundle exec unicorn_rails -c $unicorn_config -E $RAILS_ENV"
get_unicorn_pid()
{
......@@ -18,7 +19,12 @@ get_unicorn_pid()
start()
{
bundle exec unicorn_rails -D -c $unicorn_config -E $RAILS_ENV
$unicorn_cmd -D
}
start_foreground()
{
$unicorn_cmd
}
stop()
......@@ -37,6 +43,9 @@ case "$1" in
start)
start
;;
start_foreground)
start_foreground
;;
stop)
stop
;;
......
......@@ -16,6 +16,9 @@ Rails.application.configure do
# Print deprecation notices to the Rails logger
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
config.action_dispatch.best_standards_support = :builtin
......@@ -34,6 +37,8 @@ Rails.application.configure do
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
# Open sent mails in browser
config.action_mailer.delivery_method = :letter_opener
# Don't make a mess when bootstrapping a development environment
config.action_mailer.perform_deliveries = (ENV['BOOTSTRAP'] != '1')
config.eager_load = false
end
......@@ -9,13 +9,8 @@ class Settings < Settingslogic
gitlab.port.to_i == (gitlab.https ? 443 : 80)
end
# get host without www, thanks to http://stackoverflow.com/a/6674363/1233435
def get_host_without_www(url)
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
def host_without_www(url)
host(url).sub('www.', '')
end
def build_gitlab_ci_url
......@@ -87,6 +82,17 @@ class Settings < Settingslogic
custom_port
]
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
......@@ -228,7 +234,7 @@ Settings['gravatar'] ||= Settingslogic.new({})
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['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
......
......@@ -55,6 +55,12 @@ if Gitlab::Metrics.enabled?
config.instrument_methods(const) if const.is_a?(Module)
end
Dir[Rails.root.join('app', 'finders', '*.rb')].each do |path|
const = File.basename(path, '.rb').camelize.constantize
config.instrument_instance_methods(const)
end
end
GC::Profiler.enable
......
class RaiseHookUrlLimit < ActiveRecord::Migration
def change
change_column :web_hooks, :url, :string, limit: 2000
end
end
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
change_column :merge_request_diffs, :st_diffs, :text, limit: 2147483647
change_column :snippets, :content, :text, limit: 2147483647
change_column :notes, :st_diff, :text, limit: 2147483647
change_column :events, :data, :text, limit: 2147483647
end
end
......@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20160113111034) do
ActiveRecord::Schema.define(version: 20160119145451) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
......@@ -725,20 +725,24 @@ ActiveRecord::Schema.define(version: 20160113111034) do
t.string "type"
t.string "title"
t.integer "project_id"
t.datetime "created_at"
t.datetime "updated_at"
t.boolean "active", default: false, null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.boolean "active", null: false
t.text "properties"
t.boolean "template", default: false
t.boolean "push_events", default: true
t.boolean "issues_events", default: true
t.boolean "merge_requests_events", default: true
t.boolean "tag_push_events", default: true
t.boolean "note_events", default: true, null: false
t.boolean "build_events", default: false, null: false
end
t.boolean "template", default: false
t.boolean "push_events", default: true
t.boolean "issues_events", default: true
t.boolean "merge_requests_events", default: true
t.boolean "tag_push_events", default: true
t.boolean "note_events", default: true, null: false
t.boolean "build_events", default: false, null: false
t.string "category", default: "common", null: false
t.boolean "default", default: false
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", ["default"], name: "index_services_on_default", 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
......@@ -850,6 +854,7 @@ ActiveRecord::Schema.define(version: 20160113111034) do
t.boolean "hide_project_limit", default: false
t.string "unlock_token"
t.datetime "otp_grace_period_started_at"
t.boolean "ldap_email", default: false, null: false
end
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
......@@ -874,19 +879,19 @@ ActiveRecord::Schema.define(version: 20160113111034) do
add_index "users_star_projects", ["user_id"], name: "index_users_star_projects_on_user_id", using: :btree
create_table "web_hooks", force: :cascade do |t|
t.string "url"
t.string "url", limit: 2000
t.integer "project_id"
t.datetime "created_at"
t.datetime "updated_at"
t.string "type", default: "ProjectHook"
t.string "type", default: "ProjectHook"
t.integer "service_id"
t.boolean "push_events", default: true, null: false
t.boolean "issues_events", default: false, null: false
t.boolean "merge_requests_events", default: false, null: false
t.boolean "tag_push_events", default: false
t.boolean "note_events", default: false, null: false
t.boolean "enable_ssl_verification", default: true
t.boolean "build_events", default: false, null: false
t.boolean "push_events", default: true, null: false
t.boolean "issues_events", default: false, null: false
t.boolean "merge_requests_events", default: false, null: false
t.boolean "tag_push_events", default: false
t.boolean "note_events", default: false, null: false
t.boolean "enable_ssl_verification", default: true
t.boolean "build_events", default: false, null: false
end
add_index "web_hooks", ["created_at", "id"], name: "index_web_hooks_on_created_at_and_id", using: :btree
......
......@@ -151,7 +151,53 @@ When listing resources you can pass the following parameters:
- `page` (default: `1`) - page number
- `per_page` (default: `20`, max: `100`) - number of items to list per page
[Link headers](http://www.w3.org/wiki/LinkHeader) are send back with each response. These have `rel` prev/next/first/last and contain the relevant URL. Please use these instead of generating your own URLs.
### Pagination Link header
[Link headers](http://www.w3.org/wiki/LinkHeader) are sent back with each
response. They have `rel` set to prev/next/first/last and contain the relevant
URL. Please use these links instead of generating your own URLs.
In the cURL example below, we limit the output to 3 items per page (`per_page=3`)
and we request the second page (`page=2`) of [comments](notes.md) of the issue
with ID `8` which belongs to the project with ID `8`:
```bash
curl -I -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/8/issues/8/notes?per_page=3&page=2
```
The response will then be:
```
HTTP/1.1 200 OK
Cache-Control: no-cache
Content-Length: 1103
Content-Type: application/json
Date: Mon, 18 Jan 2016 09:43:18 GMT
Link: <https://gitlab.example.com/api/v3/projects/8/issues/8/notes?page=1&per_page=3>; rel="prev", <https://gitlab.example.com/api/v3/projects/8/issues/8/notes?page=3&per_page=3>; rel="next", <https://gitlab.example.com/api/v3/projects/8/issues/8/notes?page=1&per_page=3>; rel="first", <https://gitlab.example.com/api/v3/projects/8/issues/8/notes?page=3&per_page=3>; rel="last"
Status: 200 OK
Vary: Origin
X-Next-Page: 3
X-Page: 2
X-Per-Page: 3
X-Prev-Page: 1
X-Request-Id: 732ad4ee-9870-4866-a199-a9db0cde3c86
X-Runtime: 0.108688
X-Total: 8
X-Total-Pages: 3
```
### Other pagination headers
Additional pagination headers are also sent back.
| Header | Description |
| ------ | ----------- |
| `X-Total` | The total number of items |
| `X-Total-Pages` | The total number of pages |
| `X-Per-Page` | The number of items per page |
| `X-Page` | The index of the current page (starting at 1) |
| `X-Next-Page` | The index of the next page |
| `X-Prev-Page` | The index of the previous page |
## id vs iid
......
......@@ -33,6 +33,7 @@ GET /groups/:id/projects
Parameters:
- `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`
- `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
......
......@@ -29,6 +29,7 @@ GET /projects
Parameters:
- `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`
- `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
......@@ -152,6 +153,7 @@ GET /projects/owned
Parameters:
- `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`
- `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
......@@ -167,6 +169,7 @@ GET /projects/starred
Parameters:
- `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`
- `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
......@@ -182,6 +185,7 @@ GET /projects/all
Parameters:
- `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`
- `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
......
......@@ -18,18 +18,62 @@ Returns:
```json
{
"id" : 79,
"commands" : "",
"path" : "",
"ref" : "",
"sha" : "",
"project_id" : 6,
"repo_url" : "git@demo.gitlab.com:gitlab/gitlab-shell.git",
"before_sha" : ""
"id": 48584,
"ref": "0.1.1",
"tag": true,
"sha": "d63117656af6ff57d99e50cc270f854691f335ad",
"status": "success",
"name": "pages",
"token": "9dd60b4f1a439d1765357446c1084c",
"stage": "test",
"project_id": 479,
"project_name": "test",
"commands": "echo commands",
"repo_url": "http://gitlab-ci-token:token@gitlab.example/group/test.git",
"before_sha": "0000000000000000000000000000000000000000",
"allow_git_fetch": false,
"options": {
"image": "docker:image",
"artifacts": {
"paths": [
"public"
]
},
"cache": {
"paths": [
"vendor"
]
}
},
"timeout": 3600,
"variables": [
{
"key": "CI_BUILD_TAG",
"value": "0.1.1",
"public": true
}
],
"depends_on_builds": [
{
"id": 48584,
"ref": "0.1.1",
"tag": true,
"sha": "d63117656af6ff57d99e50cc270f854691f335ad",
"status": "success",
"name": "build",
"token": "9dd60b4f1a439d1765357446c1084c",
"stage": "build",
"project_id": 479,
"project_name": "test",
"artifacts_file": {
"filename": "artifacts.zip",
"size": 0
}
}
]
}
```
### Update details of an existing build
PUT /ci/builds/:id
......
......@@ -8,7 +8,7 @@ We do not recommend using MySQL due to various issues. For example, case [(in)se
# Install the database packages
sudo apt-get install -y mysql-server mysql-client libmysqlclient-dev
# Ensure you have MySQL version 5.5.14 or later
mysql --version
......@@ -31,7 +31,7 @@ We do not recommend using MySQL due to various issues. For example, case [(in)se
# Ensure you can use the InnoDB engine which is necessary to support long indexes
# If this fails, check your MySQL config files (e.g. `/etc/mysql/*.cnf`, `/etc/mysql/conf.d/*`) for the setting "innodb = off"
mysql> SET storage_engine=INNODB;
# Create the GitLab production database
mysql> CREATE DATABASE IF NOT EXISTS `gitlabhq_production` DEFAULT CHARACTER SET `utf8` COLLATE `utf8_unicode_ci`;
......@@ -52,3 +52,25 @@ We do not recommend using MySQL due to various issues. For example, case [(in)se
mysql> \q
# 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
......@@ -78,6 +78,18 @@ On the sign in page there should now be a SAML button below the regular sign in
## Troubleshooting
If you see a "500 error" in GitLab when you are redirected back from the SAML sign in page, this likely indicates that GitLab could not get the email address for the SAML user.
If you see a "500 error" in GitLab when you are redirected back from the SAML sign in page,
this likely indicates that GitLab could not get the email address for the SAML user.
Make sure the IdP provides a claim containing the user's email address, using claim name 'email' or 'mail'. The email will be used to automatically generate the GitLab username.
\ No newline at end of file
Make sure the IdP provides a claim containing the user's email address, using claim name
'email' or 'mail'. The email will be used to automatically generate the GitLab username.
If after signing in into your SAML server you are redirected back to the sign in page and
no error is displayed, check your `production.log` file. It will most likely contain the
message `Can't verify CSRF token authenticity`. This means that there is an error during
the SAML request, but this error never reaches GitLab due to the CSRF check.
To bypass this you can add `skip_before_action :verify_authenticity_token` to the
`omniauth_callbacks_controller.rb` file. This will allow the error to hit GitLab,
where it can then be seen in the usual logs, or as a flash message in the login
screen.
\ No newline at end of file
......@@ -48,7 +48,7 @@ which should already be on your system from GitLab 8.1.
```bash
cd /home/git/gitlab-workhorse
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
```
......
......@@ -14,3 +14,4 @@ Depending on the installation method and your GitLab version, there are multiple
## Miscellaneous
- [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.
# Import your project from GitHub to GitLab
It takes just a couple of steps to import your existing GitHub projects to GitLab. Keep in mind that it is possible only if
GitHub support is enabled on your GitLab instance. You can read more about GitHub support [here](http://doc.gitlab.com/ce/integration/github.html)
_**Note:** In order to enable the GitHub import setting, you should first
enable the [GitHub integration][gh-import] in your GitLab instance._
If you want to import from a GitHub Enterprise instance, you need to use GitLab Enterprise; please see the [EE docs for the GitHub integration](http://doc.gitlab.com/ee/integration/github.html).
At its current state, GitHub importer can import:
* Sign in to GitLab.com and go to your dashboard.
* To get to the importer page, you need to go to the "New project" page.
- the repository description (introduced in GitLab 7.7)
- the git repository data (introduced in GitLab 7.7)
- the issues (introduced in GitLab 7.7)
- the pull requests (introduced in GitLab 8.4)
- the wiki pages (introduced in GitLab 8.4)
![New project page](github_importer/new_project_page.png)
It is not yet possible to import your labels, milestones and cross-repository
pull requests (those from forks). We are working on improving this in the near
future.
* Click on the "Import project from GitHub" link and you will be redirected to GitHub for permission to access your projects. After accepting, you'll be automatically redirected to the importer.
The importer page is visible when you [create a new project][new-project].
Click on the **GitHub** link and you will be redirected to GitHub for
permission to access your projects. After accepting, you'll be automatically
redirected to the importer.
![Importer page](github_importer/importer.png)
![New project page on GitLab](img/import_projects_from_github_new_project_page.png)
* To import a project, you can simple click "Add". The importer will import your repository, issues, and pull requests. Once the importer is done, a new GitLab project will be created with your imported data.
---
### Note
When you import your projects from GitHub, it is not possible to keep your labels, milestones, and cross-repository pull requests. We are working on improving this in the near future.
While at the GitHub importer page, you can see the import statuses of your
GitHub projects. Those that are being imported will show a _started_ status,
those already imported will be green, whereas those that are not yet imported
have an **Import** button on the right side of the table. If you want, you can
import all your GitHub projects in one go by hitting **Import all projects**
in the upper left corner.
![GitHub importer page](img/import_projects_from_github_importer.png)
---
The importer will create any new namespaces if they don't exist or in the
case the namespace is taken, the project will be imported on the user's
namespace.
[gh-import]: ../../integration/github.md "GitHub integration"
[ee-gh]: http://doc.gitlab.com/ee/integration/github.html "GitHub integration for GitLab EE"
[new-project]: ../../gitlab-basics/create-project.md "How to create a new project in GitLab"
# Protected branches
Permission in GitLab are fundamentally defined around the idea of having read or write permission to the repository and branches.
Permissions in GitLab are fundamentally defined around the idea of having read or write permission to the repository and branches.
To prevent people from messing with history or pushing code without review, we've created protected branches.
......
Feature: Project Builds
Feature: Project Builds Artifacts
Background:
Given I sign in as a user
And I own a project
And CI is enabled
And I have recent build for my project
Scenario: I browse build summary page
When I visit recent build summary page
Then I see summary for build
And I see build trace
And project has CI enabled
And project has a recent build
Scenario: I download build artifacts
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
Then download of build artifacts archive starts
Scenario: I browse build artifacts
Given recent build has artifacts 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
Then I should see content of artifacts archive
Scenario: I browse subdirectory of build artifacts
Given recent build has artifacts 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 link to subdirectory within build artifacts
Then I should see content of subdirectory within artifacts archive
......@@ -35,7 +30,7 @@ Feature: Project Builds
Given recent build has artifacts available
And recent build has artifacts metadata available
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 navigate to directory with UTF-8 characters in name
Then I should see content of directory with UTF-8 characters in name
......@@ -44,7 +39,7 @@ Feature: Project Builds
Given recent build has artifacts available
And recent build has artifacts metadata available
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 navigate to parent directory of directory with invalid name
Then I should not see directory with invalid name on the list
......@@ -52,7 +47,7 @@ Feature: Project Builds
Scenario: I download a single file from build artifacts
Given recent build has artifacts 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 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
Feature: Project Builds Permissions
Background:
Given I sign in as a user
And project exists in some group namespace
And project has CI enabled
And project has a recent build
Scenario: I try to download build artifacts as guest
Given I am member of a project with a guest role
And recent build has artifacts available
When I access artifacts download page
Then page status code should be 404
Scenario: I try to download build artifacts as reporter
Given I am member of a project with a reporter role
And recent build has artifacts available
When I access artifacts download page
Then download of build artifacts archive starts
Feature: Project Builds Summary
Background:
Given I sign in as a user
And I own a project
And project has CI enabled
And project has a recent build
Scenario: I browse build details page
When I visit recent build details page
Then I see details of a build
And I see build trace
Scenario: I browse project builds page
When I visit project builds page
Then I see button to CI Lint
class Spinach::Features::ProjectBuilds < Spinach::FeatureSteps
class Spinach::Features::ProjectBuildsArtifacts < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedBuilds
include RepoHelpers
step 'I see summary for build' do
expect(page).to have_content "Build ##{@build.id}"
end
step 'I see build trace' do
expect(page).to have_css '#build-trace'
end
step 'I click artifacts download button' do
page.within('.artifacts') { click_link 'Download' }
end
step 'download of build artifacts archive starts' do
expect(page.response_headers['Content-Type']).to eq 'application/zip'
expect(page.response_headers['Content-Transfer-Encoding']).to eq 'binary'
end
step 'I click artifacts browse button' do
page.within('.artifacts') { click_link 'Browse' }
end
......@@ -76,8 +63,8 @@ class Spinach::Features::ProjectBuilds < Spinach::FeatureSteps
end
end
step 'I click download button for a file within build artifacts' do
page.within('.tree-table') { first('.artifact-download').click }
step 'I click a link to file within build artifacts' do
page.within('.tree-table') { find_link('ci_artifacts.txt').click }
end
step 'download of a file extracted from build artifacts should start' do
......
class Spinach::Features::ProjectBuildsPermissions < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedBuilds
include SharedPaths
include RepoHelpers
end
class Spinach::Features::ProjectBuildsSummary < Spinach::FeatureSteps
include SharedAuthentication
include SharedProject
include SharedBuilds
include RepoHelpers
step 'I see details of a build' do
expect(page).to have_content "Build ##{@build.id}"
end
step 'I see build trace' do
expect(page).to have_css '#build-trace'
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
module SharedBuilds
include Spinach::DSL
step 'CI is enabled' do
step 'project has CI enabled' do
@project.enable_ci
end
step 'I have recent build for my project' do
step 'project has a recent build' do
ci_commit = create :ci_commit, project: @project, sha: sample_commit.id
@build = create :ci_build, commit: ci_commit
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)
end
step 'I visit project builds page' do
visit namespace_project_builds_path(@project.namespace, @project)
end
step 'recent build has artifacts available' do
artifacts = Rails.root + 'spec/fixtures/ci_build_artifacts.zip'
archive = fixture_file_upload(artifacts, 'application/zip')
......@@ -25,4 +29,13 @@ module SharedBuilds
gzip = fixture_file_upload(metadata, 'application/x-gzip')
@build.update_attributes(artifacts_metadata: gzip)
end
step 'download of build artifacts archive starts' do
expect(page.response_headers['Content-Type']).to eq 'application/zip'
expect(page.response_headers['Content-Transfer-Encoding']).to eq 'binary'
end
step 'I access artifacts download page' do
visit download_namespace_project_build_artifacts_path(@project.namespace, @project, @build)
end
end
......@@ -7,6 +7,11 @@ module SharedProject
@project.team << [@user, :master]
end
step "project exists in some group namespace" do
@group = create(:group, name: 'some group')
@project = create(:project, namespace: @group)
end
# Create a specific project called "Shop"
step 'I own project "Shop"' do
@project = Project.find_by(name: "Shop")
......@@ -97,6 +102,18 @@ module SharedProject
@project ||= Project.first
end
# ----------------------------------------
# Project permissions
# ----------------------------------------
step 'I am member of a project with a guest role' do
@project.team << [@user, Gitlab::Access::GUEST]
end
step 'I am member of a project with a reporter role' do
@project.team << [@user, Gitlab::Access::REPORTER]
end
# ----------------------------------------
# Visibility of archived project
# ----------------------------------------
......@@ -229,5 +246,4 @@ module SharedProject
project ||= create(:empty_project, visibility, name: project_name, namespace: user.namespace)
project.team << [user, :master]
end
end
......@@ -264,6 +264,10 @@ module API
projects = projects.search(params[:search])
end
if params[:visibility].present?
projects = projects.search_by_visibility(params[:visibility])
end
projects.reorder(project_order_by => project_sort)
end
......
......@@ -20,7 +20,7 @@ module Ci
if build
update_runner_info
present build, with: Entities::Build
present build, with: Entities::BuildDetails
else
not_found!
end
......@@ -111,7 +111,7 @@ module Ci
build.artifacts_metadata = metadata
if build.save
present(build, with: Entities::Build)
present(build, with: Entities::BuildDetails)
else
render_validation_error!(build)
end
......
......@@ -16,10 +16,19 @@ module Ci
end
class Build < Grape::Entity
expose :id, :commands, :ref, :sha, :status, :project_id, :repo_url,
:before_sha, :allow_git_fetch, :project_name
expose :id, :ref, :tag, :sha, :status
expose :name, :token, :stage
expose :project_id
expose :project_name
expose :artifacts_file, using: ArtifactFile, if: lambda { |build, opts| build.artifacts? }
end
class BuildDetails < Build
expose :commands
expose :repo_url
expose :before_sha
expose :allow_git_fetch
expose :token
expose :options do |model|
model.options
......@@ -30,7 +39,7 @@ module Ci
end
expose :variables
expose :artifacts_file, using: ArtifactFile
expose :depends_on_builds, using: Build
end
class Runner < Grape::Entity
......
......@@ -39,7 +39,6 @@ module Gitlab
end
use_db && ActiveRecord::Base.connection.active? &&
!ActiveRecord::Migrator.needs_migration? &&
ActiveRecord::Base.connection.table_exists?('application_settings')
rescue ActiveRecord::NoDatabaseError
......
......@@ -9,6 +9,7 @@ module Gitlab
delegate :namespace, :name_with_namespace, to: :project, prefix: :project
delegate :name, to: :author, prefix: :author
delegate :username, to: :author, prefix: :author
def initialize(notify, project_id, recipient, opts = {})
raise ArgumentError, 'Missing options: author_id, ref, action' unless
......
......@@ -30,28 +30,31 @@ module Gitlab
end
def find_by_uid_and_provider
self.class.find_by_uid_and_provider(
auth_hash.uid, auth_hash.provider)
self.class.find_by_uid_and_provider(auth_hash.uid, auth_hash.provider)
end
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
def update_user_attributes
return unless persisted?
if persisted?
if auth_hash.has_email?
gl_user.skip_reconfirmation!
gl_user.email = auth_hash.email
end
gl_user.skip_reconfirmation!
gl_user.email = auth_hash.email
# 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.build(provider: auth_hash.provider)
# 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.build(provider: auth_hash.provider)
# For a new identity set extern_uid to the LDAP DN
# For an existing identity with matching email but changed DN, update the DN.
# For an existing identity with no change in DN, this line changes nothing.
identity.extern_uid = auth_hash.uid
end
# For a new user set extern_uid to the LDAP DN
# For an existing user with matching email but changed DN, update the DN.
# For an existing user with no change in DN, this line changes nothing.
identity.extern_uid = auth_hash.uid
gl_user.ldap_email = auth_hash.has_email?
gl_user
end
......
......@@ -32,6 +32,10 @@ module Gitlab
@password ||= Gitlab::Utils.force_utf8(Devise.friendly_token[0, 8].downcase)
end
def has_email?
get_info(:email).present?
end
private
def info
......@@ -46,8 +50,8 @@ module Gitlab
def username_and_email
@username_and_email ||= begin
username = get_info(:username) || get_info(:nickname)
email = get_info(:email)
username = get_info(:username).presence || get_info(:nickname).presence
email = get_info(:email).presence
username ||= generate_username(email) if email
email ||= generate_temporarily_email(username) if username
......
......@@ -111,7 +111,7 @@ module Gitlab
def block_after_signup?
if creating_linked_ldap_user?
ldap_config.block_auto_created_users
else
else
Gitlab.config.omniauth.block_auto_created_users
end
end
......@@ -135,15 +135,18 @@ module Gitlab
def user_attributes
# Give preference to LDAP for sensitive information when creating a linked account
if creating_linked_ldap_user?
username = ldap_person.username
email = ldap_person.email.first
else
username = auth_hash.username
email = auth_hash.email
username = ldap_person.username.presence
email = ldap_person.email.first.presence
end
username ||= auth_hash.username
email ||= auth_hash.email
name = auth_hash.name
name = ::Namespace.clean_path(username) if name.strip.empty?
{
name: auth_hash.name,
name: name,
username: ::Namespace.clean_path(username),
email: email,
password: auth_hash.password,
......
......@@ -4,6 +4,9 @@ end
String.disable_colorization = true unless STDOUT.isatty
# Prevent StateMachine warnings from outputting during a cron task
StateMachines::Machine.ignore_method_conflicts = true if ENV['CRON']
namespace :gitlab do
# Ask if the user wants to continue
......
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
end
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
end
end
......@@ -110,6 +110,32 @@ describe Gitlab::LDAP::User, lib: true do
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
def configure_block(value)
allow_any_instance_of(Gitlab::LDAP::Config).
......
......@@ -40,14 +40,38 @@ describe Notify do
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|
include_examples 'an email with X-GitLab headers containing project details'
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 'X-GitLab-Project', /#{project.name}/
end
end
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
is_expected.to have_subject /^Re: /
end
......@@ -56,7 +80,6 @@ describe Notify do
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 'In-Reply-To', /<#{thread_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/
is_expected.to have_header 'X-GitLab-Project', /#{project.name}/
end
end
......@@ -656,6 +679,8 @@ describe Notify do
it_behaves_like 'it should not have Gmail Actions links'
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
sender = subject.header[:from].addrs[0]
......@@ -685,6 +710,8 @@ describe Notify do
it_behaves_like 'it should not have Gmail Actions links'
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
sender = subject.header[:from].addrs[0]
......@@ -713,6 +740,8 @@ describe Notify do
it_behaves_like 'it should not have Gmail Actions links'
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
sender = subject.header[:from].addrs[0]
......@@ -737,6 +766,8 @@ describe Notify do
it_behaves_like 'it should not have Gmail Actions links'
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
sender = subject.header[:from].addrs[0]
......@@ -765,6 +796,8 @@ describe Notify do
it_behaves_like 'it should not have Gmail Actions links'
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
sender = subject.header[:from].addrs[0]
......@@ -871,6 +904,8 @@ describe Notify do
it_behaves_like 'it should show Gmail Actions View Commit 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
sender = subject.header[:from].addrs[0]
......@@ -904,6 +939,15 @@ describe Notify do
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
should have_subject /Build success for/
end
......@@ -918,6 +962,15 @@ describe Notify do
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
should have_subject /Build failed for/
end
......
......@@ -26,7 +26,7 @@ RSpec.describe AbuseReport, type: :model do
it { is_expected.to validate_presence_of(:reporter) }
it { is_expected.to validate_presence_of(:user) }
it { is_expected.to validate_presence_of(:message) }
it { is_expected.to validate_uniqueness_of(:user_id) }
it { is_expected.to validate_uniqueness_of(:user_id).with_message('has already been reported') }
end
describe '#remove_user' do
......
......@@ -426,6 +426,30 @@ describe Ci::Build, models: true do
it { is_expected.to include(project.web_url[7..-1]) }
end
describe :depends_on_builds do
let!(:build) { FactoryGirl.create :ci_build, commit: commit, name: 'build', stage_idx: 0, stage: 'build' }
let!(:rspec_test) { FactoryGirl.create :ci_build, commit: commit, name: 'rspec', stage_idx: 1, stage: 'test' }
let!(:rubocop_test) { FactoryGirl.create :ci_build, commit: commit, name: 'rubocop', stage_idx: 1, stage: 'test' }
let!(:staging) { FactoryGirl.create :ci_build, commit: commit, name: 'staging', stage_idx: 2, stage: 'deploy' }
it 'to have no dependents if this is first build' do
expect(build.depends_on_builds).to be_empty
end
it 'to have one dependent if this is test' do
expect(rspec_test.depends_on_builds.map(&:id)).to contain_exactly(build.id)
end
it 'to have all builds from build and test stage if this is last' do
expect(staging.depends_on_builds.map(&:id)).to contain_exactly(build.id, rspec_test.id, rubocop_test.id)
end
it 'to have retried builds instead the original ones' do
retried_rspec = Ci::Build.retry(rspec_test)
expect(staging.depends_on_builds.map(&:id)).to contain_exactly(build.id, retried_rspec.id, rubocop_test.id)
end
end
def create_mr(build, commit, factory: :merge_request, created_at: Time.now)
FactoryGirl.create(factory,
source_project_id: commit.gl_project_id,
......
......@@ -90,6 +90,29 @@ describe API::API, api: true do
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
before do
project2
......
......@@ -101,6 +101,18 @@ describe Ci::API::API do
{ "key" => "TRIGGER_KEY", "value" => "TRIGGER_VALUE", "public" => false },
])
end
it "returns dependent builds" do
commit = FactoryGirl.create(:ci_commit, project: project)
commit.create_builds('master', false, nil, nil)
commit.builds.where(stage: 'test').each(&:success)
post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin }
expect(response.status).to eq(201)
expect(json_response["depends_on_builds"].count).to eq(2)
expect(json_response["depends_on_builds"][0]["name"]).to eq("rspec")
end
end
describe "PUT /builds/:id" do
......
......@@ -227,7 +227,7 @@ describe NotificationService, services: true do
end
describe :reassigned_issue do
it 'should email new assignee' do
it 'emails new assignee' do
notification.reassigned_issue(issue, @u_disabled)
should_email(issue.assignee)
......@@ -238,6 +238,62 @@ describe NotificationService, services: true do
should_not_email(@u_participating)
should_not_email(@u_disabled)
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
describe :close_issue do
......
......@@ -32,6 +32,7 @@ describe Projects::CreateService, services: true do
it { expect(@project).to be_valid }
it { expect(@project.owner).to eq(@user) }
it { expect(@project.team.masters).to include(@user) }
it { expect(@project.namespace).to eq(@user.namespace) }
end
......
......@@ -36,8 +36,8 @@ staging:
script: "cap deploy stating"
type: deploy
tags:
- capistrano
- debian
- ruby
- mysql
except:
- stable
......@@ -47,8 +47,8 @@ production:
- cap deploy production
- cap notify
tags:
- capistrano
- debian
- ruby
- mysql
only:
- master
- /^deploy-.*$/
......
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