Commit 1565a95d authored by Han Loong Liauw's avatar Han Loong Liauw

Merge branch 'master' into add-dates-snippets-show

parents 45e11d95 c856a7a5
......@@ -25,7 +25,6 @@ config/initializers/rack_attack.rb
config/initializers/smtp_settings.rb
config/resque.yml
config/unicorn.rb
config/mail_room.yml
config/secrets.yml
coverage/*
db/*.sqlite3
......
Please view this file on the master branch, on stable branches it's out of date.
v 8.2.0 (unreleased)
- Show last project commit to default branch on project home page
- Highlight comment based on anchor in URL
v 8.1.0 (unreleased)
- Fix error preventing displaying of commit data for a directory with a leading dot (Stan Hu)
- Speed up load times of issue detail pages by roughly 1.5x
- Add a system note and update relevant merge requests when a branch is deleted or re-added (Stan Hu)
- Make diff file view easier to use on mobile screens (Stan Hu)
- Improved performance of finding users by username or Email address
- Fix bug where merge request comments created by API would not trigger notifications (Stan Hu)
- Add support for creating directories from Files page (Stan Hu)
- Allow removing of project without confirmation when JavaScript is disabled (Stan Hu)
- Support filtering by "Any" milestone or issue and fix "No Milestone" and "No Label" filters (Stan Hu)
......@@ -17,7 +26,10 @@ v 8.1.0 (unreleased)
- Fix cases where Markdown did not render links in activity feed (Stan Hu)
- Add first and last to pagination (Zeger-Jan van de Weg)
- Added Commit Status API
- Added Builds View
- Added when to .gitlab-ci.yml
- Show CI status on commit page
- Added CI_BUILD_TAG, _STAGE, _NAME and _TRIGGERED to CI builds
- Show CI status on Your projects page and Starred projects page
- Remove "Continuous Integration" page from dashboard
- Add notes and SSL verification entries to hook APIs (Ben Boeckel)
......@@ -27,6 +39,7 @@ v 8.1.0 (unreleased)
- Move CI triggers page to project settings area
- Move CI project settings page to CE project settings area
- Fix bug when removed file was not appearing in merge request diff
- Show warning when build cannot be served by any of the available CI runners
- Note the original location of a moved project when notifying users of the move
- Improve error message when merging fails
- Add support of multibyte characters in LDAP UID (Roman Petrov)
......@@ -47,11 +60,15 @@ v 8.1.0 (unreleased)
- Fix bug where Emojis in Markdown would truncate remaining text (Sakata Sinji)
- Persist filters when sorting on admin user page (Jerry Lukins)
- Added last modified date to snippets#show (Han Loong Liauw)
- Allow dashboard and group issues/MRs to be filtered by label
- Add spellcheck=false to certain input fields
- Invalidate stored service password if the endpoint URL is changed
- Project names are not fully shown if group name is too big, even on group page view
- Apply new design for Files page
- Add "New Page" button to Wiki Pages tab (Stan Hu)
- Only render 404 page from /public
- Hide passwords from services API (Alex Lossent)
- Fix: Images cannot show when projects' path was changed
v 8.0.4
- Fix Message-ID header to be RFC 2111-compliant to prevent e-mails being dropped (Stan Hu)
......
source "https://rubygems.org"
def darwin_only(require_as)
RUBY_PLATFORM.include?('darwin') && require_as
end
def linux_only(require_as)
RUBY_PLATFORM.include?('linux') && require_as
end
gem 'rails', '4.1.12'
# Specify a sprockets version due to security issue
......@@ -47,7 +39,7 @@ gem "browser", '~> 1.0.0'
# Extracting information from a git repository
# Provide access to Gitlab::Git library
gem "gitlab_git", '~> 7.2.18'
gem "gitlab_git", '~> 7.2.19'
# LDAP Auth
# GitLab fork with several improvements to original library. For full list of changes
......@@ -102,7 +94,7 @@ gem "seed-fu", '~> 2.3.5'
gem 'html-pipeline', '~> 1.11.0'
gem 'task_list', '~> 1.0.2', require: 'task_list/railtie'
gem 'github-markup', '~> 1.3.1'
gem 'redcarpet', '~> 3.3.2'
gem 'redcarpet', '~> 3.3.3'
gem 'RedCloth', '~> 4.2.9'
gem 'rdoc', '~>3.6'
gem 'org-ruby', '~> 0.9.12'
......@@ -196,7 +188,7 @@ gem 'charlock_holmes', '~> 0.6.9.4'
gem "sass-rails", '~> 4.0.5'
gem "coffee-rails", '~> 4.1.0'
gem "uglifier", '~> 2.3.2'
gem "uglifier", '~> 2.7.2'
gem 'turbolinks', '~> 2.5.0'
gem 'jquery-turbolinks', '~> 2.0.1'
......@@ -224,6 +216,9 @@ group :development do
gem 'quiet_assets', '~> 1.0.2'
gem 'rack-mini-profiler', '~> 0.9.0', require: false
gem 'rerun', '~> 0.10.0'
gem 'bullet', require: false
gem 'active_record_query_trace', require: false
gem 'rack-lineprof', platform: :mri
# Better errors handler
gem 'better_errors', '~> 1.0.1'
......@@ -290,7 +285,7 @@ gem 'newrelic-grape'
gem 'octokit', '~> 3.7.0'
gem "mail_room", "~> 0.6.0"
gem "mail_room", "~> 0.6.1"
gem 'email_reply_parser', '~> 0.5.8'
......@@ -304,11 +299,3 @@ gem 'oauth2', '~> 1.0.0'
# Soft deletion
gem "paranoia", "~> 2.0"
group :development, :test do
gem 'guard-rspec', '~> 4.2.0'
gem 'rb-fsevent', require: darwin_only('rb-fsevent')
gem 'growl', require: darwin_only('growl')
gem 'rb-inotify', require: linux_only('rb-inotify')
end
......@@ -17,6 +17,7 @@ GEM
activesupport (= 4.1.12)
builder (~> 3.1)
erubis (~> 2.7.0)
active_record_query_trace (1.5)
activemodel (4.1.12)
activesupport (= 4.1.12)
builder (~> 3.1)
......@@ -87,6 +88,9 @@ GEM
terminal-table (~> 1.4)
browser (1.0.0)
builder (3.2.2)
bullet (4.14.9)
activesupport (>= 3.0.0)
uniform_notifier (~> 1.9.0)
byebug (6.0.2)
cal-heatmap-rails (0.0.1)
capybara (2.4.4)
......@@ -134,6 +138,7 @@ GEM
daemons (1.2.3)
database_cleaner (1.4.1)
debug_inspector (0.0.2)
debugger-ruby_core_source (1.3.8)
default_value_for (3.0.1)
activerecord (>= 3.2.0, < 5.0)
descendants_tracker (0.0.4)
......@@ -278,7 +283,7 @@ GEM
mime-types (~> 1.19)
gitlab_emoji (0.1.1)
gemojione (~> 2.0)
gitlab_git (7.2.18)
gitlab_git (7.2.19)
activesupport (~> 4.0)
charlock_holmes (~> 0.6)
gitlab-linguist (~> 3.0)
......@@ -314,19 +319,6 @@ GEM
grape-entity (0.4.8)
activesupport
multi_json (>= 1.3.2)
growl (1.0.3)
guard (2.13.0)
formatador (>= 0.2.4)
listen (>= 2.7, <= 4.0)
lumberjack (~> 1.0)
nenv (~> 0.1)
notiffany (~> 0.0)
pry (>= 0.9.12)
shellany (~> 0.0)
thor (>= 0.18.1)
guard-rspec (4.2.10)
guard (~> 2.1)
rspec (>= 2.14, < 4.0)
haml (4.0.7)
tilt
haml-rails (0.9.0)
......@@ -387,12 +379,11 @@ GEM
celluloid (~> 0.16.0)
rb-fsevent (>= 0.9.3)
rb-inotify (>= 0.9)
lumberjack (1.0.9)
macaddr (1.7.1)
systemu (~> 2.6.2)
mail (2.6.3)
mime-types (>= 1.16, < 3)
mail_room (0.6.0)
mail_room (0.6.1)
method_source (0.8.2)
mime-types (1.25.1)
mimemagic (0.3.0)
......@@ -403,7 +394,6 @@ GEM
multi_xml (0.5.5)
multipart-post (2.0.0)
mysql2 (0.3.20)
nenv (0.2.0)
nested_form (0.3.2)
net-ldap (0.11)
net-scp (1.2.1)
......@@ -416,9 +406,6 @@ GEM
newrelic_rpm (3.9.4.245)
nokogiri (1.6.6.2)
mini_portile (~> 0.6.0)
notiffany (0.0.7)
nenv (~> 0.1)
shellany (~> 0.0)
nprogress-rails (0.1.2.3)
oauth (0.4.7)
oauth2 (1.0.0)
......@@ -502,6 +489,10 @@ GEM
rack-attack (4.3.0)
rack
rack-cors (0.4.0)
rack-lineprof (0.0.3)
rack (~> 1.5)
rblineprof (~> 0.3.6)
term-ansicolor (~> 1.3)
rack-mini-profiler (0.9.7)
rack (>= 1.1.3)
rack-mount (0.8.3)
......@@ -540,13 +531,15 @@ GEM
rb-fsevent (0.9.5)
rb-inotify (0.9.5)
ffi (>= 0.5.0)
rblineprof (0.3.6)
debugger-ruby_core_source (~> 1.3)
rbvmomi (1.8.2)
builder
nokogiri (>= 1.4.1)
trollop
rdoc (3.12.2)
json (~> 1.4)
redcarpet (3.3.2)
redcarpet (3.3.3)
redis (3.2.1)
redis-actionpack (4.0.0)
actionpack (~> 4)
......@@ -647,7 +640,6 @@ GEM
sexp_processor (4.6.0)
sham_rack (1.3.6)
rack
shellany (0.0.1)
shoulda-matchers (2.8.0)
activesupport (>= 3.0.0)
sidekiq (3.3.0)
......@@ -741,7 +733,7 @@ GEM
simple_oauth (~> 0.1.4)
tzinfo (1.2.2)
thread_safe (~> 0.1)
uglifier (2.3.3)
uglifier (2.7.2)
execjs (>= 0.3.0)
json (>= 1.8.0)
underscore-rails (1.4.4)
......@@ -755,6 +747,7 @@ GEM
unicorn-worker-killer (0.4.3)
get_process_mem (~> 0)
unicorn (~> 4)
uniform_notifier (1.9.0)
uuid (2.3.8)
macaddr (~> 1.0)
version_sorter (2.0.0)
......@@ -784,6 +777,7 @@ PLATFORMS
DEPENDENCIES
RedCloth (~> 4.2.9)
ace-rails-ap (~> 2.0.1)
active_record_query_trace
activerecord-deprecated_finders (~> 1.0.3)
activerecord-session_store (~> 0.1.0)
acts-as-taggable-on (~> 3.4)
......@@ -800,6 +794,7 @@ DEPENDENCIES
bootstrap-sass (~> 3.0)
brakeman (= 3.0.1)
browser (~> 1.0.0)
bullet
byebug
cal-heatmap-rails (~> 0.0.1)
capybara (~> 2.4.0)
......@@ -834,15 +829,13 @@ DEPENDENCIES
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-linguist (~> 3.0.1)
gitlab_emoji (~> 0.1)
gitlab_git (~> 7.2.18)
gitlab_git (~> 7.2.19)
gitlab_meta (= 7.0)
gitlab_omniauth-ldap (~> 1.2.1)
gollum-lib (~> 4.0.2)
gon (~> 5.0.0)
grape (~> 0.6.1)
grape-entity (~> 0.4.2)
growl
guard-rspec (~> 4.2.0)
haml-rails (~> 0.9.0)
hipchat (~> 1.5.0)
html-pipeline (~> 1.11.0)
......@@ -854,7 +847,7 @@ DEPENDENCIES
jquery-ui-rails (~> 4.2.1)
kaminari (~> 0.16.3)
letter_opener (~> 1.1.2)
mail_room (~> 0.6.0)
mail_room (~> 0.6.1)
minitest (~> 5.7.0)
mousetrap-rails (~> 1.4.6)
mysql2 (~> 0.3.16)
......@@ -882,14 +875,13 @@ DEPENDENCIES
quiet_assets (~> 1.0.2)
rack-attack (~> 4.3.0)
rack-cors (~> 0.4.0)
rack-lineprof
rack-mini-profiler (~> 0.9.0)
rack-oauth2 (~> 1.0.5)
rails (= 4.1.12)
raphael-rails (~> 2.1.2)
rb-fsevent
rb-inotify
rdoc (~> 3.6)
redcarpet (~> 3.3.2)
redcarpet (~> 3.3.3)
redis-rails (~> 4.0.0)
request_store (~> 1.2.0)
rerun (~> 0.10.0)
......@@ -926,7 +918,7 @@ DEPENDENCIES
thin (~> 1.6.1)
tinder (~> 1.10.0)
turbolinks (~> 2.5.0)
uglifier (~> 2.3.2)
uglifier (~> 2.7.2)
underscore-rails (~> 1.4.4)
unf (~> 0.1.4)
unicorn (~> 4.8.2)
......
......@@ -68,8 +68,8 @@ class @MergeRequestTabs
scrollToElement: (container) ->
if window.location.hash
top = $(container + " " + window.location.hash).offset().top
$('body').scrollTo(top)
$el = $("#{container} #{window.location.hash}")
$('body').scrollTo($el.offset().top) if $el.length
# Activate a tab based on the current action
activateTab: (action) ->
......@@ -127,7 +127,7 @@ class @MergeRequestTabs
document.getElementById('commits').innerHTML = data.html
$('.js-timeago').timeago()
@commitsLoaded = true
@scrollToElement(".commits")
@scrollToElement("#commits")
loadDiff: (source) ->
return if @diffsLoaded
......@@ -137,7 +137,7 @@ class @MergeRequestTabs
success: (data) =>
document.getElementById('diffs').innerHTML = data.html
@diffsLoaded = true
@scrollToElement(".diffs")
@scrollToElement("#diffs")
# Show or hide the loading spinner
#
......
......@@ -7,6 +7,7 @@ class @ShortcutsNavigation extends Shortcuts
Mousetrap.bind('g e', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-project-activity'))
Mousetrap.bind('g f', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-tree'))
Mousetrap.bind('g c', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-commits'))
Mousetrap.bind('g b', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-builds'))
Mousetrap.bind('g n', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-network'))
Mousetrap.bind('g g', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-graphs'))
Mousetrap.bind('g i', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-issues'))
......
......@@ -18,6 +18,7 @@
line-height: 36px;
}
.content-block,
.gray-content-block {
margin: -$gl-padding;
background-color: $background-color;
......@@ -27,6 +28,10 @@
border-bottom: 1px solid $border-color;
color: $gl-gray;
&.white {
background-color: white;
}
&.top-block {
border-top: none;
}
......
......@@ -32,7 +32,7 @@
}
.select2-results .select2-result-label {
padding: 16px;
padding: 9px;
}
.select2-drop{
......
......@@ -13,6 +13,10 @@
border-bottom: 1px solid #ECEEF1;
border-right: 1px solid #ECEEF1;
&:target {
background: $hover;
}
&:last-child {
border-bottom: none;
}
......
@mixin md-typography {
color: $md-text-color;
word-wrap: break-word;
a {
color: $md-link-color;
......@@ -73,6 +74,8 @@
}
blockquote {
color: #7f8fa4;
font-size: inherit;
padding: 8px 21px;
margin: 12px 0 12px;
border-left: 3px solid #e7e9ed;
......@@ -80,7 +83,7 @@
blockquote p {
color: #7f8fa4 !important;
font-size: 15px;
font-size: inherit;
line-height: 1.5;
}
......@@ -101,9 +104,9 @@
pre {
margin: 12px 0 12px 0 !important;
background-color: #f8fafc !important;
background-color: #f8fafc;
font-size: 13px !important;
color: #5b6169 !important;
color: #5b6169;
line-height: 1.6em !important;
@include border-radius(2px);
}
......@@ -112,9 +115,9 @@
font-weight: inherit;
}
ul {
color: #5c5d5e;
ul, ol {
padding: 0;
margin: 6px 0 6px 18px !important;
}
li {
......@@ -136,6 +139,33 @@
text-decoration: none;
}
}
/* Link to current header. */
h1, h2, h3, h4, h5, h6 {
position: relative;
a.anchor {
// Setting `display: none` would prevent the anchor being scrolled to, so
// instead we set the height to 0 and it gets updated on hover.
height: 0;
}
&:hover > a.anchor {
$size: 16px;
position: absolute;
right: 100%;
top: 50%;
margin-top: -$size/2;
margin-right: 0px;
padding-right: 20px;
display: inline-block;
width: $size;
height: $size;
background-image: image-url("icon-link.png");
background-size: contain;
background-repeat: no-repeat;
}
}
}
......@@ -202,49 +232,11 @@ a > code {
}
/**
* Wiki typography
* Apply Markdown typography
*
*/
.wiki {
@include md-typography;
word-wrap: break-word;
padding: 7px;
/* Link to current header. */
h1, h2, h3, h4, h5, h6 {
position: relative;
a.anchor {
// Setting `display: none` would prevent the anchor being scrolled to, so
// instead we set the height to 0 and it gets updated on hover.
height: 0;
}
&:hover > a.anchor {
$size: 16px;
position: absolute;
right: 100%;
top: 50%;
margin-top: -$size/2;
margin-right: 0px;
padding-right: 20px;
display: inline-block;
width: $size;
height: $size;
background-image: image-url("icon-link.png");
background-size: contain;
background-repeat: no-repeat;
}
}
ul,ol {
padding: 0;
margin: 6px 0 6px 18px !important;
}
ol {
color: #5c5d5e;
}
}
.md-area {
......
/* https://github.com/MozMorris/tomorrow-pygments */
pre.code.highlight.dark,
.code.dark {
background-color: #1d1f21;
color: #c5c8c6;
background-color: #1d1f21 !important;
color: #c5c8c6 !important;
pre.code,
pre.highlight,
.line-numbers,
.line-numbers a {
background-color: #1d1f21 !important;
......@@ -23,8 +22,8 @@ pre.code.highlight.dark,
// Search result highlight
span.highlight_word {
background: #ffe792;
color: #000000;
background-color: #ffe792 !important;
color: #000000 !important;
}
.hll { background-color: #373b41 }
......
/* https://github.com/richleland/pygments-css/blob/master/monokai.css */
pre.code.monokai,
.code.monokai {
background: #272822;
color: #f8f8f2;
background-color: #272822 !important;
color: #f8f8f2 !important;
pre.highlight,
.line-numbers,
.line-numbers a {
background:#272822 !important;
color:#f8f8f2 !important;
background-color :#272822 !important;
color: #f8f8f2 !important;
}
pre.code {
......@@ -23,8 +22,8 @@ pre.code.monokai,
// Search result highlight
span.highlight_word {
background: #ffe792;
color: #000000;
background-color: #ffe792 !important;
color: #000000 !important;
}
.hll { background-color: #49483e }
......
/* https://gist.github.com/qguv/7936275 */
pre.code.highlight.solarized-dark,
.code.solarized-dark {
background-color: #002b36;
color: #93a1a1;
background-color: #002b36 !important;
color: #93a1a1 !important;
pre.code,
pre.highlight,
.line-numbers,
.line-numbers a {
background-color: #002b36 !important;
......@@ -23,7 +22,7 @@ pre.code.highlight.solarized-dark,
// Search result highlight
span.highlight_word {
background: #094554;
background-color: #094554 !important;
}
/* Solarized Dark
......
/* https://gist.github.com/qguv/7936275 */
pre.code.highlight.solarized-light,
.code.solarized-light {
background-color: #fdf6e3;
color: #586e75;
background-color: #fdf6e3 !important;
color: #586e75 !important;
pre.code,
pre.highlight,
.line-numbers,
.line-numbers a {
background-color: #fdf6e3 !important;
......@@ -23,7 +22,7 @@ pre.code.highlight.solarized-light,
// Search result highlight
span.highlight_word {
background: #eee8d5;
background-color: #eee8d5 !important;
}
/* Solarized Light
......
/* https://github.com/aahan/pygments-github-style */
pre.code.highlight.white,
.code.white {
background-color: #f8fafc;
font-size: 13px;
color: #5b6169;
line-height: 1.6em;
background-color: #f8fafc !important;
color: #5b6169 !important;
pre.highlight,
.line-numbers,
.line-numbers a {
background-color: $background-color !important;
color: $gl-gray !important;
}
pre.highlight {
background-color: #fff !important;
color: #333 !important;
}
pre.code {
border-left: 1px solid $border-color;
background-color: #fff !important;
color: #333 !important;
}
// highlight line via anchor
......@@ -28,7 +24,7 @@ pre.code.highlight.white,
// Search result highlight
span.highlight_word {
background: #fafe3d;
background-color: #fafe3d !important;
}
.hll { background-color: #f8f8f8 }
......
......@@ -68,3 +68,7 @@ body.modal-open {
.modal .modal-dialog {
width: 860px;
}
.documentation {
padding: 7px;
}
......@@ -511,3 +511,38 @@ pre.light-well {
margin-top: -1px;
}
}
.project-last-commit {
margin: 0 7px;
.ci-status {
margin-right: 16px;
}
.commit-row-message {
color: $gl-gray;
}
.commit_short_id {
margin-right: 5px;
color: $gl-link-color;
font-weight: 600;
}
.commit-author-link {
margin-left: 7px;
text-decoration: none;
.avatar {
float: none;
margin-right: 4px;
}
.commit-author-name {
font-weight: 600;
}
}
}
.project-show-readme .readme-holder {
padding: 7px;
}
......@@ -39,7 +39,13 @@ class Admin::ServicesController < Admin::ApplicationController
end
def application_services_params
params.permit(:id,
application_services_params = params.permit(:id,
service: Projects::ServicesController::ALLOWED_PARAMS)
if application_services_params[:service].is_a?(Hash)
Projects::ServicesController::FILTER_BLANK_PARAMS.each do |param|
application_services_params[:service].delete(param) if application_services_params[:service][param].blank?
end
end
application_services_params
end
end
......@@ -30,7 +30,7 @@ class ApplicationController < ActionController::Base
rescue_from ActiveRecord::RecordNotFound do |exception|
log_exception(exception)
render "errors/not_found", layout: "errors", status: 404
render_404
end
protected
......@@ -149,12 +149,8 @@ class ApplicationController < ActionController::Base
render "errors/access_denied", layout: "errors", status: 404
end
def not_found!
render "errors/not_found", layout: "errors", status: 404
end
def git_not_found!
render "errors/git_not_found", layout: "errors", status: 404
render html: "errors/git_not_found", layout: "errors", status: 404
end
def method_missing(method_sym, *arguments, &block)
......
......@@ -6,7 +6,7 @@ module Ci
@runners = Ci::Runner.order('id DESC')
@runners = @runners.search(params[:search]) if params[:search].present?
@runners = @runners.page(params[:page]).per(30)
@active_runners_cnt = Ci::Runner.where("contacted_at > ?", 1.minutes.ago).count
@active_runners_cnt = Ci::Runner.online.count
end
def show
......@@ -66,7 +66,7 @@ module Ci
end
def runner_params
params.require(:runner).permit(:token, :description, :tag_list, :contacted_at, :active)
params.require(:runner).permit(:token, :description, :tag_list, :active)
end
end
end
......@@ -62,7 +62,7 @@ class Import::BitbucketController < Import::BaseController
end
def verify_bitbucket_import_enabled
not_found! unless bitbucket_import_enabled?
render_404 unless bitbucket_import_enabled?
end
def bitbucket_auth
......
......@@ -99,6 +99,6 @@ class Import::FogbugzController < Import::BaseController
end
def verify_fogbugz_import_enabled
not_found! unless fogbugz_import_enabled?
render_404 unless fogbugz_import_enabled?
end
end
......@@ -47,7 +47,7 @@ class Import::GithubController < Import::BaseController
end
def verify_github_import_enabled
not_found! unless github_import_enabled?
render_404 unless github_import_enabled?
end
def github_auth
......
......@@ -44,7 +44,7 @@ class Import::GitlabController < Import::BaseController
end
def verify_gitlab_import_enabled
not_found! unless gitlab_import_enabled?
render_404 unless gitlab_import_enabled?
end
def gitlab_auth
......
......@@ -42,7 +42,7 @@ class Import::GitoriousController < Import::BaseController
end
def verify_gitorious_import_enabled
not_found! unless gitorious_import_enabled?
render_404 unless gitorious_import_enabled?
end
end
......@@ -106,7 +106,7 @@ class Import::GoogleCodeController < Import::BaseController
end
def verify_google_code_import_enabled
not_found! unless google_code_import_enabled?
render_404 unless google_code_import_enabled?
end
def user_map
......
......@@ -12,7 +12,7 @@ class Projects::AvatarsController < Projects::ApplicationController
filename: @blob.name
)
else
not_found!
render_404
end
end
......
......@@ -113,14 +113,14 @@ class Projects::BlobController < Projects::ApplicationController
end
end
return not_found!
return render_404
end
end
def commit
@commit = @repository.commit(@ref)
return not_found! unless @commit
return render_404 unless @commit
end
def assign_blob_vars
......@@ -128,7 +128,7 @@ class Projects::BlobController < Projects::ApplicationController
@ref, @path = extract_ref(@id)
rescue InvalidPathError
not_found!
render_404
end
def after_edit_path
......
class Projects::BuildsController < Projects::ApplicationController
before_action :ci_project
before_action :build
before_action :build, except: [:index, :cancel_all]
before_action :authorize_admin_project!, except: [:show, :status]
before_action :authorize_admin_project!, except: [:index, :show, :status]
layout "project"
def index
@scope = params[:scope]
@all_builds = project.ci_builds
@builds =
case @scope
when 'all'
@all_builds
when 'finished'
@all_builds.finished
else
@all_builds.running_or_pending
end
@builds = @builds.order('created_at DESC').page(params[:page]).per(30)
end
def cancel_all
@project.ci_builds.running_or_pending.each(&:cancel)
redirect_to namespace_project_builds_path(project.namespace, project)
end
def show
@builds = @ci_project.commits.find_by_sha(@build.sha).builds.order('id DESC')
@builds = @builds.where("id not in (?)", @build.id).page(params[:page]).per(20)
......
......@@ -55,9 +55,9 @@ class Projects::IssuesController < Projects::ApplicationController
end
def show
@participants = @issue.participants(current_user, @project)
@participants = @issue.participants(current_user)
@note = @project.notes.new(noteable: @issue)
@notes = @issue.notes.inc_author.fresh
@notes = @issue.notes.with_associations.fresh
@noteable = @issue
respond_with(@issue)
......
......@@ -246,7 +246,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def define_show_vars
@participants = @merge_request.participants(current_user, @project)
@participants = @merge_request.participants(current_user)
# Build a note object for comment form
@note = @project.notes.new(noteable: @merge_request)
......
......@@ -20,7 +20,7 @@ class Projects::RawController < Projects::ApplicationController
disposition: 'inline'
)
else
not_found!
render_404
end
end
......
......@@ -3,6 +3,7 @@ class Projects::RefsController < Projects::ApplicationController
include TreeHelper
before_action :require_non_empty_project
before_action :validate_ref_id
before_action :assign_ref_vars
before_action :authorize_download_code!
......@@ -71,4 +72,10 @@ class Projects::RefsController < Projects::ApplicationController
format.js
end
end
private
def validate_ref_id
return not_found! if params[:id].present? && params[:id] !~ Gitlab::Regex.git_reference_regex
end
end
......@@ -11,18 +11,9 @@ class Projects::RepositoriesController < Projects::ApplicationController
end
def archive
begin
file_path = ArchiveRepositoryService.new(@project, params[:ref], params[:format]).execute
rescue
return head :not_found
end
if file_path
# Send file to user
response.headers["Content-Length"] = File.open(file_path).size.to_s
send_file file_path
else
redirect_to request.fullpath
end
render json: ArchiveRepositoryService.new(@project, params[:ref], params[:format]).execute
rescue => ex
logger.error("#{self.class.name}: #{ex}")
return git_not_found!
end
end
......@@ -60,6 +60,6 @@ class Projects::RunnersController < Projects::ApplicationController
end
def runner_params
params.require(:runner).permit(:description, :tag_list, :contacted_at, :active)
params.require(:runner).permit(:description, :tag_list, :active)
end
end
......@@ -9,6 +9,10 @@ class Projects::ServicesController < Projects::ApplicationController
:note_events, :send_from_committer_email, :disable_diffs, :external_wiki_url,
:notify, :color,
:server_host, :server_port, :default_irc_uri, :enable_ssl_verification]
# Parameters to ignore if no value is specified
FILTER_BLANK_PARAMS = [:password]
# Authorize
before_action :authorize_admin_project!
before_action :service, only: [:edit, :update, :test]
......@@ -59,7 +63,9 @@ class Projects::ServicesController < Projects::ApplicationController
def service_params
service_params = params.require(:service).permit(ALLOWED_PARAMS)
service_params.delete("password") if service_params["password"].blank?
FILTER_BLANK_PARAMS.each do |param|
service_params.delete(param) if service_params[param].blank?
end
service_params
end
end
......@@ -10,7 +10,7 @@ class Projects::TreeController < Projects::ApplicationController
before_action :authorize_push_code!, only: [:create_dir]
def show
return not_found! unless @repository.commit(@ref)
return render_404 unless @repository.commit(@ref)
if tree.entries.empty?
if @repository.blob_at(@commit.id, @path)
......@@ -19,7 +19,7 @@ class Projects::TreeController < Projects::ApplicationController
File.join(@ref, @path))
) and return
elsif @path.present?
return not_found!
return render_404
end
end
......@@ -31,7 +31,7 @@ class Projects::TreeController < Projects::ApplicationController
end
def create_dir
return not_found! unless @commit_params.values.all?
return render_404 unless @commit_params.values.all?
begin
result = Files::CreateDirService.new(@project, current_user, @commit_params).execute
......
......@@ -20,7 +20,7 @@ class Projects::UploadsController < Projects::ApplicationController
end
def show
return not_found! if uploader.nil? || !uploader.file.exists?
return render_404 if uploader.nil? || !uploader.file.exists?
disposition = uploader.image? ? 'inline' : 'attachment'
send_file uploader.file.path, disposition: disposition
......
......@@ -10,7 +10,7 @@ class UploadsController < ApplicationController
end
unless uploader.file && uploader.file.exists?
return not_found!
return render_404
end
disposition = uploader.image? ? 'inline' : 'attachment'
......@@ -21,7 +21,7 @@ class UploadsController < ApplicationController
def find_model
unless upload_model && upload_mount
return not_found!
return render_404
end
@model = upload_model.find(params[:id])
......@@ -44,7 +44,7 @@ class UploadsController < ApplicationController
return if authorized
if current_user
not_found!
render_404
else
authenticate_user!
end
......
......@@ -68,13 +68,17 @@ module ApplicationHelper
end
end
def avatar_icon(user_email = '', size = nil)
user = User.find_by(email: user_email)
def avatar_icon(user_or_email = nil, size = nil)
if user_or_email.is_a?(User)
user = user_or_email
else
user = User.find_by(email: user_or_email)
end
if user
user.avatar_url(size) || default_avatar
else
gravatar_icon(user_email, size)
gravatar_icon(user_or_email, size)
end
end
......
......@@ -25,6 +25,10 @@ module GitlabRoutingHelper
namespace_project_commits_path(project.namespace, project, @ref || project.repository.root_ref)
end
def project_builds_path(project, *args)
namespace_project_builds_path(project.namespace, project, *args)
end
def activity_project_path(project, *args)
activity_namespace_project_path(project.namespace, project, *args)
end
......
......@@ -92,11 +92,19 @@ module LabelsHelper
end
end
def project_labels_options(project)
labels = project.labels.to_a
labels.unshift(Label::None)
labels.unshift(Label::Any)
options_from_collection_for_select(labels, 'name', 'title', params[:label_name])
def projects_labels_options
labels =
if @project
@project.labels
else
Label.where(project_id: @projects)
end
grouped_labels = Labels::GroupService.new(labels).execute
grouped_labels.unshift(Label::None)
grouped_labels.unshift(Label::Any)
options_from_collection_for_select(grouped_labels, 'name', 'title', params[:label_name])
end
# Required for Gitlab::Markdown::LabelReferenceFilter
......
......@@ -47,7 +47,7 @@ module MergeRequestsHelper
end
def issues_sentence(issues)
issues.map { |i| "##{i.iid}" }.to_sentence
issues.map(&:to_reference).to_sentence
end
def mr_change_branches_path(merge_request)
......
......@@ -29,7 +29,7 @@ module ProjectsHelper
author_html = ""
# Build avatar image tag
author_html << image_tag(avatar_icon(author.try(:email), opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]}", alt:'') if opts[:avatar]
author_html << image_tag(avatar_icon(author, opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]}", alt:'') if opts[:avatar]
# Build name span tag
author_html << content_tag(:span, sanitize(author.name), class: opts[:author_class]) if opts[:name]
......@@ -113,6 +113,10 @@ module ProjectsHelper
nav_tabs << :merge_requests
end
if can?(current_user, :read_build, project)
nav_tabs << :builds
end
if can?(current_user, :admin_project, project)
nav_tabs << :settings
end
......
module RunnersHelper
def runner_status_icon(runner)
unless runner.contacted_at
return content_tag :i, nil,
class: "fa fa-warning-sign",
status = runner.status
case status
when :not_connected
content_tag :i, nil,
class: "fa fa-warning",
title: "New runner. Has not connected yet"
end
status =
if runner.active?
runner.contacted_at > 3.hour.ago ? :online : :offline
else
:paused
end
when :online, :offline, :paused
content_tag :i, nil,
class: "fa fa-circle runner-status-#{status}",
title: "Runner is #{status}, last contact was #{time_ago_in_words(runner.contacted_at)} ago"
end
end
def runner_link(runner)
display_name = truncate(runner.display_name, length: 15)
id = "\##{runner.id}"
if current_user && current_user.admin
link_to ci_admin_runner_path(runner) do
display_name + id
end
else
display_name + id
end
end
end
......@@ -41,6 +41,7 @@ class Ability
:read_project_member,
:read_merge_request,
:read_note,
:read_build,
:download_code
]
......@@ -127,6 +128,7 @@ class Ability
:read_project_member,
:read_merge_request,
:read_note,
:read_build,
:create_project,
:create_issue,
:create_note
......
......@@ -93,10 +93,7 @@ module Ci
Ci::WebHookService.new.build_end(build)
end
if build.commit.should_create_next_builds?(build)
build.commit.create_next_builds(build.ref, build.tag, build.user, build.trigger_request)
end
build.commit.create_next_builds(build)
project.execute_services(build)
if project.coverage_enabled?
......@@ -119,7 +116,7 @@ module Ci
end
def variables
yaml_variables + project_variables + trigger_variables
predefined_variables + yaml_variables + project_variables + trigger_variables
end
def project
......@@ -220,15 +217,27 @@ module Ci
def cancel_url
if active?
Gitlab::Application.routes.url_helpers.
cancel_namespace_project_build_path(gl_project.namespace, gl_project, self, return_to: request.original_url)
cancel_namespace_project_build_path(gl_project.namespace, gl_project, self)
end
end
def retry_url
if commands.present?
Gitlab::Application.routes.url_helpers.
cancel_namespace_project_build_path(gl_project.namespace, gl_project, self, return_to: request.original_url)
retry_namespace_project_build_path(gl_project.namespace, gl_project, self)
end
end
def can_be_served?(runner)
(tag_list - runner.tag_list).empty?
end
def any_runners_online?
project.any_runners? { |runner| runner.active? && runner.online? && can_be_served?(runner) }
end
def show_warning?
pending? && !any_runners_online?
end
private
......@@ -258,5 +267,14 @@ module Ci
[]
end
end
def predefined_variables
variables = []
variables << { key: :CI_BUILD_TAG, value: ref, public: true } if tag?
variables << { key: :CI_BUILD_NAME, value: name, public: true }
variables << { key: :CI_BUILD_STAGE, value: stage, public: true }
variables << { key: :CI_BUILD_TRIGGERED, value: 'true', public: true } if trigger_request
variables
end
end
end
......@@ -24,6 +24,8 @@ module Ci
has_many :builds, class_name: 'Ci::Build'
has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest'
scope :ordered, -> { order('CASE WHEN ci_commits.committed_at IS NULL THEN 0 ELSE 1 END', :committed_at, :id) }
validates_presence_of :sha
validate :valid_commit_sha
......@@ -89,19 +91,28 @@ module Ci
def create_builds(ref, tag, user, trigger_request = nil)
return unless config_processor
config_processor.stages.any? do |stage|
CreateBuildsService.new.execute(self, stage, ref, tag, user, trigger_request).present?
CreateBuildsService.new.execute(self, stage, ref, tag, user, trigger_request, 'success').present?
end
end
def create_next_builds(ref, tag, user, trigger_request)
def create_next_builds(build)
return unless config_processor
stages = builds.where(ref: ref, tag: tag, trigger_request: trigger_request).group_by(&:stage)
# don't create other builds if this one is retried
latest_builds = builds.similar(build).latest
return unless latest_builds.exists?(build.id)
config_processor.stages.any? do |stage|
unless stages.include?(stage)
CreateBuildsService.new.execute(self, stage, ref, tag, user, trigger_request).present?
end
# get list of stages after this build
next_stages = config_processor.stages.drop_while { |stage| stage != build.stage }
next_stages.delete(build.stage)
# get status for all prior builds
prior_builds = latest_builds.reject { |other_build| next_stages.include?(other_build.stage) }
status = Ci::Status.get_status(prior_builds)
# create builds for next stages based
next_stages.any? do |stage|
CreateBuildsService.new.execute(self, stage, build.ref, build.tag, build.user, build.trigger_request, status).present?
end
end
......@@ -130,24 +141,7 @@ module Ci
return 'failed'
end
@status ||= begin
latest = latest_statuses
latest.reject! { |status| status.try(&:allow_failure?) }
if latest.none?
'skipped'
elsif latest.all?(&:success?)
'success'
elsif latest.all?(&:pending?)
'pending'
elsif latest.any?(&:running?) || latest.any?(&:pending?)
'running'
elsif latest.all?(&:canceled?)
'canceled'
else
'failed'
end
end
@status ||= Ci::Status.get_status(latest_statuses)
end
def pending?
......@@ -217,16 +211,6 @@ module Ci
update!(committed_at: DateTime.now)
end
def should_create_next_builds?(build)
# don't create other builds if this one is retried
other_builds = builds.similar(build).latest
return false unless other_builds.include?(build)
other_builds.all? do |build|
build.success? || build.ignored?
end
end
private
def save_yaml_error(error)
......
......@@ -115,12 +115,12 @@ module Ci
web_url
end
def any_runners?
if runners.active.any?
def any_runners?(&block)
if runners.active.any?(&block)
return true
end
shared_runners_enabled && Ci::Runner.shared.active.any?
shared_runners_enabled && Ci::Runner.shared.active.any?(&block)
end
def set_default_values
......@@ -205,7 +205,7 @@ module Ci
end
def commits
gl_project.ci_commits
gl_project.ci_commits.ordered
end
def builds
......
......@@ -21,6 +21,8 @@ module Ci
class Runner < ActiveRecord::Base
extend Ci::Model
LAST_CONTACT_TIME = 5.minutes.ago
has_many :builds, class_name: 'Ci::Build'
has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject'
has_many :projects, through: :runner_projects, class_name: 'Ci::Project'
......@@ -33,6 +35,7 @@ module Ci
scope :shared, ->() { where(is_shared: true) }
scope :active, ->() { where(active: true) }
scope :paused, ->() { where(active: false) }
scope :online, ->() { where('contacted_at > ?', LAST_CONTACT_TIME) }
acts_as_taggable
......@@ -56,7 +59,7 @@ module Ci
end
def display_name
return token unless !description.blank?
return short_sha unless !description.blank?
description
end
......@@ -65,6 +68,20 @@ module Ci
is_shared
end
def online?
contacted_at && contacted_at > LAST_CONTACT_TIME
end
def status
if contacted_at.nil?
:not_connected
elsif active?
online? ? :online : :offline
else
:paused
end
end
def belongs_to_one_project?
runner_projects.count == 1
end
......@@ -78,7 +95,7 @@ module Ci
end
def short_sha
token[0...10]
token[0...8] if token
end
end
end
......@@ -16,6 +16,7 @@ class CommitStatus < ActiveRecord::Base
scope :success, -> { where(status: 'success') }
scope :failed, -> { where(status: 'failed') }
scope :running_or_pending, -> { where(status:[:running, :pending]) }
scope :finished, -> { where(status:[:success, :failed, :canceled]) }
scope :latest, -> { where(id: unscope(:select).select('max(id)').group(:name, :ref)) }
scope :ordered, -> { order(:ref, :stage_idx, :name) }
scope :for_ref, ->(ref) { where(ref: ref) }
......@@ -27,7 +28,7 @@ class CommitStatus < ActiveRecord::Base
end
event :drop do
transition running: :failed
transition [:pending, :running] => :failed
end
event :success do
......@@ -88,4 +89,8 @@ class CommitStatus < ActiveRecord::Base
def retry_url
nil
end
def show_warning?
false
end
end
......@@ -47,7 +47,8 @@ module Issuable
prefix: true
attr_mentionable :title, :description
participant :author, :assignee, :notes, :mentioned_users
participant :author, :assignee, :notes_with_associations, :mentioned_users
end
module ClassMethods
......@@ -176,6 +177,10 @@ module Issuable
self.class.to_s.underscore
end
def notes_with_associations
notes.includes(:author, :project)
end
private
def filter_superceded_votes(votes, notes)
......
......@@ -41,55 +41,49 @@ module Mentionable
self
end
# Determine whether or not a cross-reference Note has already been created between this Mentionable and
# the specified target.
def has_mentioned?(target)
SystemNoteService.cross_reference_exists?(target, local_reference)
def all_references(current_user = self.author, text = self.mentionable_text)
ext = Gitlab::ReferenceExtractor.new(self.project, current_user)
ext.analyze(text)
ext
end
def mentioned_users(current_user = nil)
return [] if mentionable_text.blank?
ext = Gitlab::ReferenceExtractor.new(self.project, current_user)
ext.analyze(mentionable_text)
ext.users.uniq
all_references(current_user).users.uniq
end
# Extract GFM references to other Mentionables from this Mentionable. Always excludes its #local_reference.
def references(p = project, current_user = self.author, text = mentionable_text)
def referenced_mentionables(current_user = self.author, text = self.mentionable_text)
return [] if text.blank?
ext = Gitlab::ReferenceExtractor.new(p, current_user)
ext.analyze(text)
(ext.issues + ext.merge_requests + ext.commits).uniq - [local_reference]
refs = all_references(current_user, text)
(refs.issues + refs.merge_requests + refs.commits).uniq - [local_reference]
end
# Create a cross-reference Note for each GFM reference to another Mentionable found in +mentionable_text+.
def create_cross_references!(p = project, a = author, without = [])
refs = references(p)
def create_cross_references!(author = self.author, without = [], text = self.mentionable_text)
refs = referenced_mentionables(author, text)
# We're using this method instead of Array diffing because that requires
# both of the object's `hash` values to be the same, which may not be the
# case for otherwise identical Commit objects.
refs.reject! { |ref| without.include?(ref) }
refs.reject! { |ref| without.include?(ref) || cross_reference_exists?(ref) }
refs.each do |ref|
SystemNoteService.cross_reference(ref, local_reference, a)
SystemNoteService.cross_reference(ref, local_reference, author)
end
end
# When a mentionable field is changed, creates cross-reference notes that
# don't already exist
def create_new_cross_references!(p = project, a = author)
def create_new_cross_references!(author = self.author)
changes = detect_mentionable_changes
return if changes.empty?
original_text = changes.collect { |_, vals| vals.first }.join(' ')
preexisting = references(p, self.author, original_text)
create_cross_references!(p, a, preexisting)
preexisting = referenced_mentionables(author, original_text)
create_cross_references!(author, preexisting)
end
private
......@@ -111,4 +105,10 @@ module Mentionable
# Only include changed fields that are mentionable
source.select { |key, val| mentionable.include?(key) }
end
# Determine whether or not a cross-reference Note has already been created between this Mentionable and
# the specified target.
def cross_reference_exists?(target)
SystemNoteService.cross_reference_exists?(target, local_reference)
end
end
......@@ -37,8 +37,8 @@ module Participable
# Be aware that this method makes a lot of sql queries.
# Save result into variable if you are going to reuse it inside same request
def participants(current_user = self.author, project = self.project)
participants = self.class.participant_attrs.flat_map do |attr|
def participants(current_user = self.author)
self.class.participant_attrs.flat_map do |attr|
meth = method(attr)
value =
......@@ -48,28 +48,22 @@ module Participable
meth.call
end
participants_for(value, current_user, project)
end.compact.uniq
if project
participants.select! do |user|
user.can?(:read_project, project)
end
participants_for(value, current_user)
end.compact.uniq.select do |user|
user.can?(:read_project, self.project)
end
participants
end
private
def participants_for(value, current_user = nil, project = nil)
def participants_for(value, current_user = nil)
case value
when User
[value]
when Enumerable, ActiveRecord::Relation
value.flat_map { |v| participants_for(v, current_user, project) }
value.flat_map { |v| participants_for(v, current_user) }
when Participable
value.participants(current_user, project)
value.participants(current_user)
end
end
end
......@@ -64,7 +64,7 @@ class Group < Namespace
end
def owners
@owners ||= group_members.owners.map(&:user)
@owners ||= group_members.owners.includes(:user).map(&:user)
end
def add_users(user_ids, access_level, current_user = nil)
......
class GroupLabel
attr_accessor :title, :labels
alias_attribute :name, :title
def initialize(title, labels)
@title = title
@labels = labels
end
end
class GroupMilestone
attr_accessor :title, :milestones
alias_attribute :name, :title
def initialize(title, milestones)
......@@ -7,18 +7,10 @@ class GroupMilestone
@milestones = milestones
end
def title
@title
end
def safe_title
@title.parameterize
end
def milestones
@milestones
end
def projects
milestones.map { |milestone| milestone.project }
end
......
......@@ -163,7 +163,8 @@ class MergeRequestDiff < ActiveRecord::Base
merge_request.fetch_ref
# Get latest sha of branch from source project
source_sha = merge_request.source_project.commit(source_branch).sha
source_commit = merge_request.source_project.commit(source_branch)
source_sha = source_commit.try(:sha)
Gitlab::CompareResult.new(
Gitlab::Git::Compare.new(
......
......@@ -118,6 +118,8 @@ class Namespace < ActiveRecord::Base
gitlab_shell.add_namespace(path_was)
if gitlab_shell.mv_namespace(path_was, path)
Gitlab::UploadsTransfer.new.rename_namespace(path_was, path)
# If repositories moved successfully we need to
# send update instructions to users.
# However we cannot allow rollback since we moved namespace dir
......
......@@ -60,9 +60,13 @@ class Note < ActiveRecord::Base
scope :inc_author_project, ->{ includes(:project, :author) }
scope :inc_author, ->{ includes(:author) }
scope :with_associations, -> do
includes(:author, :noteable, :updated_by,
project: [:project_members, { group: [:group_members] }])
end
serialize :st_diff
before_create :set_diff, if: ->(n) { n.line_code.present? }
after_update :set_references
class << self
def discussions_from_notes(notes)
......@@ -333,15 +337,13 @@ class Note < ActiveRecord::Base
end
def noteable_type_name
if noteable_type.present?
noteable_type.downcase
end
noteable_type.downcase if noteable_type.present?
end
# FIXME: Hack for polymorphic associations with STI
# For more information visit http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#label-Polymorphic+Associations
def noteable_type=(sType)
super(sType.to_s.classify.constantize.base_class.to_s)
def noteable_type=(noteable_type)
super(noteable_type.to_s.classify.constantize.base_class.to_s)
end
# Reset notes events cache
......@@ -357,10 +359,6 @@ class Note < ActiveRecord::Base
Event.reset_event_cache_for(self)
end
def set_references
create_new_cross_references!(project, author)
end
def system?
read_attribute(:system)
end
......
......@@ -119,7 +119,7 @@ class Project < ActiveRecord::Base
has_many :deploy_keys, through: :deploy_keys_projects
has_many :users_star_projects, dependent: :destroy
has_many :starrers, through: :users_star_projects, source: :user
has_many :ci_commits, ->() { order('CASE WHEN ci_commits.committed_at IS NULL THEN 0 ELSE 1 END', :committed_at, :id) }, dependent: :destroy, class_name: 'Ci::Commit', foreign_key: :gl_project_id
has_many :ci_commits, dependent: :destroy, class_name: 'Ci::Commit', foreign_key: :gl_project_id
has_many :ci_builds, through: :ci_commits, source: :builds, dependent: :destroy, class_name: 'Ci::Build'
has_one :import_data, dependent: :destroy, class_name: "ProjectImportData"
......@@ -656,6 +656,8 @@ class Project < ActiveRecord::Base
# db changes in order to prevent out of sync between db and fs
raise Exception.new('repository cannot be renamed')
end
Gitlab::UploadsTransfer.new.rename_project(path_was, path, namespace.path)
end
def hook_attrs
......
......@@ -48,7 +48,7 @@ class BambooService < CiService
end
def reset_password
if prop_updated?(:bamboo_url)
if bamboo_url_changed? && !password_touched?
self.password = nil
end
end
......
......@@ -45,7 +45,7 @@ class TeamcityService < CiService
end
def reset_password
if prop_updated?(:teamcity_url)
if teamcity_url_changed? && !password_touched?
self.password = nil
end
end
......
......@@ -139,15 +139,28 @@ class ProjectTeam
Gitlab::Access.options.key max_member_access(user_id)
end
# This method assumes project and group members are eager loaded for optimal
# performance.
def max_member_access(user_id)
access = []
access << project.project_members.find_by(user_id: user_id).try(:access_field)
project.project_members.each do |member|
if member.user_id == user_id
access << member.access_field if member.access_field
break
end
end
if group
access << group.group_members.find_by(user_id: user_id).try(:access_field)
group.group_members.each do |member|
if member.user_id == user_id
access << member.access_field if member.access_field
break
end
end
end
access.compact.max
access.max
end
private
......
......@@ -480,6 +480,10 @@ class Repository
end
end
def merge_base(first_commit_id, second_commit_id)
rugged.merge_base(first_commit_id, second_commit_id)
end
def search_files(query, ref)
offset = 2
args = %W(git grep -i -n --before-context #{offset} --after-context #{offset} #{query} #{ref || root_ref})
......
......@@ -33,6 +33,8 @@ class Service < ActiveRecord::Base
after_initialize :initialize_properties
after_commit :reset_updated_properties
belongs_to :project
has_one :service_hook
......@@ -103,6 +105,7 @@ class Service < ActiveRecord::Base
# Provide convenient accessor methods
# for each serialized property.
# Also keep track of updated properties in a similar way as ActiveModel::Dirty
def self.prop_accessor(*args)
args.each do |arg|
class_eval %{
......@@ -111,19 +114,37 @@ class Service < ActiveRecord::Base
end
def #{arg}=(value)
updated_properties['#{arg}'] = #{arg} unless #{arg}_changed?
self.properties['#{arg}'] = value
end
def #{arg}_changed?
#{arg}_touched? && #{arg} != #{arg}_was
end
def #{arg}_touched?
updated_properties.include?('#{arg}')
end
def #{arg}_was
updated_properties['#{arg}']
end
}
end
end
# ActiveRecord does not provide a mechanism to track changes in serialized keys.
# This is why we need to perform extra query to do it mannually.
def prop_updated?(prop_name)
relation_name = self.type.underscore
previous_value = project.send(relation_name).send(prop_name)
return false if previous_value.nil?
previous_value != send(prop_name)
# 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,
# 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.
def updated_properties
@updated_properties ||= ActiveSupport::HashWithIndifferentAccess.new
end
def reset_updated_properties
@updated_properties = nil
end
def async_execute(data)
......
......@@ -68,6 +68,7 @@ class User < ActiveRecord::Base
include Referable
include Sortable
include TokenAuthenticatable
include CaseSensitivity
default_value_for :admin, false
default_value_for :can_create_group, gitlab_config.default_can_create_group
......@@ -273,8 +274,13 @@ class User < ActiveRecord::Base
end
def by_login(login)
where('lower(username) = :value OR lower(email) = :value',
value: login.to_s.downcase).first
return nil unless login
if login.include?('@'.freeze)
unscoped.iwhere(email: login).take
else
unscoped.iwhere(username: login).take
end
end
def find_by_username!(username)
......
......@@ -9,17 +9,10 @@ class ArchiveRepositoryService
def execute(options = {})
project.repository.clean_old_archives
raise "No archive file path" unless file_path
metadata = project.repository.archive_metadata(ref, storage_path, format)
raise "Repository or ref not found" if metadata.empty?
return file_path if archived?
unless archiving?
RepositoryArchiveWorker.perform_async(project.id, ref, format)
end
archived = wait_until_archived(options[:timeout] || 5.0)
file_path if archived
metadata
end
private
......@@ -27,36 +20,4 @@ class ArchiveRepositoryService
def storage_path
Gitlab.config.gitlab.repository_downloads_path
end
def file_path
@file_path ||= project.repository.archive_file_path(ref, storage_path, format)
end
def pid_file_path
@pid_file_path ||= project.repository.archive_pid_file_path(ref, storage_path, format)
end
def archived?
File.exist?(file_path)
end
def archiving?
File.exist?(pid_file_path)
end
def wait_until_archived(timeout = 5.0)
return archived? if timeout == 0.0
t1 = Time.now
begin
sleep 0.1
success = archived?
t2 = Time.now
end until success || t2 - t1 >= timeout
success
end
end
module Ci
class CreateBuildsService
def execute(commit, stage, ref, tag, user, trigger_request)
def execute(commit, stage, ref, tag, user, trigger_request, status)
builds_attrs = commit.config_processor.builds_for_stage_and_ref(stage, ref, tag)
# check when to create next build
builds_attrs = builds_attrs.select do |build_attrs|
case build_attrs[:when]
when 'on_success'
status == 'success'
when 'on_failure'
status == 'failed'
when 'always'
%w(success failed).include?(status)
end
end
builds_attrs.map do |build_attrs|
# don't create the same build twice
unless commit.builds.find_by(ref: ref, tag: tag, trigger_request: trigger_request, name: build_attrs[:name])
......
......@@ -17,7 +17,7 @@ module Ci
builds = builds.order('created_at ASC')
build = builds.find do |build|
(build.tag_list - current_runner.tag_list).empty?
build.can_be_served?(current_runner)
end
......
......@@ -49,10 +49,13 @@ class GitPushService
elsif push_to_existing_branch?(ref, oldrev)
# Collect data for this git push
@push_commits = project.repository.commits_between(oldrev, newrev)
project.update_merge_requests(oldrev, newrev, ref, @user)
process_commit_messages(ref)
end
# Update merge requests that may be affected by this push. A new branch
# could cause the last commit of a merge request to change.
project.update_merge_requests(oldrev, newrev, ref, @user)
@push_data = build_push_data(oldrev, newrev, ref)
# If CI was disabled but .gitlab-ci.yml file was pushed
......@@ -74,48 +77,30 @@ class GitPushService
def process_commit_messages(ref)
is_default_branch = is_default_branch?(ref)
@push_commits.each do |commit|
# Close issues if these commits were pushed to the project's default branch and the commit message matches the
# closing regex. Exclude any mentioned Issues from cross-referencing even if the commits are being pushed to
# a different branch.
issues_to_close = commit.closes_issues(user)
authors = Hash.new do |hash, commit|
email = commit.author_email
return hash[email] if hash.has_key?(email)
# Load commit author only if needed.
# For push with 1k commits it prevents 900+ requests in database
author = nil
hash[email] = commit_user(commit)
end
@push_commits.each do |commit|
# Keep track of the issues that will be actually closed because they are on a default branch.
# Hence, when creating cross-reference notes, the not-closed issues (on non-default branches)
# will also have cross-reference.
actually_closed_issues = []
closed_issues = []
if issues_to_close.present? && is_default_branch
author ||= commit_user(commit)
actually_closed_issues = issues_to_close
issues_to_close.each do |issue|
Issues::CloseService.new(project, author, {}).execute(issue, commit)
end
end
if project.default_issues_tracker?
create_cross_reference_notes(commit, actually_closed_issues)
end
if is_default_branch
# Close issues if these commits were pushed to the project's default branch and the commit message matches the
# closing regex. Exclude any mentioned Issues from cross-referencing even if the commits are being pushed to
# a different branch.
closed_issues = commit.closes_issues(user)
closed_issues.each do |issue|
Issues::CloseService.new(project, authors[commit], {}).execute(issue, commit)
end
end
def create_cross_reference_notes(commit, issues_to_close)
# Create cross-reference notes for any other references than those given in issues_to_close.
# Omit any issues that were referenced in an issue-closing phrase, or have already been
# mentioned from this commit (probably from this commit being pushed to a different branch).
refs = commit.references(project, user) - issues_to_close
refs.reject! { |r| commit.has_mentioned?(r) }
if refs.present?
author ||= commit_user(commit)
refs.each do |r|
SystemNoteService.cross_reference(r, commit, author)
end
commit.create_cross_references!(authors[commit], closed_issues)
end
end
......
......@@ -10,7 +10,7 @@ module Issues
issue.update_attributes(label_ids: label_params)
notification_service.new_issue(issue, current_user)
event_service.open_issue(issue, current_user)
issue.create_cross_references!(issue.project, current_user)
issue.create_cross_references!(current_user)
execute_hooks(issue, 'open')
end
......
......@@ -35,7 +35,7 @@ module Issues
create_title_change_note(issue, issue.previous_changes['title'].first)
end
issue.create_new_cross_references!(issue.project, current_user)
issue.create_new_cross_references!
execute_hooks(issue, 'update')
end
......
module Labels
class GroupService < ::BaseService
def initialize(project_labels)
@project_labels = project_labels.group_by(&:title)
end
def execute
build(@project_labels)
end
def label(title)
if title
group_label = @project_labels[title].group_by(&:title)
build(group_label).first
else
nil
end
end
private
def build(label)
label.map { |title, labels| GroupLabel.new(title, labels) }
end
end
end
......@@ -18,7 +18,7 @@ module MergeRequests
merge_request.update_attributes(label_ids: label_params)
event_service.open_mr(merge_request, current_user)
notification_service.new_merge_request(merge_request, current_user)
merge_request.create_cross_references!(merge_request.project, current_user)
merge_request.create_cross_references!(current_user)
execute_hooks(merge_request)
end
......
......@@ -6,12 +6,20 @@ module MergeRequests
@oldrev, @newrev = oldrev, newrev
@branch_name = Gitlab::Git.ref_name(ref)
@fork_merge_requests = @project.fork_merge_requests.opened
@commits = @project.repository.commits_between(oldrev, newrev)
@commits = []
# Leave a system note if a branch were deleted/added
if Gitlab::Git.blank_ref?(oldrev) || Gitlab::Git.blank_ref?(newrev)
comment_mr_branch_presence_changed
comment_mr_with_commits if @commits.present?
else
@commits = @project.repository.commits_between(oldrev, newrev)
comment_mr_with_commits
close_merge_requests
end
reload_merge_requests
execute_mr_web_hooks
comment_mr_with_commits
true
end
......@@ -31,7 +39,6 @@ module MergeRequests
commit_ids.include?(merge_request.last_commit.id)
end
merge_requests.uniq.select(&:source_project).each do |merge_request|
MergeRequests::PostMergeService.
new(merge_request.target_project, @current_user).
......@@ -70,13 +77,38 @@ module MergeRequests
end
end
# Add comment about branches being deleted or added to merge requests
def comment_mr_branch_presence_changed
presence = Gitlab::Git.blank_ref?(@oldrev) ? :add : :delete
merge_requests_for_source_branch.each do |merge_request|
last_commit = merge_request.last_commit
# Only look at changed commits in restore branch case
unless Gitlab::Git.blank_ref?(@newrev)
begin
# Since any number of commits could have been made to the restored branch,
# find the common root to see what has been added.
common_ref = @project.repository.merge_base(last_commit.id, @newrev)
# If the a commit no longer exists in this repo, gitlab_git throws
# a Rugged::OdbError. This is fixed in https://gitlab.com/gitlab-org/gitlab_git/merge_requests/52
@commits = @project.repository.commits_between(common_ref, @newrev) if common_ref
rescue
end
# Prevent system notes from seeing a blank SHA
@oldrev = nil
end
SystemNoteService.change_branch_presence(
merge_request, merge_request.project, @current_user,
:source, @branch_name, presence)
end
end
# Add comment about pushing new commits to merge requests
def comment_mr_with_commits
merge_requests = @project.origin_merge_requests.opened.where(source_branch: @branch_name).to_a
merge_requests += @fork_merge_requests.where(source_branch: @branch_name).to_a
merge_requests = filter_merge_requests(merge_requests)
merge_requests.each do |merge_request|
merge_requests_for_source_branch.each do |merge_request|
mr_commit_ids = Set.new(merge_request.commits.map(&:id))
new_commits, existing_commits = @commits.partition do |commit|
......@@ -91,14 +123,7 @@ module MergeRequests
# Call merge request webhook with update branches
def execute_mr_web_hooks
merge_requests = @project.origin_merge_requests.opened
.where(source_branch: @branch_name)
.to_a
merge_requests += @fork_merge_requests.where(source_branch: @branch_name)
.to_a
merge_requests = filter_merge_requests(merge_requests)
merge_requests.each do |merge_request|
merge_requests_for_source_branch.each do |merge_request|
execute_hooks(merge_request, 'update')
end
end
......@@ -106,5 +131,13 @@ module MergeRequests
def filter_merge_requests(merge_requests)
merge_requests.uniq.select(&:source_project)
end
def merge_requests_for_source_branch
@source_merge_requests ||= begin
merge_requests = @project.origin_merge_requests.opened.where(source_branch: @branch_name).to_a
merge_requests += @fork_merge_requests.where(source_branch: @branch_name).to_a
filter_merge_requests(merge_requests)
end
end
end
end
......@@ -59,7 +59,7 @@ module MergeRequests
merge_request.mark_as_unchecked
end
merge_request.create_new_cross_references!(merge_request.project, current_user)
merge_request.create_new_cross_references!
execute_hooks(merge_request, 'update')
end
......
......@@ -11,13 +11,7 @@ module Notes
# Skip system notes, like status changes and cross-references.
unless note.system
event_service.leave_note(note, note.author)
# Create a cross-reference note if this Note contains GFM that names an
# issue, merge request, or commit.
note.references.each do |mentioned|
SystemNoteService.cross_reference(mentioned, note.noteable, note.author)
end
note.create_cross_references!
execute_hooks(note)
end
end
......
......@@ -4,7 +4,7 @@ module Notes
return note unless note.editable?
note.update_attributes(params.merge(updated_by: current_user))
note.create_new_cross_references!
note.reset_events_cache
note
......
......@@ -27,6 +27,7 @@ module Projects
def transfer(project, new_namespace)
Project.transaction do
old_path = project.path_with_namespace
old_namespace = project.namespace
new_path = File.join(new_namespace.try(:path) || '', project.path)
if Project.where(path: project.path, namespace_id: new_namespace.try(:id)).present?
......@@ -51,6 +52,9 @@ module Projects
# clear project cached events
project.reset_events_cache
# Move uploads
Gitlab::UploadsTransfer.new.move_project(project.path, old_namespace.path, new_namespace.path)
true
end
end
......
......@@ -168,6 +168,31 @@ class SystemNoteService
create_note(noteable: noteable, project: project, author: author, note: body)
end
# Called when a branch in Noteable is added or deleted
#
# noteable - Noteable object
# project - Project owning noteable
# author - User performing the change
# branch_type - :source or :target
# branch - branch name
# presence - :add or :delete
#
# Example Note text:
#
# "Restored target branch `feature`"
#
# Returns the created Note object
def self.change_branch_presence(noteable, project, author, branch_type, branch, presence)
verb =
if presence == :add
'restored'
else
'deleted'
end
body = "#{verb} #{branch_type.to_s} branch `#{branch}`".capitalize
create_note(noteable: noteable, project: project, author: author, note: body)
end
# Called when a Mentionable references a Noteable
#
# noteable - Noteable object being referenced
......
......@@ -26,7 +26,7 @@ class FileUploader < CarrierWave::Uploader::Base
end
def secure_url
File.join(Gitlab.config.gitlab.url, @project.path_with_namespace, "uploads", @secret, file.filename)
File.join("/uploads", @secret, file.filename)
end
def file_storage?
......
......@@ -8,7 +8,7 @@
= @user.name
%ul.well-list
%li
= image_tag avatar_icon(@user.email, 60), class: "avatar s60"
= image_tag avatar_icon(@user, 60), class: "avatar s60"
%li
%span.light Profile page:
%strong
......
......@@ -7,4 +7,4 @@
= link_to_gfm issue.title, [project.namespace.becomes(Namespace), project, issue], title: issue.title
.pull-right.assignee-icon
- if issue.assignee
= image_tag avatar_icon(issue.assignee.email, 16), class: "avatar s16"
= image_tag avatar_icon(issue.assignee, 16), class: "avatar s16"
......@@ -7,4 +7,4 @@
= link_to_gfm merge_request.title, [project.namespace.becomes(Namespace), project, merge_request], title: merge_request.title
.pull-right.assignee-icon
- if merge_request.assignee
= image_tag avatar_icon(merge_request.assignee.email, 16), class: "avatar s16"
= image_tag avatar_icon(merge_request.assignee, 16), class: "avatar s16"
......@@ -79,7 +79,7 @@
- @dashboard_milestone.participants.each do |user|
%li
= link_to user, title: user.name, class: "darken" do
= image_tag avatar_icon(user.email, 32), class: "avatar s32"
= image_tag avatar_icon(user, 32), class: "avatar s32"
%strong= truncate(user.name, lenght: 40)
%br
%small.cgray= user.username
......@@ -5,6 +5,7 @@
- if current_user.can_create_project?
%span.input-group-btn
= link_to new_project_path, class: 'btn btn-green' do
New project
%i.fa.fa-plus
New Project
= render 'shared/projects/list', projects: @projects, ci: true
......@@ -5,6 +5,7 @@
- if can? current_user, :create_projects, @group
%span.input-group-btn
= link_to new_project_path(namespace_id: @group.id), class: 'btn btn-green' do
New project
%i.fa.fa-plus
New Project
= render 'shared/projects/list', projects: @projects, projects_limit: 20, stars: false, skip_namespace: true
......@@ -5,7 +5,7 @@
%li{class: "#{dom_class(member)} js-toggle-container", id: dom_id(member)}
%span{class: ("list-item-name" if show_controls)}
- if member.user
= image_tag avatar_icon(user.email, 16), class: "avatar s16", alt: ''
= image_tag avatar_icon(user, 16), class: "avatar s16", alt: ''
%strong
= link_to user.name, user_path(user)
%span.cgray= user.username
......
......@@ -7,4 +7,4 @@
= link_to_gfm issue.title, [project.namespace.becomes(Namespace), project, issue], title: issue.title
.pull-right.assignee-icon
- if issue.assignee
= image_tag avatar_icon(issue.assignee.email, 16), class: "avatar s16", alt: ''
= image_tag avatar_icon(issue.assignee, 16), class: "avatar s16", alt: ''
......@@ -7,4 +7,4 @@
= link_to_gfm merge_request.title, [project.namespace.becomes(Namespace), project, merge_request], title: merge_request.title
.pull-right.assignee-icon
- if merge_request.assignee
= image_tag avatar_icon(merge_request.assignee.email, 16), class: "avatar s16", alt: ''
= image_tag avatar_icon(merge_request.assignee, 16), class: "avatar s16", alt: ''
......@@ -87,7 +87,7 @@
- @group_milestone.participants.each do |user|
%li
= link_to user, title: user.name, class: "darken" do
= image_tag avatar_icon(user.email, 32), class: "avatar s32"
= image_tag avatar_icon(user, 32), class: "avatar s32"
%strong= truncate(user.name, lenght: 40)
%br
%small.cgray= user.username
......@@ -99,6 +99,12 @@
.key c
%td
Go to commits
%tr
%td.shortcut
.key g
.key b
%td
Go to builds
%tr
%td.shortcut
.key g
......
......@@ -18,7 +18,7 @@
= render partial: 'layouts/collapse_button'
- if current_user
= link_to current_user, class: 'sidebar-user' do
= image_tag avatar_icon(current_user.email, 60), alt: 'User activity', class: 'avatar avatar s36'
= image_tag avatar_icon(current_user, 60), alt: 'User activity', class: 'avatar avatar s36'
.username
= current_user.username
.content-wrapper
......
......@@ -15,7 +15,7 @@
= render partial: 'layouts/collapse_button'
- if current_user
= link_to current_user, class: 'sidebar-user' do
= image_tag avatar_icon(current_user.email, 60), alt: 'User activity', class: 'avatar avatar s36'
= image_tag avatar_icon(current_user, 60), alt: 'User activity', class: 'avatar avatar s36'
.username
= current_user.username
.content-wrapper
......
......@@ -32,12 +32,20 @@
Files
- if project_nav_tab? :commits
= nav_link(controller: %w(commit commits compare repositories tags branches builds)) do
= nav_link(controller: %w(commit commits compare repositories tags branches)) do
= link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits', data: {placement: 'right'} do
= icon('history fw')
%span
Commits
- if project_nav_tab? :builds
= nav_link(controller: %w(builds)) do
= link_to project_builds_path(@project), title: 'Builds', class: 'shortcuts-builds', data: {placement: 'right'} do
= icon('cubes fw')
%span
Builds
%span.count.builds_counter= @project.ci_builds.running_or_pending.count(:all)
- if project_nav_tab? :network
= nav_link(controller: %w(network)) do
= link_to namespace_project_network_path(@project.namespace, @project, current_ref), title: 'Network', class: 'shortcuts-network', data: {placement: 'right'} do
......
......@@ -68,7 +68,7 @@
.col-md-5
.light-well
= image_tag avatar_icon(@user.email, 160), alt: '', class: 'avatar s160'
= image_tag avatar_icon(@user, 160), alt: '', class: 'avatar s160'
.clearfix
.profile-avatar-form-option
......
.project-last-commit
- ci_commit = project.ci_commit(commit.sha)
- if ci_commit
= link_to ci_status_path(ci_commit), class: "ci-status ci-#{ci_commit.status}" do
= ci_status_icon(ci_commit)
= ci_commit.status
= link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id"
= link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message"
&middot;
#{time_ago_with_tooltip(commit.committed_date, skip_js: true)} by
= commit_author_link(commit, avatar: true, size: 24)
......@@ -2,10 +2,10 @@
.md-header.clearfix
%ul.center-top-menu
%li.active
= link_to '#md-write-holder', class: 'js-md-write-button', tabindex: '-1' do
%a.js-md-write-button(href="#md-write-holder" tabindex="-1")
Write
%li
= link_to '#md-preview-holder', class: 'js-md-preview-button', tabindex: '-1' do
%a.js-md-preview-button(href="md-preview-holder" tabindex="-1")
Preview
- if defined?(referenced_users) && referenced_users
......
.zennable
%input#zen-toggle-comment.zen-toggle-comment{ tabindex: '-1', type: 'checkbox' }
%input#zen-toggle-comment.zen-toggle-comment(tabindex="-1" type="checkbox")
.zen-backdrop
- classes << ' js-gfm-input markdown-area'
= f.text_area attr, class: classes, placeholder: ''
= link_to nil, class: 'zen-enter-link', tabindex: '-1' do
%a.zen-enter-link(tabindex="-1" href="#")
%i.fa.fa-expand
Edit in fullscreen
= link_to nil, class: 'zen-leave-link' do
%a.zen-leave-link(href="#")
%i.fa.fa-compress
%tr.build
%td.status
= ci_status_with_icon(build.status)
%td.commit_status-link
- if build.target_url
= link_to build.target_url do
%strong Build ##{build.id}
- else
%strong Build ##{build.id}
- if build.show_warning?
%i.fa.fa-warning.text-warning
%td
= link_to build.short_sha, namespace_project_commit_path(@project.namespace, @project, build.sha)
%td
= link_to build.ref, namespace_project_commits_path(@project.namespace, @project, build.ref)
%td
- if build.runner
= runner_link(build.runner)
- else
.light none
%td
= build.name
.pull-right
- if build.tags.any?
- build.tags.each do |tag|
%span.label.label-primary
= tag
- if build.trigger_request
%span.label.label-info triggered
- if build.allow_failure
%span.label.label-danger allowed to fail
%td.duration
- if build.duration
#{duration_in_words(build.finished_at, build.started_at)}
%td.timestamp
- if build.finished_at
%span #{time_ago_in_words build.finished_at} ago
%td
.pull-right
- if current_user && can?(current_user, :manage_builds, @project)
- if build.cancel_url
= link_to build.cancel_url, title: 'Cancel' do
%i.fa.fa-remove.cred
- page_title "Builds"
- header_title project_title(@project, "Builds", project_builds_path(@project))
.project-issuable-filter
.controls
- if @ci_project && current_user && can?(current_user, :manage_builds, @project)
.pull-left.hidden-xs
- if @all_builds.running_or_pending.any?
= link_to 'Cancel all', cancel_all_namespace_project_builds_path(@project.namespace, @project), data: { confirm: 'Are you sure?' }, class: 'btn btn-danger'
%ul.center-top-menu
%li{class: ('active' if @scope.nil?)}
= link_to project_builds_path(@project) do
Running
%span.badge.js-running-count= @all_builds.running_or_pending.count(:id)
%li{class: ('active' if @scope == 'finished')}
= link_to project_builds_path(@project, scope: :finished) do
Finished
%span.badge.js-running-count= @all_builds.finished.count(:id)
%li{class: ('active' if @scope == 'all')}
= link_to project_builds_path(@project, scope: :all) do
All
%span.badge.js-totalbuilds-count= @all_builds.count(:id)
.gray-content-block
List of #{@scope || 'running'} builds from this project
%ul.content-list
- if @builds.blank?
%li
.nothing-here-block No builds to show
- else
%table.table.builds
%thead
%tr
%th Status
%th Build ID
%th Commit
%th Ref
%th Runner
%th Name
%th Duration
%th Finished at
%th
- @builds.each do |build|
= render 'projects/builds/build', build: build
= paginate @builds
......@@ -25,7 +25,7 @@
%a
Build ##{@build.id}
&middot;
%i.fa.fa-warning-sign
%i.fa.fa-warning
This build was retried.
.gray-content-block.second-block
......@@ -39,6 +39,25 @@
.pull-right
= @build.updated_at.stamp('19:00 Aug 27')
- if @build.show_warning?
- unless @build.any_runners_online?
.bs-callout.bs-callout-warning
%p
- if no_runners_for_project?(@build.project)
This build is stuck, because the project doesn't have any runners online assigned to it.
- elsif @build.tags.any?
This build is stuck, because you don't have any active runners online with any of these tags assigned to them:
- @build.tags.each do |tag|
%span.label.label-primary
= tag
- else
This build is stuck, because you don't have any active runners that can run this build.
%br
Go to
= link_to namespace_project_runners_path(@build.gl_project.namespace, @build.gl_project) do
Runners page
.row.prepend-top-default
.col-md-9
.clearfix
......
......@@ -3,11 +3,6 @@
= render "commit_box"
= render "ci_menu"
- if @ci_project && current_user && can?(current_user, :manage_builds, @project)
.pull-right
- if @ci_commit.builds.running_or_pending.any?
= link_to "Cancel", cancel_builds_namespace_project_commit_path(@project.namespace, @project, @commit.sha), class: 'btn btn-sm btn-danger'
- if @ci_commit.yaml_errors.present?
.bs-callout.bs-callout-danger
......@@ -22,11 +17,18 @@
.gray-content-block.second-block
Latest builds
.pull-right
- if @ci_commit.duration > 0
%small.pull-right
%i.fa.fa-time
#{time_interval_in_words @ci_commit.duration}
&nbsp;
- if @ci_project && current_user && can?(current_user, :manage_builds, @project)
- if @ci_commit.builds.running_or_pending.any?
= link_to "Cancel all", cancel_builds_namespace_project_commit_path(@project.namespace, @project, @commit.sha), class: 'btn btn-xs btn-danger'
%table.table.builds
%thead
%tr
......@@ -41,7 +43,8 @@
%th Coverage
%th
- @ci_commit.refs.each do |ref|
= render partial: "projects/commit_statuses/commit_status", collection: @ci_commit.statuses.for_ref(ref).latest.ordered, coverage: @ci_project.try(:coverage_enabled?), controls: true
= render partial: "projects/commit_statuses/commit_status", collection: @ci_commit.statuses.for_ref(ref).latest.ordered,
locals: { coverage: @ci_project.try(:coverage_enabled?), allow_retry: true }
- if @ci_commit.retried.any?
.gray-content-block.second-block
......@@ -60,4 +63,5 @@
- if @ci_project && @ci_project.coverage_enabled?
%th Coverage
%th
= render partial: "projects/commit_statuses/commit_status", collection: @ci_commit.retried, coverage: @ci_project.try(:coverage_enabled?)
= render partial: "projects/commit_statuses/commit_status", collection: @ci_commit.retried,
locals: { coverage: @ci_project.try(:coverage_enabled?) }
......@@ -9,6 +9,9 @@
- else
%strong Build ##{commit_status.id}
- if commit_status.show_warning?
%i.fa.fa-warning.text-warning
%td
= commit_status.ref
......@@ -41,11 +44,11 @@
#{commit_status.coverage}%
%td
- if defined?(controls) && controls && current_user && can?(current_user, :manage_builds, gl_project)
.pull-right
- if current_user && can?(current_user, :manage_builds, commit_status.gl_project)
- if commit_status.cancel_url
= link_to commit_status.cancel_url, title: 'Cancel' do
%i.fa.fa-remove.cred
- elsif commit_status.retry_url
- elsif defined?(allow_retry) && allow_retry && commit_status.retry_url
= link_to commit_status.retry_url, method: :post, title: 'Retry' do
%i.fa.fa-repeat
......@@ -17,6 +17,6 @@
This URL must be publicly accessible or you can add a username and password like this: https://username:password@gitlab.com/company/project.git.
%br
The import will time out after 4 minutes. For big repositories, use a clone/push combination.
For SVN repositories, check #{link_to "this migrating from SVN doc.", "http://doc.gitlab.com/ce/workflow/migrating_from_svn.html"}
For SVN repositories, check #{link_to "this migrating from SVN doc.", "http://doc.gitlab.com/ce/workflow/importing/migrating_from_svn.html"}
.form-actions
= f.submit 'Start import', class: "btn btn-create", tabindex: 4
......@@ -36,6 +36,7 @@
- if @merge_request.open? && @merge_request.can_be_merged?
.light.append-bottom-20
You can also accept this merge request manually using the
= succeed '.' do
= link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal"
- if @commits.present?
......
%li{ id: dom_id(issue, 'sortable'), class: 'issue-row', 'data-iid' => issue.iid, 'data-url' => issue_path(issue) }
.pull-right.assignee-icon
- if issue.assignee
= image_tag avatar_icon(issue.assignee.email, 16), class: "avatar s16", alt: ''
= image_tag avatar_icon(issue.assignee, 16), class: "avatar s16", alt: ''
%span
= link_to [@project.namespace.becomes(Namespace), @project, issue] do
%span.cgray ##{issue.iid}
......
......@@ -5,4 +5,4 @@
= link_to_gfm merge_request.title, [@project.namespace.becomes(Namespace), @project, merge_request], title: merge_request.title
.pull-right.assignee-icon
- if merge_request.assignee
= image_tag avatar_icon(merge_request.assignee.email, 16), class: "avatar s16", alt: ''
= image_tag avatar_icon(merge_request.assignee, 16), class: "avatar s16", alt: ''
......@@ -104,7 +104,7 @@
- @users.each do |user|
%li
= link_to user, title: user.name, class: "darken" do
= image_tag avatar_icon(user.email, 32), class: "avatar s32"
= image_tag avatar_icon(user, 32), class: "avatar s32"
%strong= truncate(user.name, lenght: 40)
%br
%small.cgray= user.username
%li.timeline-entry{ id: dom_id(note), class: [dom_class(note), "note-row-#{note.id}", ('system-note' if note.system)], data: { discussion: note.discussion_id } }
.timeline-entry-inner
.timeline-icon
= link_to user_path(note.author) do
= image_tag avatar_icon(note.author_email), class: 'avatar s40', alt: ''
%a{href: user_path(note.author)}
%img.avatar.s40{src: avatar_icon(note.author), alt: ''}
.timeline-content
.note-header
- if note_editable?(note)
......@@ -25,7 +25,7 @@
= '@' + note.author.username
%span.note-last-update
= link_to "##{dom_id(note)}", name: dom_id(note), title: "Link here" do
%a{name: dom_id(note), href: "##{dom_id(note)}", title: 'Link here'}
= time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note_created_ago')
- if note.updated_at != note.created_at
%span
......
......@@ -4,7 +4,7 @@
%li{class: "#{dom_class(member)} js-toggle-container project_member_row access-#{member.human_access.downcase}", id: dom_id(member)}
%span.list-item-name
- if member.user
= image_tag avatar_icon(user.email, 16), class: "avatar s16", alt: ''
= image_tag avatar_icon(user, 16), class: "avatar s16", alt: ''
%strong
= link_to user.name, user_path(user)
%span.cgray= user.username
......
......@@ -64,6 +64,10 @@
= icon("exclamation-triangle fw")
Archived project! Repository is read-only
- if @repository.commit
.content-block.second-block.white
= render 'projects/last_commit', commit: @repository.commit, project: @project
%section
- if prefer_readme?
.project-show-readme
......
......@@ -42,9 +42,8 @@
class: 'select2 trigger-submit', include_blank: true,
data: {placeholder: 'Milestone'})
- if @project
.filter-item.inline.labels-filter
= select_tag('label_name', project_labels_options(@project),
= select_tag('label_name', projects_labels_options,
class: 'select2 trigger-submit', include_blank: true,
data: {placeholder: 'Label'})
......
......@@ -9,8 +9,8 @@
.row
%section.col-md-7
.header-with-avatar
= link_to avatar_icon(@user.email, 400), target: '_blank' do
= image_tag avatar_icon(@user.email, 90), class: "avatar avatar-tile s90", alt: ''
= link_to avatar_icon(@user, 400), target: '_blank' do
= image_tag avatar_icon(@user, 90), class: "avatar avatar-tile s90", alt: ''
%h3
= @user.name
- if @user == current_user
......
......@@ -99,7 +99,29 @@ production: &base
# For documentation on how to set this up, see http://doc.gitlab.com/ce/incoming_email/README.html
incoming_email:
enabled: false
address: "incoming+%{key}@gitlab.example.com"
# The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to.
# The `%{key}` placeholder is added after the user part, after a `+` character, before the `@`.
address: "gitlab-incoming+%{key}@gmail.com"
# Email account username
# With third party providers, this is usually the full email address.
# With self-hosted email servers, this is usually the user part of the email address.
user: "gitlab-incoming@gmail.com"
# Email account password
password: "[REDACTED]"
# IMAP server host
host: "imap.gmail.com"
# IMAP server port
port: 993
# Whether the IMAP server uses SSL
ssl: true
# Whether the IMAP server uses StartTLS
start_tls: false
# The mailbox where incoming mail will end up. Usually "inbox".
mailbox: "inbox"
## Gravatar
## For Libravatar see: http://doc.gitlab.com/ce/customization/libravatar.html
......
......@@ -188,6 +188,10 @@ Settings.gitlab_ci['builds_path'] = File.expand_path(Settings.gitlab_ci[
#
Settings['incoming_email'] ||= Settingslogic.new({})
Settings.incoming_email['enabled'] = false if Settings.incoming_email['enabled'].nil?
Settings.incoming_email['port'] = 143 if Settings.incoming_email['port'].nil?
Settings.incoming_email['ssl'] = 143 if Settings.incoming_email['ssl'].nil?
Settings.incoming_email['start_tls'] = 143 if Settings.incoming_email['start_tls'].nil?
Settings.incoming_email['mailbox'] = "inbox" if Settings.incoming_email['mailbox'].nil?
#
# Gravatar
......
if ENV['ENABLE_QUERY_TRACE']
require 'active_record_query_trace'
ActiveRecordQueryTrace.enabled = 'true'
end
if ENV['ENABLE_BULLET']
require 'bullet'
Bullet.enable = true
Bullet.console = true
end
# The default colors of rack-lineprof can be very hard to look at in terminals
# with darker backgrounds. This patch tweaks the colors a bit so the output is
# actually readable.
if Rails.env.development? and RUBY_ENGINE == 'ruby' and ENV['ENABLE_LINEPROF']
Gitlab::Application.config.middleware.use(Rack::Lineprof)
module Rack
class Lineprof
class Sample < Rack::Lineprof::Sample.superclass
def format(*)
formatted = if level == CONTEXT
sprintf " | % 3i %s", line, code
else
sprintf "% 8.1fms %5i | % 3i %s", ms, calls, line, code
end
case level
when CRITICAL
color.red formatted
when WARNING
color.yellow formatted
when NOMINAL
color.white formatted
else # CONTEXT
formatted
end
end
end
end
end
end
:mailboxes:
<%
require_relative 'config/environment.rb'
if Gitlab::IncomingEmail.enabled?
config = Gitlab::IncomingEmail.config
redis_config_file = "config/resque.yml"
redis_url =
if File.exists?(redis_config_file)
YAML.load_file(redis_config_file)[Rails.env]
else
"redis://localhost:6379"
end
%>
-
:host: <%= config.host.to_json %>
:port: <%= config.port.to_json %>
:ssl: <%= config.ssl.to_json %>
:start_tls: <%= config.start_tls.to_json %>
:email: <%= config.user.to_json %>
:password: <%= config.password.to_json %>
:name: <%= config.mailbox.to_json %>
:delete_after_delivery: true
:delivery_method: sidekiq
:delivery_options:
:redis_url: <%= redis_url.to_json %>
:namespace: resque:gitlab
:queue: incoming_email
:worker: EmailReceiverWorker
:arbitration_method: redis
:arbitration_options:
:redis_url: <%= redis_url.to_json %>
:namespace: mail_room:gitlab
<% end %>
:mailboxes:
-
# # IMAP server host
# :host: "imap.gmail.com"
# # IMAP server port
# :port: 993
# # Whether the IMAP server uses SSL
# :ssl: true
# # Whether the IMAP server uses StartTLS
# :start_tls: false
# # Email account username. Usually the full email address.
# :email: "gitlab-incoming@gmail.com"
# # Email account password
# :password: "password"
# # The name of the mailbox where incoming mail will end up. Usually "inbox".
# :name: "inbox"
# # Always "sidekiq".
# :delivery_method: sidekiq
# # Always true.
# :delete_after_delivery: true
# :delivery_options:
# # The URL to the Redis server used by Sidekiq. Should match the URL in config/resque.yml.
# :redis_url: redis://localhost:6379
# # Always "resque:gitlab".
# :namespace: resque:gitlab
# # Always "incoming_email".
# :queue: incoming_email
# # Always "EmailReceiverWorker".
# :worker: EmailReceiverWorker
# # Always "redis".
# :arbitration_method: redis
# :arbitration_options:
# # The URL to the Redis server. Should match the URL in config/resque.yml.
# :redis_url: redis://localhost:6379
# # Always "mail_room:gitlab".
# :namespace: mail_room:gitlab
......@@ -543,8 +543,10 @@ Gitlab::Application.routes.draw do
member do
# tree viewer logs
get 'logs_tree', constraints: { id: Gitlab::Regex.git_reference_regex }
# Directories with leading dots erroneously get rejected if git
# ref regex used in constraints. Regex verification now done in controller.
get 'logs_tree/*path' => 'refs#logs_tree', as: :logs_file, constraints: {
id: Gitlab::Regex.git_reference_regex,
id: /.*/,
path: /.*/
}
end
......@@ -585,7 +587,11 @@ Gitlab::Application.routes.draw do
end
end
resources :builds, only: [:show] do
resources :builds, only: [:index, :show] do
collection do
get :cancel_all
end
member do
get :cancel
get :status
......
class AddUsersLowerUsernameEmailIndexes < ActiveRecord::Migration
disable_ddl_transaction!
def up
return unless Gitlab::Database.postgresql?
execute 'CREATE INDEX CONCURRENTLY index_on_users_lower_username ON users (LOWER(username));'
execute 'CREATE INDEX CONCURRENTLY index_on_users_lower_email ON users (LOWER(email));'
end
def down
return unless Gitlab::Database.postgresql?
remove_index :users, :index_on_users_lower_username
remove_index :users, :index_on_users_lower_email
end
end
class AddCiProjectsGlProjectIdIndex < ActiveRecord::Migration
def change
add_index :ci_commits, :gl_project_id
end
end
class AddCiBuildsAndProjectsIndexes < ActiveRecord::Migration
def change
add_index :ci_projects, :gitlab_id
add_index :ci_projects, :shared_runners_enabled
add_index :ci_builds, :type
add_index :ci_builds, :status
end
end
class AddNotesLineCodeIndex < ActiveRecord::Migration
def change
add_index :notes, :line_code
end
end
......@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20151008130321) do
ActiveRecord::Schema.define(version: 20151016195706) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
......@@ -115,6 +115,8 @@ ActiveRecord::Schema.define(version: 20151008130321) do
add_index "ci_builds", ["project_id", "commit_id"], name: "index_ci_builds_on_project_id_and_commit_id", using: :btree
add_index "ci_builds", ["project_id"], name: "index_ci_builds_on_project_id", using: :btree
add_index "ci_builds", ["runner_id"], name: "index_ci_builds_on_runner_id", using: :btree
add_index "ci_builds", ["status"], name: "index_ci_builds_on_status", using: :btree
add_index "ci_builds", ["type"], name: "index_ci_builds_on_type", using: :btree
create_table "ci_commits", force: true do |t|
t.integer "project_id"
......@@ -130,6 +132,7 @@ ActiveRecord::Schema.define(version: 20151008130321) do
t.integer "gl_project_id"
end
add_index "ci_commits", ["gl_project_id"], name: "index_ci_commits_on_gl_project_id", using: :btree
add_index "ci_commits", ["project_id", "committed_at", "id"], name: "index_ci_commits_on_project_id_and_committed_at_and_id", using: :btree
add_index "ci_commits", ["project_id", "committed_at"], name: "index_ci_commits_on_project_id_and_committed_at", using: :btree
add_index "ci_commits", ["project_id", "sha"], name: "index_ci_commits_on_project_id_and_sha", using: :btree
......@@ -189,6 +192,9 @@ ActiveRecord::Schema.define(version: 20151008130321) do
t.text "generated_yaml_config"
end
add_index "ci_projects", ["gitlab_id"], name: "index_ci_projects_on_gitlab_id", using: :btree
add_index "ci_projects", ["shared_runners_enabled"], name: "index_ci_projects_on_shared_runners_enabled", using: :btree
create_table "ci_runner_projects", force: true do |t|
t.integer "runner_id", null: false
t.integer "project_id", null: false
......@@ -529,6 +535,7 @@ ActiveRecord::Schema.define(version: 20151008130321) do
add_index "notes", ["commit_id"], name: "index_notes_on_commit_id", using: :btree
add_index "notes", ["created_at", "id"], name: "index_notes_on_created_at_and_id", using: :btree
add_index "notes", ["created_at"], name: "index_notes_on_created_at", using: :btree
add_index "notes", ["line_code"], name: "index_notes_on_line_code", using: :btree
add_index "notes", ["noteable_id", "noteable_type"], name: "index_notes_on_noteable_id_and_noteable_type", using: :btree
add_index "notes", ["noteable_type"], name: "index_notes_on_noteable_type", using: :btree
add_index "notes", ["project_id", "noteable_type"], name: "index_notes_on_project_id_and_noteable_type", using: :btree
......
......@@ -15,21 +15,27 @@ The API_TOKEN will take the Secure Variable value: `SECURE`.
### Predefined variables (Environment Variables)
| Variable | Description |
| Variable | Runner | Description |
|-------------------------|-------------|
| **CI** | Mark that build is executed in CI environment |
| **GITLAB_CI** | Mark that build is executed in GitLab CI environment |
| **CI_SERVER** | Mark that build is executed in CI environment |
| **CI_SERVER_NAME** | CI server that is used to coordinate builds |
| **CI_SERVER_VERSION** | Not yet defined |
| **CI_SERVER_REVISION** | Not yet defined |
| **CI_BUILD_REF** | The commit revision for which project is built |
| **CI_BUILD_BEFORE_SHA** | The first commit that were included in push request |
| **CI_BUILD_REF_NAME** | The branch or tag name for which project is built |
| **CI_BUILD_ID** | The unique id of the current build that GitLab CI uses internally |
| **CI_BUILD_REPO** | The URL to clone the Git repository |
| **CI_PROJECT_ID** | The unique id of the current project that GitLab CI uses internally |
| **CI_PROJECT_DIR** | The full path where the repository is cloned and where the build is ran |
| **CI** | 0.4 | Mark that build is executed in CI environment |
| **GITLAB_CI** | all | Mark that build is executed in GitLab CI environment |
| **CI_SERVER** | all | Mark that build is executed in CI environment |
| **CI_SERVER_NAME** | all | CI server that is used to coordinate builds |
| **CI_SERVER_VERSION** | all | Not yet defined |
| **CI_SERVER_REVISION** | all | Not yet defined |
| **CI_BUILD_REF** | all | The commit revision for which project is built |
| **CI_BUILD_TAG** | 0.5 | The commit tag name. Present only when building tags. |
| **CI_BUILD_NAME** | 0.5 | The name of the build as defined in `.gitlab-ci.yml` |
| **CI_BUILD_STAGE** | 0.5 | The name of the stage as defined in `.gitlab-ci.yml` |
| **CI_BUILD_BEFORE_SHA** | all | The first commit that were included in push request |
| **CI_BUILD_REF_NAME** | all | The branch or tag name for which project is built |
| **CI_BUILD_ID** | all | The unique id of the current build that GitLab CI uses internally |
| **CI_BUILD_REPO** | all | The URL to clone the Git repository |
| **CI_BUILD_TRIGGERED** | 0.5 | The flag to indicate that build was triggered |
| **CI_PROJECT_ID** | all | The unique id of the current project that GitLab CI uses internally |
| **CI_PROJECT_DIR** | all | The full path where the repository is cloned and where the build is ran |
**Some of the variables are only available when using runner with at least defined version.**
Example values:
......@@ -39,6 +45,10 @@ export CI_BUILD_ID="50"
export CI_BUILD_REF="1ecfd275763eff1d6b4844ea3168962458c9f27a"
export CI_BUILD_REF_NAME="master"
export CI_BUILD_REPO="https://gitlab.com/gitlab-org/gitlab-ce.git"
export CI_BUILD_TAG="1.0.0"
export CI_BUILD_NAME="spec:other"
export CI_BUILD_STAGE="test"
export CI_BUILD_TRIGGERED="true"
export CI_PROJECT_DIR="/builds/gitlab-org/gitlab-ce"
export CI_PROJECT_ID="34"
export CI_SERVER="yes"
......
......@@ -140,6 +140,7 @@ job_name:
| except | optional | Defines a list of git refs for which build is not created |
| tags | optional | Defines a list of tags which are used to select runner |
| allow_failure | optional | Allow build to fail. Failed build doesn't contribute to commit status |
| when | optional | Define when to run build. Can be `on_success`, `on_failure` or `always` |
### script
`script` is a shell script which is executed by runner. The shell script is prepended with `before_script`.
......@@ -196,6 +197,55 @@ job:
The above specification will make sure that `job` is built by a runner that have `ruby` AND `postgres` tags defined.
### when
`when` is used to implement jobs that are run in case of failure or despite the failure.
`when` can be set to one of the following values:
1. `on_success` - execute build only when all builds from prior stages succeeded. This is the default.
1. `on_failure` - execute build only when at least one build from prior stages failed.
1. `always` - execute build despite the status of builds from prior stages.
```
stages:
- build
- cleanup_build
- test
- deploy
- cleanup
build:
stage: build
script:
- make build
cleanup_build:
stage: cleanup_build
script:
- cleanup build when failed
when: on_failure
test:
stage: test
script:
- make test
deploy:
stage: deploy
script:
- make deploy
cleanup:
stage: cleanup
script:
- cleanup after builds
when: always
```
The above script will:
1. Execute `cleanup_build` only when the `build` failed,
2. Always execute `cleanup` as the last step in pipeline.
## Validate the .gitlab-ci.yml
Each instance of GitLab CI has an embedded debug tool called Lint.
You can find the link to the Lint in the project's settings page or use short url `/lint`.
......
# Profiling
To make it easier to track down performance problems GitLab comes with a set of
profiling tools, some of these are available by default while others need to be
explicitly enabled.
## rack-mini-profiler
This Gem is enabled by default in development only. It allows you to see the
timings of the various components that made up a web request (e.g. the SQL
queries executed and their execution timings).
## Bullet
Bullet is a Gem that can be used to track down N+1 query problems. Because
Bullet adds quite a bit of logging noise it's disabled by default. To enable
Bullet, set the environment variable `ENABLE_BULLET` to a non-empty value before
starting GitLab. For example:
ENABLE_BULLET=true bundle exec rails s
Bullet will log query problems to both the Rails log as well as the Chrome
console.
## ActiveRecord Query Trace
This Gem adds backtraces for every ActiveRecord query in the Rails console. This
can be useful to track down where a query was executed. Because this Gem adds
quite a bit of noise (5-10 extra lines per ActiveRecord query) it's disabled by
default. To use this Gem you'll need to set `ENABLE_QUERY_TRACE` to a non empty
file before starting GitLab. For example:
ENABLE_QUERY_TRACE=true bundle exec rails s
## rack-lineprof
This is a Gem that can trace the execution time of code on a per line basis.
Because this Gem can add quite a bit of overhead it's disabled by default. To
enable it, set the environment variable `ENABLE_LINEPROF` to a non-empty value.
For example:
ENABLE_LINEPROF=true bundle exec rails s
Once enabled you'll need to add a query string parameter to a request to
actually profile code execution. The name of the parameter is `lineprof` and
should be set to a regular expression (minus the starting/ending slash) used to
select what files to profile. To profile all files containing "foo" somewhere in
the path you'd use the following parameter:
?lineprof=foo
Or when filtering for files containing "foo" and "bar" in their path:
?lineprof=foo|bar
Once set the profiling output will be displayed in your terminal.
......@@ -4,9 +4,9 @@ GitLab can be set up to allow users to comment on issues and merge requests by r
## Get a mailbox
Reply by email requires an IMAP-enabled email account, with a provider or server that supports [email sub-addressing](https://en.wikipedia.org/wiki/Email_address#Sub-addressing). Sub-addressing is a feature where any email to `user+some_arbitrary_tag@example.com` will end up in the mailbox for `user@example.com`, and is supported by providers such as Gmail, Yahoo! Mail, Outlook.com and iCloud, as well as the Postfix mail server which you can run on-premises.
Reply by email requires an IMAP-enabled email account, with a provider or server that supports [email sub-addressing](https://en.wikipedia.org/wiki/Email_address#Sub-addressing). Sub-addressing is a feature where any email to `user+some_arbitrary_tag@example.com` will end up in the mailbox for `user@example.com`, and is supported by providers such as Gmail, Google Apps, Yahoo! Mail, Outlook.com and iCloud, as well as the Postfix mail server which you can run on-premises.
If you want to use Gmail with Reply by email, make sure you have [IMAP access enabled](https://support.google.com/mail/troubleshooter/1668960?hl=en#ts=1665018) and [allow less secure apps to access the account](https://support.google.com/accounts/answer/6010255).
If you want to use Gmail / Google Apps with Reply by email, make sure you have [IMAP access enabled](https://support.google.com/mail/troubleshooter/1668960?hl=en#ts=1665018) and [allow less secure apps to access the account](https://support.google.com/accounts/answer/6010255).
To set up a basic Postfix mail server with IMAP access on Ubuntu, follow [these instructions](./postfix.md).
......@@ -14,30 +14,62 @@ To set up a basic Postfix mail server with IMAP access on Ubuntu, follow [these
### Omnibus package installations
1. Find the `incoming_email` section in `/etc/gitlab/gitlab.rb`, enable the feature, enter the email address including a placeholder for the `key` that references the item being replied to and fill in the details for your specific IMAP server and email account:
1. Find the `incoming_email` section in `/etc/gitlab/gitlab.rb`, enable the feature and fill in the details for your specific IMAP server and email account:
```ruby
# Postfix mail server, assumes mailbox incoming@gitlab.example.com
# Configuration for Postfix mail server, assumes mailbox incoming@gitlab.example.com
gitlab_rails['incoming_email_enabled'] = true
# The email address including a placeholder for the key that references the item being replied to.
# The `%{key}` placeholder is added after the user part, before the `@`.
gitlab_rails['incoming_email_address'] = "incoming+%{key}@gitlab.example.com"
gitlab_rails['incoming_email_host'] = "gitlab.example.com" # IMAP server host
gitlab_rails['incoming_email_port'] = 143 # IMAP server port
gitlab_rails['incoming_email_ssl'] = false # Whether the IMAP server uses SSL
gitlab_rails['incoming_email_email'] = "incoming" # Email account username. Usually the full email address.
gitlab_rails['incoming_email_password'] = "[REDACTED]" # Email account password
gitlab_rails['incoming_email_mailbox_name'] = "inbox" # The name of the mailbox where incoming mail will end up. Usually "inbox".
# Email account username
# With third party providers, this is usually the full email address.
# With self-hosted email servers, this is usually the user part of the email address.
gitlab_rails['incoming_email_email'] = "incoming"
# Email account password
gitlab_rails['incoming_email_password'] = "[REDACTED]"
# IMAP server host
gitlab_rails['incoming_email_host'] = "gitlab.example.com"
# IMAP server port
gitlab_rails['incoming_email_port'] = 143
# Whether the IMAP server uses SSL
gitlab_rails['incoming_email_ssl'] = false
# Whether the IMAP server uses StartTLS
gitlab_rails['incoming_email_start_tls'] = false
# The mailbox where incoming mail will end up. Usually "inbox".
gitlab_rails['incoming_email_mailbox_name'] = "inbox"
```
```ruby
# Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com
# Configuration for Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com
gitlab_rails['incoming_email_enabled'] = true
# The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to.
# The `%{key}` placeholder is added after the user part, after a `+` character, before the `@`.
gitlab_rails['incoming_email_address'] = "gitlab-incoming+%{key}@gmail.com"
gitlab_rails['incoming_email_host'] = "imap.gmail.com" # IMAP server host
gitlab_rails['incoming_email_port'] = 993 # IMAP server port
gitlab_rails['incoming_email_ssl'] = true # Whether the IMAP server uses SSL
gitlab_rails['incoming_email_email'] = "gitlab-incoming@gmail.com" # Email account username. Usually the full email address.
gitlab_rails['incoming_email_password'] = "[REDACTED]" # Email account password
gitlab_rails['incoming_email_mailbox_name'] = "inbox" # The name of the mailbox where incoming mail will end up. Usually "inbox".
# Email account username
# With third party providers, this is usually the full email address.
# With self-hosted email servers, this is usually the user part of the email address.
gitlab_rails['incoming_email_email'] = "gitlab-incoming@gmail.com"
# Email account password
gitlab_rails['incoming_email_password'] = "[REDACTED]"
# IMAP server host
gitlab_rails['incoming_email_host'] = "imap.gmail.com"
# IMAP server port
gitlab_rails['incoming_email_port'] = 993
# Whether the IMAP server uses SSL
gitlab_rails['incoming_email_ssl'] = true
# Whether the IMAP server uses StartTLS
gitlab_rails['incoming_email_start_tls'] = false
# The mailbox where incoming mail will end up. Usually "inbox".
gitlab_rails['incoming_email_mailbox_name'] = "inbox"
```
As mentioned, the part after `+` in the address is ignored, and any email sent here will end up in the mailbox for `incoming@gitlab.example.com`/`gitlab-incoming@gmail.com`.
......@@ -64,229 +96,146 @@ To set up a basic Postfix mail server with IMAP access on Ubuntu, follow [these
cd /home/git/gitlab
```
1. Find the `incoming_email` section in `config/gitlab.yml`, enable the feature and enter the email address including a placeholder for the `key` that references the item being replied to:
1. Find the `incoming_email` section in `config/gitlab.yml`, enable the feature and fill in the details for your specific IMAP server and email account:
```sh
sudo editor config/gitlab.yml
```
```yaml
# Postfix mail server, assumes mailbox incoming@gitlab.example.com
# Configuration for Postfix mail server, assumes mailbox incoming@gitlab.example.com
incoming_email:
enabled: true
address: "incoming+%{key}@gitlab.example.com"
```
```yaml
# Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com
incoming_email:
enabled: true
address: "gitlab-incoming+%{key}@gmail.com"
```
As mentioned, the part after `+` in the address is ignored, and any email sent here will end up in the mailbox for `incoming@gitlab.example.com`/`gitlab-incoming@gmail.com`.
2. Copy `config/mail_room.yml.example` to `config/mail_room.yml`:
```sh
sudo cp config/mail_room.yml.example config/mail_room.yml
```
3. Uncomment the configuration options in `config/mail_room.yml` and fill in the details for your specific IMAP server and email account:
# The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to.
# The `%{key}` placeholder is added after the user part, after a `+` character, before the `@`.
address: "incoming+%{key}@gitlab.example.com"
```sh
sudo editor config/mail_room.yml
```
# Email account username
# With third party providers, this is usually the full email address.
# With self-hosted email servers, this is usually the user part of the email address.
user: "incoming"
# Email account password
password: "[REDACTED]"
```yaml
# Postfix mail server
:mailboxes:
-
# IMAP server host
:host: "gitlab.example.com"
host: "gitlab.example.com"
# IMAP server port
:port: 143
port: 143
# Whether the IMAP server uses SSL
:ssl: false
ssl: false
# Whether the IMAP server uses StartTLS
:start_tls: false
# Email account username. Usually the full email address.
:email: "incoming"
# Email account password
:password: "[REDACTED]"
# The name of the mailbox where incoming mail will end up. Usually "inbox".
:name: "inbox"
# Always "sidekiq".
:delivery_method: sidekiq
# Always true.
:delete_after_delivery: true
:delivery_options:
# The URL to the Redis server used by Sidekiq. Should match the URL in config/resque.yml.
:redis_url: redis://localhost:6379
# Always "resque:gitlab".
:namespace: resque:gitlab
# Always "incoming_email".
:queue: incoming_email
# Always "EmailReceiverWorker"
:worker: EmailReceiverWorker
# Always "redis".
:arbitration_method: redis
:arbitration_options:
# The URL to the Redis server. Should match the URL in config/resque.yml.
:redis_url: redis://localhost:6379
# Always "mail_room:gitlab".
:namespace: mail_room:gitlab
start_tls: false
# The mailbox where incoming mail will end up. Usually "inbox".
mailbox: "inbox"
```
```yaml
# Gmail / Google Apps
:mailboxes:
-
# Configuration for Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com
incoming_email:
enabled: true
# The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to.
# The `%{key}` placeholder is added after the user part, after a `+` character, before the `@`.
address: "gitlab-incoming+%{key}@gmail.com"
# Email account username
# With third party providers, this is usually the full email address.
# With self-hosted email servers, this is usually the user part of the email address.
user: "gitlab-incoming@gmail.com"
# Email account password
password: "[REDACTED]"
# IMAP server host
:host: "imap.gmail.com"
host: "imap.gmail.com"
# IMAP server port
:port: 993
port: 993
# Whether the IMAP server uses SSL
:ssl: true
ssl: true
# Whether the IMAP server uses StartTLS
:start_tls: false
# Email account username. Usually the full email address.
:email: "gitlab-incoming@gmail.com"
# Email account password
:password: "[REDACTED]"
# The name of the mailbox where incoming mail will end up. Usually "inbox".
:name: "inbox"
# Always "sidekiq".
:delivery_method: sidekiq
# Always true.
:delete_after_delivery: true
:delivery_options:
# The URL to the Redis server used by Sidekiq. Should match the URL in config/resque.yml.
:redis_url: redis://localhost:6379
# Always "resque:gitlab".
:namespace: resque:gitlab
# Always "incoming_email".
:queue: incoming_email
# Always "EmailReceiverWorker"
:worker: EmailReceiverWorker
# Always "redis".
:arbitration_method: redis
:arbitration_options:
# The URL to the Redis server. Should match the URL in config/resque.yml.
:redis_url: redis://localhost:6379
# Always "mail_room:gitlab".
:namespace: mail_room:gitlab
start_tls: false
# The mailbox where incoming mail will end up. Usually "inbox".
mailbox: "inbox"
```
5. Edit the init script configuration at `/etc/default/gitlab` to enable `mail_room`:
As mentioned, the part after `+` in the address is ignored, and any email sent here will end up in the mailbox for `incoming@gitlab.example.com`/`gitlab-incoming@gmail.com`.
1. Enable `mail_room` in the init script at `/etc/default/gitlab`:
```sh
sudo mkdir -p /etc/default
echo 'mail_room_enabled=true' | sudo tee -a /etc/default/gitlab
```
6. Restart GitLab:
1. Restart GitLab:
```sh
sudo service gitlab restart
```
7. Verify that everything is configured correctly:
1. Verify that everything is configured correctly:
```sh
sudo -u git -H bundle exec rake gitlab:incoming_email:check RAILS_ENV=production
```
8. Reply by email should now be working.
1. Reply by email should now be working.
### Development
1. Go to the GitLab installation directory.
1. Find the `incoming_email` section in `config/gitlab.yml`, enable the feature and enter the email address including a placeholder for the `key` that references the item being replied to:
1. Find the `incoming_email` section in `config/gitlab.yml`, enable the feature and fill in the details for your specific IMAP server and email account:
```yaml
# Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com
# Configuration for Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com
incoming_email:
enabled: true
address: "gitlab-incoming+%{key}@gmail.com"
```
As mentioned, the part after `+` is ignored, and this will end up in the mailbox for `gitlab-incoming@gmail.com`.
2. Copy `config/mail_room.yml.example` to `config/mail_room.yml`:
```sh
sudo cp config/mail_room.yml.example config/mail_room.yml
```
# The email address including a placeholder for the key that references the item being replied to.
# The `%{key}` placeholder is added after the user part, before the `@`.
address: "gitlab-incoming+%{key}@gmail.com"
3. Uncomment the configuration options in `config/mail_room.yml` and fill in the details for your specific IMAP server and email account:
# Email account username
# With third party providers, this is usually the full email address.
# With self-hosted email servers, this is usually the user part of the email address.
user: "gitlab-incoming@gmail.com"
# Email account password
password: "[REDACTED]"
```yaml
# Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com
:mailboxes:
-
# IMAP server host
:host: "imap.gmail.com"
host: "imap.gmail.com"
# IMAP server port
:port: 993
port: 993
# Whether the IMAP server uses SSL
:ssl: true
ssl: true
# Whether the IMAP server uses StartTLS
:start_tls: false
# Email account username. Usually the full email address.
:email: "gitlab-incoming@gmail.com"
# Email account password
:password: "[REDACTED]"
# The name of the mailbox where incoming mail will end up. Usually "inbox".
:name: "inbox"
# Always "sidekiq".
:delivery_method: sidekiq
# Always true.
:delete_after_delivery: true
:delivery_options:
# The URL to the Redis server used by Sidekiq. Should match the URL in config/resque.yml.
:redis_url: redis://localhost:6379
# Always "resque:gitlab".
:namespace: resque:gitlab
# Always "incoming_email".
:queue: incoming_email
# Always "EmailReceiverWorker"
:worker: EmailReceiverWorker
# Always "redis".
:arbitration_method: redis
:arbitration_options:
# The URL to the Redis server. Should match the URL in config/resque.yml.
:redis_url: redis://localhost:6379
# Always "mail_room:gitlab".
:namespace: mail_room:gitlab
start_tls: false
# The mailbox where incoming mail will end up. Usually "inbox".
mailbox: "inbox"
```
4. Uncomment the `mail_room` line in your `Procfile`:
As mentioned, the part after `+` is ignored, and this will end up in the mailbox for `gitlab-incoming@gmail.com`.
1. Uncomment the `mail_room` line in your `Procfile`:
```yaml
mail_room: bundle exec mail_room -q -c config/mail_room.yml
```
6. Restart GitLab:
1. Restart GitLab:
```sh
bundle exec foreman start
```
7. Verify that everything is configured correctly:
1. Verify that everything is configured correctly:
```sh
bundle exec rake gitlab:incoming_email:check RAILS_ENV=development
```
8. Reply by email should now be working.
1. Reply by email should now be working.
......@@ -211,9 +211,9 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
### Clone the Source
# Clone GitLab repository
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-0-stable gitlab
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-1-stable gitlab
**Note:** You can change `8-0-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
**Note:** You can change `8-1-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
### Configure It
......@@ -325,6 +325,7 @@ GitLab Shell is an SSH access and repository management software developed speci
cd /home/git
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-git-http-server.git
cd gitlab-git-http-server
sudo -u git -H git checkout 0.3.0
sudo -u git -H make
### Initialize Database and Activate Advanced Features
......
......@@ -16,7 +16,7 @@ You need to keep a separate copy of `/etc/gitlab/gitlab-secrets.json`
from source). This file contains the database encryption key used
for two-factor authentication. If you restore a GitLab backup without
restoring the database encryption key, users who have two-factor
authentication enabled will loose access to your GitLab server.
authentication enabled will lose access to your GitLab server.
If you are interested in GitLab CI backup please follow to the [CI backup documentation](https://gitlab.com/gitlab-org/gitlab-ci/blob/master/doc/raketasks/backup_restore.md)*
......
......@@ -84,6 +84,7 @@ Now we download `gitlab-git-http-server` and install it in `/home/git/gitlab-git
cd /home/git
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-git-http-server.git
cd gitlab-git-http-server
sudo -u git -H git checkout 0.2.14
sudo -u git -H make
```
......
# From 8.0 to 8.1
**NOTE:** GitLab 8.0 introduced several significant changes related to
installation and configuration which *are not duplicated here*. Be sure you're
already running a working version of 8.0 before proceeding with this guide.
### 0. Double-check your Git version
**This notice applies only to /usr/local/bin/git**
If you compiled Git from source on your GitLab server then please double-check
that you are using a version that protects against CVE-2014-9390. For six
months after this vulnerability became known the GitLab installation guide
still contained instructions that would install the outdated, 'vulnerable' Git
version 2.1.2.
Run the following command to get your current Git version:
```sh
/usr/local/bin/git --version
```
If you see 'No such file or directory' then you did not install Git according
to the outdated instructions from the GitLab installation guide and you can go
to the next step 'Stop server' below.
If you see a version string then it should be v1.8.5.6, v1.9.5, v2.0.5, v2.1.4,
v2.2.1 or newer. You can use the [instructions in the GitLab source
installation
guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md#1-packages-dependencies)
to install a newer version of Git.
### 1. Stop server
sudo service gitlab stop
### 2. Backup
```bash
cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
```
### 3. Get latest code
```bash
sudo -u git -H git fetch --all
sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
```
For GitLab Community Edition:
```bash
sudo -u git -H git checkout 8-1-stable
```
OR
For GitLab Enterprise Edition:
```bash
sudo -u git -H git checkout 8-1-stable-ee
```
### 4. Update gitlab-shell
```bash
cd /home/git/gitlab-shell
sudo -u git -H git fetch
sudo -u git -H git checkout v2.6.5
```
### 5. Install libs, migrations, etc.
```bash
cd /home/git/gitlab
# MySQL installations (note: the line below states '--without postgres')
sudo -u git -H bundle install --without postgres development test --deployment
# PostgreSQL installations (note: the line below states '--without mysql')
sudo -u git -H bundle install --without mysql development test --deployment
# Run database migrations
sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
# Clean up assets and cache
sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
# Update init.d script
sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
```
### 6. Update configuration files
#### New configuration options for `gitlab.yml`
There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them manually to your current `gitlab.yml`:
```sh
git diff origin/8-0-stable:config/gitlab.yml.example origin/8-1-stable:config/gitlab.yml.example
```
### 7. Start application
sudo service gitlab start
sudo service nginx restart
### 8. Check application status
Check if GitLab and its environment are configured correctly:
sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
To make sure you didn't miss anything run a more thorough check:
sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
If all items are green, then congratulations, the upgrade is complete!
## Things went south? Revert to previous version (8.0)
### 1. Revert the code to the previous version
Follow the [upgrade guide from 7.14 to 8.0](7.14-to-8.0.md), except for the database migration
(The backup is already migrated to the previous version)
### 2. Restore from the backup
```bash
cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
```
If you have more than one backup `*.tar` file(s) please add `BACKUP=timestamp_of_backup` to the command above.
## Troubleshooting
### "You appear to have cloned an empty repository."
See the [7.14 to 8.0 update guide](7.14-to-8.0.md#troubleshooting).
......@@ -7,24 +7,24 @@ Background:
And I click "New project" link
@javascript
Scenario: I should see New projects page
Then I see "New project" page
Scenario: I should see New Projects page
Then I see "New Project" page
Then I see all possible import optios
@javascript
Scenario: I should see instructions on how to import from Git URL
Given I see "New project" page
Given I see "New Project" page
When I click on "Any repo by URL"
Then I see instructions on how to import from Git URL
@javascript
Scenario: I should see instructions on how to import from GitHub
Given I see "New project" page
Given I see "New Project" page
When I click on "Import project from GitHub"
Then I see instructions on how to import from GitHub
@javascript
Scenario: I should see Google Code import page
Given I see "New project" page
Given I see "New Project" page
When I click on "Google Code"
Then I redirected to Google Code import page
......@@ -31,6 +31,12 @@ Feature: Project
And I visit project "Shop" page
Then I should see project "Shop" README
Scenario: I should see last commit with CI
Given project "Shop" has CI enabled
Given project "Shop" has CI build
And I visit project "Shop" page
And I should see last commit with CI status
@javascript
Scenario: I should see project activity
When I visit project "Shop" activity page
......
......@@ -205,3 +205,9 @@ Feature: Project Source Browse Files
And I see the ref 'test' has been selected
And I visit the 'test' tree
Then I see the commit data
@javascript
Scenario: I browse code with a leading dot in the directory
Given I switch ref to fix
And I visit the fix tree
Then I see the commit data for a directory with a leading dot
......@@ -41,6 +41,8 @@ class Spinach::Features::AdminProjects < Spinach::FeatureSteps
end
step 'I transfer project to group \'Web\'' do
allow_any_instance_of(Projects::TransferService).
to receive(:move_uploads_to_new_namespace).and_return(true)
find(:xpath, "//input[@id='new_namespace_id']").set group.id
click_button 'Transfer'
end
......
......@@ -3,13 +3,13 @@ class Spinach::Features::NewProject < Spinach::FeatureSteps
include SharedPaths
include SharedProject
step 'I click "New project" link' do
step 'I click "New Project" link' do
page.within('.content') do
click_link "New project"
click_link "New Project"
end
end
step 'I see "New project" page' do
step 'I see "New Project" page' do
expect(page).to have_content('Project path')
end
......
......@@ -113,7 +113,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
end
step 'I click status link' do
click_link "Builds"
find('.commit-ci-menu').click_link "Builds"
end
step 'I see builds list' do
......
......@@ -286,6 +286,10 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
select "'test'", from: 'ref'
end
step "I switch ref to fix" do
select "fix", from: 'ref'
end
step "I see the ref 'test' has been selected" do
expect(page).to have_selector '.select2-chosen', text: "'test'"
end
......@@ -294,11 +298,20 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
visit namespace_project_tree_path(@project.namespace, @project, "'test'")
end
step "I visit the fix tree" do
visit namespace_project_tree_path(@project.namespace, @project, "fix/.testdir")
end
step 'I see the commit data' do
expect(page).to have_css('.tree-commit-link', visible: true)
expect(page).not_to have_content('Loading commit data...')
end
step 'I see the commit data for a directory with a leading dot' do
expect(page).to have_css('.tree-commit-link', visible: true)
expect(page).not_to have_content('Loading commit data...')
end
private
def set_new_content
......
......@@ -206,4 +206,11 @@ module SharedProject
project = Project.find_by(name: "Shop")
create :ci_commit, gl_project: project, sha: project.commit.sha
end
step 'I should see last commit with CI status' do
page.within ".project-last-commit" do
expect(page).to have_content(project.commit.sha[0..6])
expect(page).to have_content("skipped")
end
end
end
......@@ -262,6 +262,18 @@ module API
expose :notification_level
end
class ProjectService < Grape::Entity
expose :id, :title, :created_at, :updated_at, :active
expose :push_events, :issues_events, :merge_requests_events, :tag_push_events, :note_events
# Expose serialized properties
expose :properties do |service, options|
field_names = service.fields.
select { |field| options[:include_passwords] || field[:type] != 'password' }.
map { |field| field[:name] }
service.properties.slice(*field_names)
end
end
class ProjectWithAccess < Project
expose :permissions do
expose :project_access, using: Entities::ProjectAccess do |project, options|
......
......@@ -249,8 +249,16 @@ module API
required_attributes! [:note]
merge_request = user_project.merge_requests.find(params[:merge_request_id])
note = merge_request.notes.new(note: params[:note], project_id: user_project.id)
note.author = current_user
authorize! :create_note, merge_request
opts = {
note: params[:note],
noteable_type: 'MergeRequest',
noteable_id: merge_request.id
}
note = ::Notes::CreateService.new(user_project, current_user, opts).execute
if note.save
present note, with: Entities::MRNote
......
......@@ -133,7 +133,7 @@ module API
authorize! :download_code, user_project
begin
file_path = ArchiveRepositoryService.new(
ArchiveRepositoryService.new(
user_project,
params[:sha],
params[:format]
......@@ -141,17 +141,6 @@ module API
rescue
not_found!('File')
end
if file_path && File.exists?(file_path)
data = File.open(file_path, 'rb').read
basename = File.basename(file_path)
header['Content-Disposition'] = "attachment; filename=\"#{basename}\""
content_type MIME::Types.type_for(file_path).first.content_type
env['api.format'] = :binary
present data
else
redirect request.fullpath
end
end
# Compare two branches, tags or commits
......
......@@ -57,7 +57,7 @@ module API
# GET /project/:id/services/gitlab-ci
#
get ':id/services/:service_slug' do
present project_service
present project_service, with: Entities::ProjectService, include_passwords: current_user.is_admin?
end
end
end
......
......@@ -5,7 +5,7 @@ module Ci
DEFAULT_STAGES = %w(build test deploy)
DEFAULT_STAGE = 'test'
ALLOWED_YAML_KEYS = [:before_script, :image, :services, :types, :stages, :variables]
ALLOWED_JOB_KEYS = [:tags, :script, :only, :except, :type, :image, :services, :allow_failure, :type, :stage]
ALLOWED_JOB_KEYS = [:tags, :script, :only, :except, :type, :image, :services, :allow_failure, :type, :stage, :when]
attr_reader :before_script, :image, :services, :variables
......@@ -93,6 +93,7 @@ module Ci
only: job[:only],
except: job[:except],
allow_failure: job[:allow_failure] || false,
when: job[:when] || 'on_success',
options: {
image: job[:image] || @image,
services: job[:services] || @services
......@@ -184,6 +185,10 @@ module Ci
if job[:allow_failure] && !job[:allow_failure].in?([true, false])
raise ValidationError, "#{name}: allow_failure parameter should be an boolean"
end
if job[:when] && !job[:when].in?(%w(on_success on_failure always))
raise ValidationError, "#{name}: when parameter should be on_success, on_failure or always"
end
end
private
......
module Ci
class Status
def self.get_status(statuses)
statuses.reject! { |status| status.try(&:allow_failure?) }
if statuses.none?
'skipped'
elsif statuses.all?(&:success?)
'success'
elsif statuses.all?(&:pending?)
'pending'
elsif statuses.any?(&:running?) || statuses.any?(&:pending?)
'running'
elsif statuses.all?(&:canceled?)
'canceled'
else
'failed'
end
end
end
end
......@@ -110,7 +110,7 @@ module ExtractsPath
@project, @ref, @path)
rescue RuntimeError, NoMethodError, InvalidPathError
not_found!
render_404
end
def tree
......
......@@ -193,7 +193,14 @@ module Grack
end
def render_grack_auth_ok
[200, { "Content-Type" => "application/json" }, [JSON.dump({ 'GL_ID' => Gitlab::ShellEnv.gl_id(@user) })]]
[
200,
{ "Content-Type" => "application/json" },
[JSON.dump({
'GL_ID' => Gitlab::ShellEnv.gl_id(@user),
'RepoPath' => project.repository.path_to_repo,
})]
]
end
def render_not_found
......
......@@ -24,12 +24,12 @@ module Gitlab
match[1]
end
private
def config
Gitlab.config.incoming_email
end
private
def address_regex
wildcard_address = config.address
return nil unless wildcard_address
......
......@@ -48,6 +48,7 @@ module Gitlab
autoload :TableOfContentsFilter, 'gitlab/markdown/table_of_contents_filter'
autoload :TaskListFilter, 'gitlab/markdown/task_list_filter'
autoload :UserReferenceFilter, 'gitlab/markdown/user_reference_filter'
autoload :UploadLinkFilter, 'gitlab/markdown/upload_link_filter'
# Public: Parse the provided text with GitLab-Flavored Markdown
#
......@@ -140,6 +141,7 @@ module Gitlab
Gitlab::Markdown::SyntaxHighlightFilter,
Gitlab::Markdown::SanitizationFilter,
Gitlab::Markdown::UploadLinkFilter,
Gitlab::Markdown::RelativeLinkFilter,
Gitlab::Markdown::EmojiFilter,
Gitlab::Markdown::TableOfContentsFilter,
......
require 'gitlab/markdown'
require 'html/pipeline/filter'
require 'uri'
module Gitlab
module Markdown
# HTML filter that "fixes" relative upload links to files.
# Context options:
# :project (required) - Current project
#
class UploadLinkFilter < HTML::Pipeline::Filter
def call
doc.search('a').each do |el|
process_link_attr el.attribute('href')
end
doc.search('img').each do |el|
process_link_attr el.attribute('src')
end
doc
end
protected
def process_link_attr(html_attr)
return if html_attr.blank?
uri = html_attr.value
if uri.starts_with?("/uploads/")
html_attr.value = build_url(uri).to_s
end
end
def build_url(uri)
File.join(Gitlab.config.gitlab.url, context[:project].path_with_namespace, uri)
end
# Ensure that a :project key exists in context
#
# Note that while the key might exist, its value could be nil!
def validate
needs :project
end
end
end
end
......@@ -39,6 +39,8 @@ module Gitlab
#
# Returns the results Array for the requested filter type
def pipeline_result(filter_type)
return [] if @text.blank?
klass = filter_type.to_s.camelize + 'ReferenceFilter'
filter = Gitlab::Markdown.const_get(klass)
......
module Gitlab
class UploadsTransfer
def move_project(project_path, namespace_path_was, namespace_path)
new_namespace_folder = File.join(root_dir, namespace_path)
FileUtils.mkdir_p(new_namespace_folder) unless Dir.exist?(new_namespace_folder)
from = File.join(root_dir, namespace_path_was, project_path)
to = File.join(root_dir, namespace_path, project_path)
move(from, to, "")
end
def rename_project(path_was, path, namespace_path)
base_dir = File.join(root_dir, namespace_path)
move(path_was, path, base_dir)
end
def rename_namespace(path_was, path)
move(path_was, path)
end
private
def move(path_was, path, base_dir = nil)
base_dir = root_dir unless base_dir
from = File.join(base_dir, path_was)
to = File.join(base_dir, path)
FileUtils.mv(from, to)
rescue Errno::ENOENT
false
end
def root_dir
File.join(Rails.root, "public", "uploads")
end
end
end
......@@ -113,7 +113,25 @@ server {
proxy_pass http://gitlab;
}
location ~ [-\/\w\.]+\.git\/ {
location ~ ^/[\w\.-]+/[\w\.-]+/(info/refs|git-upload-pack|git-receive-pack)$ {
# 'Error' 418 is a hack to re-use the @gitlab-git-http-server block
error_page 418 = @gitlab-git-http-server;
return 418;
}
location ~ ^/[\w\.-]+/[\w\.-]+/repository/archive {
# 'Error' 418 is a hack to re-use the @gitlab-git-http-server block
error_page 418 = @gitlab-git-http-server;
return 418;
}
location ~ ^/api/v3/projects/.*/repository/archive {
# 'Error' 418 is a hack to re-use the @gitlab-git-http-server block
error_page 418 = @gitlab-git-http-server;
return 418;
}
location @gitlab-git-http-server {
## If you use HTTPS make sure you disable gzip compression
## to be safe against BREACH attack.
# gzip off;
......
......@@ -160,7 +160,25 @@ server {
proxy_pass http://gitlab;
}
location ~ [-\/\w\.]+\.git\/ {
location ~ ^/[\w\.-]+/[\w\.-]+/(info/refs|git-upload-pack|git-receive-pack)$ {
# 'Error' 418 is a hack to re-use the @gitlab-git-http-server block
error_page 418 = @gitlab-git-http-server;
return 418;
}
location ~ ^/[\w\.-]+/[\w\.-]+/repository/archive {
# 'Error' 418 is a hack to re-use the @gitlab-git-http-server block
error_page 418 = @gitlab-git-http-server;
return 418;
}
location ~ ^/api/v3/projects/.*/repository/archive {
# 'Error' 418 is a hack to re-use the @gitlab-git-http-server block
error_page 418 = @gitlab-git-http-server;
return 418;
}
location @gitlab-git-http-server {
## If you use HTTPS make sure you disable gzip compression
## to be safe against BREACH attack.
gzip off;
......
......@@ -642,7 +642,6 @@ namespace :gitlab do
if Gitlab.config.incoming_email.enabled
check_address_formatted_correctly
check_mail_room_config_exists
check_imap_authentication
if Rails.env.production?
......@@ -744,42 +743,16 @@ namespace :gitlab do
end
end
def check_mail_room_config_exists
print "MailRoom config exists? ... "
mail_room_config_file = Rails.root.join("config", "mail_room.yml")
if File.exists?(mail_room_config_file)
puts "yes".green
else
puts "no".red
try_fixing_it(
"Copy config/mail_room.yml.example to config/mail_room.yml",
"Check that the information in config/mail_room.yml is correct"
)
for_more_information(
"doc/incoming_email/README.md"
)
fix_and_rerun
end
end
def check_imap_authentication
print "IMAP server credentials are correct? ... "
mail_room_config_file = Rails.root.join("config", "mail_room.yml")
unless File.exists?(mail_room_config_file)
puts "can't check because of previous errors".magenta
return
end
config = YAML.load_file(mail_room_config_file)[:mailboxes].first rescue nil
config = Gitlab.config.incoming_email
if config
begin
imap = Net::IMAP.new(config[:host], port: config[:port], ssl: config[:ssl])
imap.login(config[:email], config[:password])
imap = Net::IMAP.new(config.host, port: config.port, ssl: config.ssl)
imap.starttls if config.start_tls
imap.login(config.user, config.password)
connected = true
rescue
connected = false
......@@ -791,7 +764,7 @@ namespace :gitlab do
else
puts "no".red
try_fixing_it(
"Check that the information in config/mail_room.yml is correct"
"Check that the information in config/gitlab.yml is correct"
)
for_more_information(
"doc/incoming_email/README.md"
......
require Rails.root.join('db/migrate/20151007120511_namespaces_projects_path_lower_indexes')
require Rails.root.join('db/migrate/20151008110232_add_users_lower_username_email_indexes')
desc 'GitLab | Sets up PostgreSQL'
task setup_postgresql: :environment do
NamespacesProjectsPathLowerIndexes.new.up
AddUsersLowerUsernameEmailIndexes.new.up
end
require 'spec_helper'
describe ProjectTeam, benchmark: true do
describe '#max_member_access' do
let(:group) { create(:group) }
let(:project) { create(:empty_project, group: group) }
let(:user) { create(:user) }
before do
project.team << [user, :master]
5.times do
project.team << [create(:user), :reporter]
project.group.add_user(create(:user), :reporter)
end
end
benchmark_subject { project.team.max_member_access(user.id) }
it { is_expected.to iterate_per_second(35000) }
end
end
......@@ -11,7 +11,9 @@ describe User, benchmark: true do
end
end
let(:iterations) { 1000 }
# The iteration count is based on the query taking little over 1 ms when
# using PostgreSQL.
let(:iterations) { 900 }
describe 'using a capitalized username' do
benchmark_subject { User.by_login('Alice') }
......
......@@ -33,33 +33,5 @@ describe Projects::RepositoriesController do
expect(response.status).to eq(404)
end
end
context "when the service doesn't return a path" do
before do
allow(service).to receive(:execute).and_return(nil)
end
it "reloads the page" do
get :archive, namespace_id: project.namespace.path, project_id: project.path, ref: "master", format: "zip"
expect(response).to redirect_to(archive_namespace_project_repository_path(project.namespace, project, ref: "master", format: "zip"))
end
end
context "when the service returns a path" do
let(:path) { Rails.root.join("spec/fixtures/dk.png").to_s }
before do
allow(service).to receive(:execute).and_return(path)
end
it "sends the file" do
get :archive, namespace_id: project.namespace.path, project_id: project.path, ref: "master", format: "zip"
expect(response.body).to eq(File.binread(path))
end
end
end
end
......@@ -33,7 +33,7 @@ describe Projects::UploadsController do
it 'returns a content with original filename, new link, and correct type.' do
expect(response.body).to match '\"alt\":\"rails_sample\"'
expect(response.body).to match "\"url\":\"http://localhost/#{project.path_with_namespace}/uploads"
expect(response.body).to match "\"url\":\"/uploads"
expect(response.body).to match '\"is_image\":true'
end
end
......@@ -49,7 +49,7 @@ describe Projects::UploadsController do
it 'returns a content with original filename, new link, and correct type.' do
expect(response.body).to match '\"alt\":\"doc_sample.txt\"'
expect(response.body).to match "\"url\":\"http://localhost/#{project.path_with_namespace}/uploads"
expect(response.body).to match "\"url\":\"/uploads"
expect(response.body).to match '\"is_image\":false'
end
end
......
......@@ -9,6 +9,54 @@ describe "Builds" do
@gl_project.team << [@user, :master]
end
describe "GET /:project/builds" do
context "Running scope" do
before do
@build.run!
visit namespace_project_builds_path(@gl_project.namespace, @gl_project)
end
it { expect(page).to have_content 'Running' }
it { expect(page).to have_content 'Cancel all' }
it { expect(page).to have_content @build.short_sha }
it { expect(page).to have_content @build.ref }
it { expect(page).to have_content @build.name }
end
context "Finished scope" do
before do
@build.run!
visit namespace_project_builds_path(@gl_project.namespace, @gl_project, scope: :finished)
end
it { expect(page).to have_content 'No builds to show' }
it { expect(page).to have_content 'Cancel all' }
end
context "All builds" do
before do
@gl_project.ci_builds.running_or_pending.each(&:success)
visit namespace_project_builds_path(@gl_project.namespace, @gl_project, scope: :all)
end
it { expect(page).to have_content 'All' }
it { expect(page).to have_content @build.short_sha }
it { expect(page).to have_content @build.ref }
it { expect(page).to have_content @build.name }
it { expect(page).to_not have_content 'Cancel all' }
end
end
describe "GET /:project/builds/:id/cancel_all" do
before do
@build.run!
visit cancel_all_namespace_project_builds_path(@gl_project.namespace, @gl_project)
end
it { expect(page).to have_content 'No builds to show' }
it { expect(page).to_not have_content 'Cancel all' }
end
describe "GET /:project/builds/:id" do
before do
visit namespace_project_build_path(@gl_project.namespace, @gl_project, @build)
......
......@@ -29,8 +29,16 @@ describe "Commits" do
it { expect(page).to have_content @commit.git_author_name }
end
describe "Cancel commit" do
describe "Cancel all builds" do
it "cancels commit" do
visit ci_status_path(@commit)
click_on "Cancel all"
expect(page).to have_content "canceled"
end
end
describe "Cancel build" do
it "cancels build" do
visit ci_status_path(@commit)
click_on "Cancel"
expect(page).to have_content "canceled"
......
......@@ -99,6 +99,15 @@ describe ApplicationHelper do
helper.avatar_icon('foo@example.com', 20)
end
describe 'using a User' do
it 'should return an URL for the avatar' do
user = create(:user, avatar: File.open(avatar_file_path))
expect(helper.avatar_icon(user).to_s).
to match("/uploads/user/avatar/#{user.id}/banana_sample.gif")
end
end
end
describe 'gravatar_icon' do
......
......@@ -14,11 +14,6 @@ describe LabelsHelper do
expect(label).not_to receive(:project)
link_to_label(label)
end
it 'includes option for "No Label"' do
result = project_labels_options(project)
expect(result).to include('No Label')
end
end
context 'without @project set' do
......
......@@ -70,4 +70,18 @@ describe ProjectsHelper do
expect(helper.send(:readme_cache_key)).to eq("#{project.path_with_namespace}-nil-readme")
end
end
describe 'link_to_member' do
let(:group) { create(:group) }
let(:project) { create(:empty_project, group: group) }
let(:user) { create(:user) }
describe 'using the default options' do
it 'returns an HTML link to the user' do
link = helper.link_to_member(project, user)
expect(link).to match(%r{/u/#{user.username}})
end
end
end
end
......@@ -12,7 +12,7 @@ describe RunnersHelper do
end
it "returns online text" do
runner = FactoryGirl.build(:ci_runner, contacted_at: 1.hour.ago, active: true)
runner = FactoryGirl.build(:ci_runner, contacted_at: 1.second.ago, active: true)
expect(runner_status_icon(runner)).to include("Runner is online")
end
end
......@@ -24,7 +24,8 @@ module Ci
commands: "pwd\nrspec",
tag_list: [],
options: {},
allow_failure: false
allow_failure: false,
when: "on_success"
})
end
......@@ -125,7 +126,8 @@ module Ci
image: "ruby:2.1",
services: ["mysql"]
},
allow_failure: false
allow_failure: false,
when: "on_success"
})
end
......@@ -152,7 +154,8 @@ module Ci
image: "ruby:2.5",
services: ["postgresql"]
},
allow_failure: false
allow_failure: false,
when: "on_success"
})
end
end
......@@ -174,6 +177,21 @@ module Ci
end
end
describe "When" do
%w(on_success on_failure always).each do |when_state|
it "returns #{when_state} when defined" do
config = YAML.dump({
rspec: { script: "rspec", when: when_state }
})
config_processor = GitlabCiYamlProcessor.new(config)
builds = config_processor.builds_for_stage_and_ref("test", "master")
expect(builds.size).to eq(1)
expect(builds.first[:when]).to eq(when_state)
end
end
end
describe "Error handling" do
it "indicates that object is invalid" do
expect{GitlabCiYamlProcessor.new("invalid_yaml\n!ccdvlf%612334@@@@")}.to raise_error(GitlabCiYamlProcessor::ValidationError)
......@@ -311,6 +329,13 @@ module Ci
GitlabCiYamlProcessor.new(config)
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "variables should be a map of key-valued strings")
end
it "returns errors if job when is not on_success, on_failure or always" do
config = YAML.dump({ rspec: { script: "test", when: 1 } })
expect do
GitlabCiYamlProcessor.new(config)
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: when parameter should be on_success, on_failure or always")
end
end
end
end
......@@ -13,7 +13,6 @@ describe Gitlab::Email::AttachmentUploader do
expect(link).not_to be_nil
expect(link[:is_image]).to be_truthy
expect(link[:alt]).to eq("bricks")
expect(link[:url]).to include("/#{project.path_with_namespace}")
expect(link[:url]).to include("bricks.png")
end
end
......
# encoding: UTF-8
require 'spec_helper'
module Gitlab::Markdown
describe UploadLinkFilter do
def filter(doc, contexts = {})
contexts.reverse_merge!({
project: project
})
described_class.call(doc, contexts)
end
def image(path)
%(<img src="#{path}" />)
end
def link(path)
%(<a href="#{path}">#{path}</a>)
end
let(:project) { create(:project) }
shared_examples :preserve_unchanged do
it 'does not modify any relative URL in anchor' do
doc = filter(link('README.md'))
expect(doc.at_css('a')['href']).to eq 'README.md'
end
it 'does not modify any relative URL in image' do
doc = filter(image('files/images/logo-black.png'))
expect(doc.at_css('img')['src']).to eq 'files/images/logo-black.png'
end
end
it 'does not raise an exception on invalid URIs' do
act = link("://foo")
expect { filter(act) }.not_to raise_error
end
context 'with a valid repository' do
it 'rebuilds relative URL for a link' do
doc = filter(link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))
expect(doc.at_css('a')['href']).
to eq "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
end
it 'rebuilds relative URL for an image' do
doc = filter(link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))
expect(doc.at_css('a')['href']).
to eq "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg"
end
it 'does not modify absolute URL' do
doc = filter(link('http://example.com'))
expect(doc.at_css('a')['href']).to eq 'http://example.com'
end
it 'supports Unicode filenames' do
path = '/uploads/한글.png'
escaped = Addressable::URI.escape(path)
# Stub these methods so the file doesn't actually need to be in the repo
allow_any_instance_of(described_class).
to receive(:file_exists?).and_return(true)
allow_any_instance_of(described_class).
to receive(:image?).with(path).and_return(true)
doc = filter(image(escaped))
expect(doc.at_css('img')['src']).to match "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/%ED%95%9C%EA%B8%80.png"
end
end
end
end
require 'spec_helper'
describe Gitlab::UploadsTransfer do
before do
@root_dir = File.join(Rails.root, "public", "uploads")
@upload_transfer = Gitlab::UploadsTransfer.new
@project_path_was = "test_project_was"
@project_path = "test_project"
@namespace_path_was = "test_namespace_was"
@namespace_path = "test_namespace"
end
after do
FileUtils.rm_rf([
File.join(@root_dir, @namespace_path),
File.join(@root_dir, @namespace_path_was)
])
end
describe '#move_project' do
it "moves project upload to another namespace" do
FileUtils.mkdir_p(File.join(@root_dir, @namespace_path_was, @project_path))
@upload_transfer.move_project(@project_path, @namespace_path_was, @namespace_path)
expected_path = File.join(@root_dir, @namespace_path, @project_path)
expect(Dir.exist?(expected_path)).to be_truthy
end
end
describe '#rename_project' do
it "renames project" do
FileUtils.mkdir_p(File.join(@root_dir, @namespace_path, @project_path_was))
@upload_transfer.rename_project(@project_path_was, @project_path, @namespace_path)
expected_path = File.join(@root_dir, @namespace_path, @project_path)
expect(Dir.exist?(expected_path)).to be_truthy
end
end
describe '#rename_namespace' do
it "renames namespace" do
FileUtils.mkdir_p(File.join(@root_dir, @namespace_path_was, @project_path))
@upload_transfer.rename_namespace(@namespace_path_was, @namespace_path)
expected_path = File.join(@root_dir, @namespace_path, @project_path)
expect(Dir.exist?(expected_path)).to be_truthy
end
end
end
......@@ -200,13 +200,34 @@ describe Ci::Build do
context 'returns variables' do
subject { build.variables }
let(:variables) do
let(:predefined_variables) do
[
{ key: :CI_BUILD_NAME, value: 'test', public: true },
{ key: :CI_BUILD_STAGE, value: 'stage', public: true },
]
end
let(:yaml_variables) do
[
{ key: :DB_NAME, value: 'postgres', public: true }
]
end
it { is_expected.to eq(variables) }
before { build.update_attributes(stage: 'stage') }
it { is_expected.to eq(predefined_variables + yaml_variables) }
context 'for tag' do
let(:tag_variable) do
[
{ key: :CI_BUILD_TAG, value: 'master', public: true }
]
end
before { build.update_attributes(tag: true) }
it { is_expected.to eq(tag_variable + predefined_variables + yaml_variables) }
end
context 'and secure variables' do
let(:secure_variables) do
......@@ -219,7 +240,7 @@ describe Ci::Build do
build.project.variables << Ci::Variable.new(key: 'SECRET_KEY', value: 'secret_value')
end
it { is_expected.to eq(variables + secure_variables) }
it { is_expected.to eq(predefined_variables + yaml_variables + secure_variables) }
context 'and trigger variables' do
let(:trigger) { FactoryGirl.create :ci_trigger, project: project }
......@@ -229,12 +250,17 @@ describe Ci::Build do
{ key: :TRIGGER_KEY, value: 'TRIGGER_VALUE', public: false }
]
end
let(:predefined_trigger_variable) do
[
{ key: :CI_BUILD_TRIGGERED, value: 'true', public: true }
]
end
before do
build.trigger_request = trigger_request
end
it { is_expected.to eq(variables + secure_variables + trigger_variables) }
it { is_expected.to eq(predefined_variables + predefined_trigger_variable + yaml_variables + secure_variables + trigger_variables) }
end
end
end
......@@ -273,4 +299,105 @@ describe Ci::Build do
is_expected.to eq(['rec1', pusher_email])
end
end
describe :can_be_served? do
let(:runner) { FactoryGirl.create :ci_specific_runner }
before { build.project.runners << runner }
context 'runner without tags' do
it 'can handle builds without tags' do
expect(build.can_be_served?(runner)).to be_truthy
end
it 'cannot handle build with tags' do
build.tag_list = ['aa']
expect(build.can_be_served?(runner)).to be_falsey
end
end
context 'runner with tags' do
before { runner.tag_list = ['bb', 'cc'] }
it 'can handle builds without tags' do
expect(build.can_be_served?(runner)).to be_truthy
end
it 'can handle build with matching tags' do
build.tag_list = ['bb']
expect(build.can_be_served?(runner)).to be_truthy
end
it 'cannot handle build with not matching tags' do
build.tag_list = ['aa']
expect(build.can_be_served?(runner)).to be_falsey
end
end
end
describe :any_runners_online? do
subject { build.any_runners_online? }
context 'when no runners' do
it { is_expected.to be_falsey }
end
context 'if there are runner' do
let(:runner) { FactoryGirl.create :ci_specific_runner }
before do
build.project.runners << runner
runner.update_attributes(contacted_at: 1.second.ago)
end
it { is_expected.to be_truthy }
it 'that is inactive' do
runner.update_attributes(active: false)
is_expected.to be_falsey
end
it 'that is not online' do
runner.update_attributes(contacted_at: nil)
is_expected.to be_falsey
end
it 'that cannot handle build' do
expect_any_instance_of(Ci::Build).to receive(:can_be_served?).and_return(false)
is_expected.to be_falsey
end
end
end
describe :show_warning? do
subject { build.show_warning? }
%w(pending).each do |state|
context "if commit_status.status is #{state}" do
before { build.status = state }
it { is_expected.to be_truthy }
context "and there are specific runner" do
let(:runner) { FactoryGirl.create :ci_specific_runner, contacted_at: 1.second.ago }
before do
build.project.runners << runner
runner.save
end
it { is_expected.to be_falsey }
end
end
end
%w(success failed canceled running).each do |state|
context "if commit_status.status is #{state}" do
before { build.status = state }
it { is_expected.to be_falsey }
end
end
end
end
......@@ -32,6 +32,24 @@ describe Ci::Commit do
it { is_expected.to respond_to :git_author_email }
it { is_expected.to respond_to :short_sha }
describe :ordered do
let(:project) { FactoryGirl.create :empty_project }
it 'returns ordered list of commits' do
commit1 = FactoryGirl.create :ci_commit, committed_at: 1.hour.ago, gl_project: project
commit2 = FactoryGirl.create :ci_commit, committed_at: 2.hour.ago, gl_project: project
expect(project.ci_commits.ordered).to eq([commit2, commit1])
end
it 'returns commits ordered by committed_at and id, with nulls last' do
commit1 = FactoryGirl.create :ci_commit, committed_at: 1.hour.ago, gl_project: project
commit2 = FactoryGirl.create :ci_commit, committed_at: nil, gl_project: project
commit3 = FactoryGirl.create :ci_commit, committed_at: 2.hour.ago, gl_project: project
commit4 = FactoryGirl.create :ci_commit, committed_at: nil, gl_project: project
expect(project.ci_commits.ordered).to eq([commit2, commit4, commit3, commit1])
end
end
describe :last_build do
subject { commit.last_build }
before do
......@@ -143,28 +161,28 @@ describe Ci::Commit do
end
describe :create_builds do
let(:commit) { FactoryGirl.create :ci_commit, gl_project: gl_project }
let!(:commit) { FactoryGirl.create :ci_commit, gl_project: gl_project }
def create_builds(trigger_request = nil)
commit.create_builds('master', false, nil, trigger_request)
end
def create_next_builds(trigger_request = nil)
commit.create_next_builds('master', false, nil, trigger_request)
def create_next_builds
commit.create_next_builds(commit.builds.order(:id).last)
end
it 'creates builds' do
expect(create_builds).to be_truthy
commit.builds.reload
expect(commit.builds.size).to eq(2)
commit.builds.update_all(status: "success")
expect(commit.builds.count(:all)).to eq(2)
expect(create_next_builds).to be_truthy
commit.builds.reload
expect(commit.builds.size).to eq(4)
commit.builds.update_all(status: "success")
expect(commit.builds.count(:all)).to eq(4)
expect(create_next_builds).to be_truthy
commit.builds.reload
expect(commit.builds.size).to eq(5)
commit.builds.update_all(status: "success")
expect(commit.builds.count(:all)).to eq(5)
expect(create_next_builds).to be_falsey
end
......@@ -176,12 +194,12 @@ describe Ci::Commit do
it 'creates builds' do
expect(create_builds).to be_truthy
commit.builds.reload
expect(commit.builds.size).to eq(2)
commit.builds.update_all(status: "success")
expect(commit.builds.count(:all)).to eq(2)
expect(create_develop_builds).to be_truthy
commit.builds.reload
expect(commit.builds.size).to eq(4)
commit.builds.update_all(status: "success")
expect(commit.builds.count(:all)).to eq(4)
expect(commit.refs.size).to eq(2)
expect(commit.builds.pluck(:name).uniq.size).to eq(2)
end
......@@ -193,28 +211,24 @@ describe Ci::Commit do
it 'creates builds' do
expect(create_builds(trigger_request)).to be_truthy
commit.builds.reload
expect(commit.builds.size).to eq(2)
expect(commit.builds.count(:all)).to eq(2)
end
it 'rebuilds commit' do
expect(create_builds).to be_truthy
commit.builds.reload
expect(commit.builds.size).to eq(2)
expect(commit.builds.count(:all)).to eq(2)
expect(create_builds(trigger_request)).to be_truthy
commit.builds.reload
expect(commit.builds.size).to eq(4)
expect(commit.builds.count(:all)).to eq(4)
end
it 'creates next builds' do
expect(create_builds(trigger_request)).to be_truthy
commit.builds.reload
expect(commit.builds.size).to eq(2)
expect(commit.builds.count(:all)).to eq(2)
commit.builds.update_all(status: "success")
expect(create_next_builds(trigger_request)).to be_truthy
commit.builds.reload
expect(commit.builds.size).to eq(4)
expect(create_next_builds).to be_truthy
expect(commit.builds.count(:all)).to eq(4)
end
context 'for [ci skip]' do
......@@ -224,7 +238,7 @@ describe Ci::Commit do
it 'rebuilds commit' do
expect(commit.status).to eq('skipped')
expect(create_builds(trigger_request)).to be_truthy
expect(create_builds).to be_truthy
# since everything in Ci::Commit is cached we need to fetch a new object
new_commit = Ci::Commit.find_by_id(commit.id)
......@@ -232,6 +246,129 @@ describe Ci::Commit do
end
end
end
context 'properly creates builds when "when" is defined' do
let(:yaml) do
{
stages: ["build", "test", "test_failure", "deploy", "cleanup"],
build: {
stage: "build",
script: "BUILD",
},
test: {
stage: "test",
script: "TEST",
},
test_failure: {
stage: "test_failure",
script: "ON test failure",
when: "on_failure",
},
deploy: {
stage: "deploy",
script: "PUBLISH",
},
cleanup: {
stage: "cleanup",
script: "TIDY UP",
when: "always",
}
}
end
before do
stub_ci_commit_yaml_file(YAML.dump(yaml))
end
it 'properly creates builds' do
expect(create_builds).to be_truthy
expect(commit.builds.pluck(:name)).to contain_exactly('build')
expect(commit.builds.pluck(:status)).to contain_exactly('pending')
commit.builds.running_or_pending.each(&:success)
expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test')
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'pending')
commit.builds.running_or_pending.each(&:success)
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'pending')
expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy')
commit.builds.running_or_pending.each(&:success)
expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy', 'cleanup')
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'success', 'pending')
commit.builds.running_or_pending.each(&:success)
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'success', 'success')
expect(commit.status).to eq('success')
end
it 'properly creates builds when test fails' do
expect(create_builds).to be_truthy
expect(commit.builds.pluck(:name)).to contain_exactly('build')
expect(commit.builds.pluck(:status)).to contain_exactly('pending')
commit.builds.running_or_pending.each(&:success)
expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test')
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'pending')
commit.builds.running_or_pending.each(&:drop)
expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure')
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'pending')
commit.builds.running_or_pending.each(&:success)
expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure', 'cleanup')
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'success', 'pending')
commit.builds.running_or_pending.each(&:success)
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'success', 'success')
expect(commit.status).to eq('failed')
end
it 'properly creates builds when test and test_failure fails' do
expect(create_builds).to be_truthy
expect(commit.builds.pluck(:name)).to contain_exactly('build')
expect(commit.builds.pluck(:status)).to contain_exactly('pending')
commit.builds.running_or_pending.each(&:success)
expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test')
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'pending')
commit.builds.running_or_pending.each(&:drop)
expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure')
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'pending')
commit.builds.running_or_pending.each(&:drop)
expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure', 'cleanup')
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'failed', 'pending')
commit.builds.running_or_pending.each(&:success)
expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure', 'cleanup')
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'failed', 'success')
expect(commit.status).to eq('failed')
end
it 'properly creates builds when deploy fails' do
expect(create_builds).to be_truthy
expect(commit.builds.pluck(:name)).to contain_exactly('build')
expect(commit.builds.pluck(:status)).to contain_exactly('pending')
commit.builds.running_or_pending.each(&:success)
expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test')
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'pending')
commit.builds.running_or_pending.each(&:success)
expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy')
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'pending')
commit.builds.running_or_pending.each(&:drop)
expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy', 'cleanup')
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'failed', 'pending')
commit.builds.running_or_pending.each(&:success)
expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'failed', 'success')
expect(commit.status).to eq('failed')
end
end
end
describe "#finished_at" do
......@@ -281,59 +418,4 @@ describe Ci::Commit do
expect(commit.coverage).to be_nil
end
end
describe :should_create_next_builds? do
before do
@build1 = FactoryGirl.create :ci_build, commit: commit, name: 'build1', ref: 'master', tag: false, status: 'success'
@build2 = FactoryGirl.create :ci_build, commit: commit, name: 'build1', ref: 'develop', tag: false, status: 'failed'
@build3 = FactoryGirl.create :ci_build, commit: commit, name: 'build1', ref: 'master', tag: true, status: 'failed'
@build4 = FactoryGirl.create :ci_build, commit: commit, name: 'build4', ref: 'master', tag: false, status: 'success'
end
context 'for success' do
it 'to create if all succeeded' do
expect(commit.should_create_next_builds?(@build4)).to be_truthy
end
end
context 'for failed' do
before do
@build4.update_attributes(status: 'failed')
end
it 'to not create' do
expect(commit.should_create_next_builds?(@build4)).to be_falsey
end
context 'and ignore failures for current' do
before do
@build4.update_attributes(allow_failure: true)
end
it 'to create' do
expect(commit.should_create_next_builds?(@build4)).to be_truthy
end
end
end
context 'for running' do
before do
@build4.update_attributes(status: 'running')
end
it 'to not create' do
expect(commit.should_create_next_builds?(@build4)).to be_falsey
end
end
context 'for retried' do
before do
@build5 = FactoryGirl.create :ci_build, commit: commit, name: 'build4', ref: 'master', tag: false, status: 'failed'
end
it 'to not create' do
expect(commit.should_create_next_builds?(@build4)).to be_falsey
end
end
end
end
......@@ -131,24 +131,6 @@ describe Ci::Project do
end
end
describe 'ordered commits' do
let(:project) { FactoryGirl.create :empty_project }
it 'returns ordered list of commits' do
commit1 = FactoryGirl.create :ci_commit, committed_at: 1.hour.ago, gl_project: project
commit2 = FactoryGirl.create :ci_commit, committed_at: 2.hour.ago, gl_project: project
expect(project.ci_commits).to eq([commit2, commit1])
end
it 'returns commits ordered by committed_at and id, with nulls last' do
commit1 = FactoryGirl.create :ci_commit, committed_at: 1.hour.ago, gl_project: project
commit2 = FactoryGirl.create :ci_commit, committed_at: nil, gl_project: project
commit3 = FactoryGirl.create :ci_commit, committed_at: 2.hour.ago, gl_project: project
commit4 = FactoryGirl.create :ci_commit, committed_at: nil, gl_project: project
expect(project.ci_commits).to eq([commit2, commit4, commit3, commit1])
end
end
context :valid_project do
let(:commit) { FactoryGirl.create(:ci_commit) }
......@@ -259,5 +241,18 @@ describe Ci::Project do
FactoryGirl.create(:ci_shared_runner)
expect(project.any_runners?).to be_falsey
end
it "checks the presence of specific runner" do
project = FactoryGirl.create(:ci_project)
specific_runner = FactoryGirl.create(:ci_specific_runner)
project.runners << specific_runner
expect(project.any_runners? { |runner| runner == specific_runner }).to be_truthy
end
it "checks the presence of shared runner" do
project = FactoryGirl.create(:ci_project, shared_runners_enabled: true)
shared_runner = FactoryGirl.create(:ci_shared_runner)
expect(project.any_runners? { |runner| runner == shared_runner }).to be_truthy
end
end
end
......@@ -32,7 +32,7 @@ describe Ci::Runner do
end
it 'should return the token if the description is an empty string' do
runner = FactoryGirl.build(:ci_runner, description: '')
runner = FactoryGirl.build(:ci_runner, description: '', token: 'token')
expect(runner.display_name).to eq runner.token
end
end
......@@ -48,6 +48,71 @@ describe Ci::Runner do
it { expect(shared_runner.only_for?(project)).to be_truthy }
end
describe :online do
subject { Ci::Runner.online }
before do
@runner1 = FactoryGirl.create(:ci_shared_runner, contacted_at: 1.year.ago)
@runner2 = FactoryGirl.create(:ci_shared_runner, contacted_at: 1.second.ago)
end
it { is_expected.to eq([@runner2])}
end
describe :online? do
let(:runner) { FactoryGirl.create(:ci_shared_runner) }
subject { runner.online? }
context 'never contacted' do
before { runner.contacted_at = nil }
it { is_expected.to be_falsey }
end
context 'contacted long time ago time' do
before { runner.contacted_at = 1.year.ago }
it { is_expected.to be_falsey }
end
context 'contacted 1s ago' do
before { runner.contacted_at = 1.second.ago }
it { is_expected.to be_truthy }
end
end
describe :status do
let(:runner) { FactoryGirl.create(:ci_shared_runner, contacted_at: 1.second.ago) }
subject { runner.status }
context 'never connected' do
before { runner.contacted_at = nil }
it { is_expected.to eq(:not_connected) }
end
context 'contacted 1s ago' do
before { runner.contacted_at = 1.second.ago }
it { is_expected.to eq(:online) }
end
context 'contacted long time ago' do
before { runner.contacted_at = 1.year.ago }
it { is_expected.to eq(:offline) }
end
context 'inactive' do
before { runner.active = false }
it { is_expected.to eq(:paused) }
end
end
describe "belongs_to_one_project?" do
it "returns false if there are two projects runner assigned to" do
runner = FactoryGirl.create(:ci_specific_runner)
......
......@@ -89,9 +89,9 @@ eos
end
it_behaves_like 'a mentionable' do
subject { commit }
subject { create(:project).commit }
let(:author) { create(:user, email: commit.author_email) }
let(:author) { create(:user, email: subject.author_email) }
let(:backref_text) { "commit #{subject.id}" }
let(:set_mentionable_text) do
->(txt) { allow(subject).to receive(:safe_message).and_return(txt) }
......
......@@ -25,7 +25,7 @@ describe Issue, "Mentionable" do
it 'correctly removes already-mentioned Commits' do
expect(SystemNoteService).not_to receive(:cross_reference)
issue.create_cross_references!(project, author, [commit2])
issue.create_cross_references!(author, [commit2])
end
end
......
......@@ -69,7 +69,7 @@ describe Issue do
end
it_behaves_like 'an editable mentionable' do
subject { create(:issue, project: project) }
subject { create(:issue) }
let(:backref_text) { "issue #{subject.to_reference}" }
let(:set_mentionable_text) { ->(txt){ subject.description = txt } }
......
......@@ -192,10 +192,9 @@ describe Note do
end
it_behaves_like 'an editable mentionable' do
subject { create :note, noteable: issue, project: project }
subject { create :note, noteable: issue, project: issue.project }
let(:project) { create(:project) }
let(:issue) { create :issue, project: project }
let(:issue) { create :issue }
let(:backref_text) { issue.gfm_reference }
let(:set_mentionable_text) { ->(txt) { subject.note = txt } }
end
......
......@@ -30,6 +30,7 @@ describe BambooService, models: true do
let(:user) { create(:user) }
let(:project) { create(:project) }
context "when a password was previously set" do
before do
@bamboo_service = BambooService.create(
project: create(:project),
......@@ -52,5 +53,42 @@ describe BambooService, models: true do
@bamboo_service.save
expect(@bamboo_service.password).to eq("password")
end
it "does not reset password if new url is set together with password, even if it's the same password" do
@bamboo_service.bamboo_url = 'http://gitlab_edited.com'
@bamboo_service.password = 'password'
@bamboo_service.save
expect(@bamboo_service.password).to eq("password")
expect(@bamboo_service.bamboo_url).to eq("http://gitlab_edited.com")
end
it "should reset password if url changed, even if setter called multiple times" do
@bamboo_service.bamboo_url = 'http://gitlab1.com'
@bamboo_service.bamboo_url = 'http://gitlab1.com'
@bamboo_service.save
expect(@bamboo_service.password).to be_nil
end
end
context "when no password was previously set" do
before do
@bamboo_service = BambooService.create(
project: create(:project),
properties: {
bamboo_url: 'http://gitlab.com',
username: 'mic'
}
)
end
it "saves password if new url is set together with password" do
@bamboo_service.bamboo_url = 'http://gitlab_edited.com'
@bamboo_service.password = 'password'
@bamboo_service.save
expect(@bamboo_service.password).to eq("password")
expect(@bamboo_service.bamboo_url).to eq("http://gitlab_edited.com")
end
end
end
end
......@@ -30,6 +30,7 @@ describe TeamcityService, models: true do
let(:user) { create(:user) }
let(:project) { create(:project) }
context "when a password was previously set" do
before do
@teamcity_service = TeamcityService.create(
project: create(:project),
......@@ -52,5 +53,41 @@ describe TeamcityService, models: true do
@teamcity_service.save
expect(@teamcity_service.password).to eq("password")
end
it "does not reset password if new url is set together with password, even if it's the same password" do
@teamcity_service.teamcity_url = 'http://gitlab_edited.com'
@teamcity_service.password = 'password'
@teamcity_service.save
expect(@teamcity_service.password).to eq("password")
expect(@teamcity_service.teamcity_url).to eq("http://gitlab_edited.com")
end
it "should reset password if url changed, even if setter called multiple times" do
@teamcity_service.teamcity_url = 'http://gitlab1.com'
@teamcity_service.teamcity_url = 'http://gitlab1.com'
@teamcity_service.save
expect(@teamcity_service.password).to be_nil
end
end
context "when no password was previously set" do
before do
@teamcity_service = TeamcityService.create(
project: create(:project),
properties: {
teamcity_url: 'http://gitlab.com',
username: 'mic'
}
)
end
it "saves password if new url is set together with password" do
@teamcity_service.teamcity_url = 'http://gitlab_edited.com'
@teamcity_service.password = 'password'
@teamcity_service.save
expect(@teamcity_service.password).to eq("password")
expect(@teamcity_service.teamcity_url).to eq("http://gitlab_edited.com")
end
end
end
end
......@@ -104,7 +104,7 @@ describe Service do
end
end
describe "#prop_updated?" do
describe "{property}_changed?" do
let(:service) do
BambooService.create(
project: create(:project),
......@@ -116,14 +116,112 @@ describe Service do
)
end
it "returns false" do
it "returns false when the property has not been assigned a new value" do
service.username = "key_changed"
expect(service.prop_updated?(:bamboo_url)).to be_falsy
expect(service.bamboo_url_changed?).to be_falsy
end
it "returns true" do
service.bamboo_url = "http://other.com"
expect(service.prop_updated?(:bamboo_url)).to be_truthy
it "returns true when the property has been assigned a different value" do
service.bamboo_url = "http://example.com"
expect(service.bamboo_url_changed?).to be_truthy
end
it "returns true when the property has been assigned a different value twice" do
service.bamboo_url = "http://example.com"
service.bamboo_url = "http://example.com"
expect(service.bamboo_url_changed?).to be_truthy
end
it "returns false when the property has been re-assigned the same value" do
service.bamboo_url = 'http://gitlab.com'
expect(service.bamboo_url_changed?).to be_falsy
end
it "returns false when the property has been assigned a new value then saved" do
service.bamboo_url = 'http://example.com'
service.save
expect(service.bamboo_url_changed?).to be_falsy
end
end
describe "{property}_touched?" do
let(:service) do
BambooService.create(
project: create(:project),
properties: {
bamboo_url: 'http://gitlab.com',
username: 'mic',
password: "password"
}
)
end
it "returns false when the property has not been assigned a new value" do
service.username = "key_changed"
expect(service.bamboo_url_touched?).to be_falsy
end
it "returns true when the property has been assigned a different value" do
service.bamboo_url = "http://example.com"
expect(service.bamboo_url_touched?).to be_truthy
end
it "returns true when the property has been assigned a different value twice" do
service.bamboo_url = "http://example.com"
service.bamboo_url = "http://example.com"
expect(service.bamboo_url_touched?).to be_truthy
end
it "returns true when the property has been re-assigned the same value" do
service.bamboo_url = 'http://gitlab.com'
expect(service.bamboo_url_touched?).to be_truthy
end
it "returns false when the property has been assigned a new value then saved" do
service.bamboo_url = 'http://example.com'
service.save
expect(service.bamboo_url_changed?).to be_falsy
end
end
describe "{property}_was" do
let(:service) do
BambooService.create(
project: create(:project),
properties: {
bamboo_url: 'http://gitlab.com',
username: 'mic',
password: "password"
}
)
end
it "returns nil when the property has not been assigned a new value" do
service.username = "key_changed"
expect(service.bamboo_url_was).to be_nil
end
it "returns the previous value when the property has been assigned a different value" do
service.bamboo_url = "http://example.com"
expect(service.bamboo_url_was).to eq('http://gitlab.com')
end
it "returns initial value when the property has been re-assigned the same value" do
service.bamboo_url = 'http://gitlab.com'
expect(service.bamboo_url_was).to eq('http://gitlab.com')
end
it "returns initial value when the property has been assigned multiple values" do
service.bamboo_url = "http://example.com"
service.bamboo_url = "http://example2.com"
expect(service.bamboo_url_was).to eq('http://gitlab.com')
end
it "returns nil when the property has been assigned a new value then saved" do
service.bamboo_url = 'http://example.com'
service.save
expect(service.bamboo_url_was).to be_nil
end
end
end
......@@ -379,9 +379,14 @@ describe API::API, api: true do
describe "POST /projects/:id/merge_request/:merge_request_id/comments" do
it "should return comment" do
original_count = merge_request.notes.size
post api("/projects/#{project.id}/merge_request/#{merge_request.id}/comments", user), note: "My comment"
expect(response.status).to eq(201)
expect(json_response['note']).to eq('My comment')
expect(json_response['author']['name']).to eq(user.name)
expect(json_response['author']['username']).to eq(user.username)
expect(merge_request.notes.size).to eq(original_count + 1)
end
it "should return 400 if note is missing" do
......
......@@ -166,24 +166,21 @@ describe API::API, api: true do
get api("/projects/#{project.id}/repository/archive", user)
repo_name = project.repository.name.gsub("\.git", "")
expect(response.status).to eq(200)
expect(response.headers['Content-Disposition']).to match(/filename\=\"#{repo_name}\-[^\.]+\.tar.gz\"/)
expect(response.content_type).to eq(MIME::Types.type_for('file.tar.gz').first.content_type)
expect(json_response['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.tar.gz/)
end
it "should get the archive.zip" do
get api("/projects/#{project.id}/repository/archive.zip", user)
repo_name = project.repository.name.gsub("\.git", "")
expect(response.status).to eq(200)
expect(response.headers['Content-Disposition']).to match(/filename\=\"#{repo_name}\-[^\.]+\.zip\"/)
expect(response.content_type).to eq(MIME::Types.type_for('file.zip').first.content_type)
expect(json_response['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.zip/)
end
it "should get the archive.tar.bz2" do
get api("/projects/#{project.id}/repository/archive.tar.bz2", user)
repo_name = project.repository.name.gsub("\.git", "")
expect(response.status).to eq(200)
expect(response.headers['Content-Disposition']).to match(/filename\=\"#{repo_name}\-[^\.]+\.tar.bz2\"/)
expect(response.content_type).to eq(MIME::Types.type_for('file.tar.bz2').first.content_type)
expect(json_response['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.tar.bz2/)
end
it "should return 404 for invalid sha" do
......
......@@ -3,6 +3,8 @@ require "spec_helper"
describe API::API, api: true do
include ApiHelpers
let(:user) { create(:user) }
let(:admin) { create(:admin) }
let(:user2) { create(:user) }
let(:project) {create(:project, creator_id: user.id, namespace: user.namespace) }
Service.available_services_names.each do |service|
......@@ -51,11 +53,40 @@ describe API::API, api: true do
describe "GET /projects/:id/services/#{service.dasherize}" do
include_context service
it "should get #{service} settings" do
# inject some properties into the service
before do
project.build_missing_services
service_object = project.send(service_method)
service_object.properties = service_attrs
service_object.save
end
it 'should return authentication error when unauthenticated' do
get api("/projects/#{project.id}/services/#{dashed_service}")
expect(response.status).to eq(401)
end
it "should return all properties of service #{service} when authenticated as admin" do
get api("/projects/#{project.id}/services/#{dashed_service}", admin)
expect(response.status).to eq(200)
expect(json_response['properties'].keys.map(&:to_sym)).to match_array(service_attrs_list.map)
end
it "should return properties of service #{service} other than passwords when authenticated as project owner" do
get api("/projects/#{project.id}/services/#{dashed_service}", user)
expect(response.status).to eq(200)
expect(json_response['properties'].keys.map(&:to_sym)).to match_array(service_attrs_list_without_passwords)
end
it "should return error when authenticated but not a project owner" do
project.team << [user2, :developer]
get api("/projects/#{project.id}/services/#{dashed_service}", user2)
expect(response.status).to eq(403)
end
end
end
end
......@@ -76,6 +76,8 @@ describe Ci::API::API do
expect(response.status).to eq(201)
expect(json_response["variables"]).to eq([
{ "key" => "CI_BUILD_NAME", "value" => "spinach", "public" => true },
{ "key" => "CI_BUILD_STAGE", "value" => "test", "public" => true },
{ "key" => "DB_NAME", "value" => "postgres", "public" => true },
{ "key" => "SECRET_KEY", "value" => "secret_value", "public" => false },
])
......@@ -93,6 +95,9 @@ describe Ci::API::API do
expect(response.status).to eq(201)
expect(json_response["variables"]).to eq([
{ "key" => "CI_BUILD_NAME", "value" => "spinach", "public" => true },
{ "key" => "CI_BUILD_STAGE", "value" => "test", "public" => true },
{ "key" => "CI_BUILD_TRIGGERED", "value" => "true", "public" => true },
{ "key" => "DB_NAME", "value" => "postgres", "public" => true },
{ "key" => "SECRET_KEY", "value" => "secret_value", "public" => false },
{ "key" => "TRIGGER_KEY", "value" => "TRIGGER_VALUE", "public" => false },
......
......@@ -13,7 +13,7 @@ describe ArchiveRepositoryService do
context "when the repository doesn't have an archive file path" do
before do
allow(project.repository).to receive(:archive_file_path).and_return(nil)
allow(project.repository).to receive(:archive_metadata).and_return(Hash.new)
end
it "raises an error" do
......@@ -21,70 +21,5 @@ describe ArchiveRepositoryService do
end
end
context "when the repository has an archive file path" do
let(:file_path) { "/archive.zip" }
let(:pid_file_path) { "/archive.zip.pid" }
before do
allow(project.repository).to receive(:archive_file_path).and_return(file_path)
allow(project.repository).to receive(:archive_pid_file_path).and_return(pid_file_path)
end
context "when the archive file already exists" do
before do
allow(File).to receive(:exist?).with(file_path).and_return(true)
end
it "returns the file path" do
expect(subject.execute(timeout: 0.0)).to eq(file_path)
end
end
context "when the archive file doesn't exist yet" do
before do
allow(File).to receive(:exist?).with(file_path).and_return(false)
allow(File).to receive(:exist?).with(pid_file_path).and_return(true)
end
context "when the archive pid file doesn't exist yet" do
before do
allow(File).to receive(:exist?).with(pid_file_path).and_return(false)
end
it "queues the RepositoryArchiveWorker" do
expect(RepositoryArchiveWorker).to receive(:perform_async)
subject.execute(timeout: 0.0)
end
end
context "when the archive pid file already exists" do
it "doesn't queue the RepositoryArchiveWorker" do
expect(RepositoryArchiveWorker).not_to receive(:perform_async)
subject.execute(timeout: 0.0)
end
end
context "when the archive file exists after a little while" do
before do
Thread.new do
sleep 0.1
allow(File).to receive(:exist?).with(file_path).and_return(true)
end
end
it "returns the file path" do
expect(subject.execute(timeout: 0.2)).to eq(file_path)
end
end
context "when the archive file doesn't exist after the timeout" do
it "returns nil" do
expect(subject.execute(timeout: 0.0)).to eq(nil)
end
end
end
end
end
end
......@@ -112,6 +112,14 @@ describe GitPushService do
it { expect(@event.project).to eq(project) }
it { expect(@event.action).to eq(Event::PUSHED) }
it { expect(@event.data).to eq(service.push_data) }
context "Updates merge requests" do
it "when pushing a new branch for the first time" do
expect(project).to receive(:update_merge_requests).
with(@blankrev, 'newrev', 'refs/heads/master', user)
service.execute(project, user, @blankrev, 'newrev', 'refs/heads/master')
end
end
end
describe "Web Hooks" do
......@@ -155,7 +163,7 @@ describe GitPushService do
before do
allow(commit).to receive_messages(
safe_message: "this commit \n mentions ##{issue.id}",
safe_message: "this commit \n mentions #{issue.to_reference}",
references: [issue],
author_name: commit_author.name,
author_email: commit_author.email
......
......@@ -106,6 +106,27 @@ describe MergeRequests::RefreshService do
it { expect(@fork_merge_request.notes).to be_empty }
end
context 'push new branch that exists in a merge request' do
let(:refresh_service) { service.new(@fork_project, @user) }
it 'refreshes the merge request' do
expect(refresh_service).to receive(:execute_hooks).
with(@fork_merge_request, 'update')
allow_any_instance_of(Repository).to receive(:merge_base).and_return(@oldrev)
refresh_service.execute(Gitlab::Git::BLANK_SHA, @newrev, 'refs/heads/master')
reload_mrs
expect(@merge_request.notes).to be_empty
expect(@merge_request).to be_open
notes = @fork_merge_request.notes.reorder(:created_at).map(&:note)
expect(notes[0]).to include('Restored source branch `master`')
expect(notes[1]).to include('Added 4 commits')
expect(@fork_merge_request).to be_open
end
end
def reload_mrs
@merge_request.reload
@fork_merge_request.reload
......
......@@ -37,7 +37,6 @@ describe Projects::DownloadService do
it { expect(@link_to_file).to have_key('url') }
it { expect(@link_to_file).to have_key('is_image') }
it { expect(@link_to_file['is_image']).to be true }
it { expect(@link_to_file['url']).to match("/#{@project.path_with_namespace}") }
it { expect(@link_to_file['url']).to match('rails_sample.jpg') }
it { expect(@link_to_file['alt']).to eq('rails_sample') }
end
......@@ -52,7 +51,6 @@ describe Projects::DownloadService do
it { expect(@link_to_file).to have_key('url') }
it { expect(@link_to_file).to have_key('is_image') }
it { expect(@link_to_file['is_image']).to be false }
it { expect(@link_to_file['url']).to match("/#{@project.path_with_namespace}") }
it { expect(@link_to_file['url']).to match('doc_sample.txt') }
it { expect(@link_to_file['alt']).to eq('doc_sample.txt') }
end
......
......@@ -7,6 +7,8 @@ describe Projects::TransferService do
context 'namespace -> namespace' do
before do
allow_any_instance_of(Gitlab::UploadsTransfer).
to receive(:move_project).and_return(true)
group.add_owner(user)
@result = transfer_project(project, user, group)
end
......
......@@ -18,7 +18,6 @@ describe Projects::UploadService do
it { expect(@link_to_file).to have_key(:is_image) }
it { expect(@link_to_file).to have_value('banana_sample') }
it { expect(@link_to_file[:is_image]).to equal(true) }
it { expect(@link_to_file[:url]).to match("/#{@project.path_with_namespace}") }
it { expect(@link_to_file[:url]).to match('banana_sample.gif') }
end
......@@ -34,7 +33,6 @@ describe Projects::UploadService do
it { expect(@link_to_file).to have_value('dk') }
it { expect(@link_to_file).to have_key(:is_image) }
it { expect(@link_to_file[:is_image]).to equal(true) }
it { expect(@link_to_file[:url]).to match("/#{@project.path_with_namespace}") }
it { expect(@link_to_file[:url]).to match('dk.png') }
end
......@@ -49,7 +47,6 @@ describe Projects::UploadService do
it { expect(@link_to_file).to have_key(:is_image) }
it { expect(@link_to_file).to have_value('rails_sample') }
it { expect(@link_to_file[:is_image]).to equal(true) }
it { expect(@link_to_file[:url]).to match("/#{@project.path_with_namespace}") }
it { expect(@link_to_file[:url]).to match('rails_sample.jpg') }
end
......@@ -64,7 +61,6 @@ describe Projects::UploadService do
it { expect(@link_to_file).to have_key(:is_image) }
it { expect(@link_to_file).to have_value('doc_sample.txt') }
it { expect(@link_to_file[:is_image]).to equal(false) }
it { expect(@link_to_file[:url]).to match("/#{@project.path_with_namespace}") }
it { expect(@link_to_file[:url]).to match('doc_sample.txt') }
end
......
......@@ -242,6 +242,18 @@ describe SystemNoteService do
end
end
describe '.change_branch_presence' do
subject { described_class.change_branch_presence(noteable, project, author, :source, 'feature', :delete) }
it_behaves_like 'a system note'
context 'when source branch deleted' do
it 'sets the note text' do
expect(subject.note).to eq "Deleted source branch `feature`"
end
end
end
describe '.cross_reference' do
subject { described_class.cross_reference(noteable, mentioner, author) }
......
......@@ -5,7 +5,7 @@
# - let(:set_mentionable_text) { lambda { |txt| "block that assigns txt to the subject's mentionable_text" } }
def common_mentionable_setup
let(:project) { create :project }
let(:project) { subject.project }
let(:author) { subject.author }
let(:mentioned_issue) { create(:issue, project: project) }
......@@ -65,7 +65,7 @@ shared_examples 'a mentionable' do
it "extracts references from its reference property" do
# De-duplicate and omit itself
refs = subject.references(project)
refs = subject.referenced_mentionables
expect(refs.size).to eq(6)
expect(refs).to include(mentioned_issue)
expect(refs).to include(mentioned_mr)
......@@ -84,14 +84,7 @@ shared_examples 'a mentionable' do
with(referenced, subject.local_reference, author)
end
subject.create_cross_references!(project, author)
end
it 'detects existing cross-references' do
SystemNoteService.cross_reference(mentioned_issue, subject.local_reference, author)
expect(subject).to have_mentioned(mentioned_issue)
expect(subject).not_to have_mentioned(mentioned_mr)
subject.create_cross_references!
end
end
......@@ -143,6 +136,6 @@ shared_examples 'an editable mentionable' do
end
set_mentionable_text.call(new_text)
subject.create_new_cross_references!(project, author)
subject.create_new_cross_references!(author)
end
end
......@@ -3,7 +3,13 @@ Service.available_services_names.each do |service|
let(:dashed_service) { service.dasherize }
let(:service_method) { "#{service}_service".to_sym }
let(:service_klass) { "#{service}_service".classify.constantize }
let(:service_attrs_list) { service_klass.new.fields.inject([]) {|arr, hash| arr << hash[:name].to_sym } }
let(:service_fields) { service_klass.new.fields }
let(:service_attrs_list) { service_fields.inject([]) {|arr, hash| arr << hash[:name].to_sym } }
let(:service_attrs_list_without_passwords) do
service_fields.
select { |field| field[:type] != 'password' }.
map { |field| field[:name].to_sym}
end
let(:service_attrs) do
service_attrs_list.inject({}) do |hash, k|
if k =~ /^(token*|.*_token|.*_key)/
......
......@@ -9,7 +9,7 @@ module TestEnv
'flatten-dir' => 'e56497b',
'feature' => '0b4bc9a',
'feature_conflict' => 'bb5206f',
'fix' => '12d65c8',
'fix' => '48f0be4',
'improve/awesome' => '5937ac0',
'markdown' => '0ed8c6c',
'master' => '5937ac0',
......
require 'spec_helper'
describe RepositoryArchiveWorker do
let(:project) { create(:project) }
subject { RepositoryArchiveWorker.new }
before do
allow(Project).to receive(:find).and_return(project)
end
describe "#perform" do
it "cleans old archives" do
expect(project.repository).to receive(:clean_old_archives)
subject.perform(project.id, "master", "zip")
end
context "when the repository doesn't have an archive file path" do
before do
allow(project.repository).to receive(:archive_file_path).and_return(nil)
end
it "doesn't archive the repo" do
expect(project.repository).not_to receive(:archive_repo)
subject.perform(project.id, "master", "zip")
end
end
context "when the repository has an archive file path" do
let(:file_path) { "/archive.zip" }
let(:pid_file_path) { "/archive.zip.pid" }
before do
allow(project.repository).to receive(:archive_file_path).and_return(file_path)
allow(project.repository).to receive(:archive_pid_file_path).and_return(pid_file_path)
end
context "when the archive file already exists" do
before do
allow(File).to receive(:exist?).with(file_path).and_return(true)
end
it "doesn't archive the repo" do
expect(project.repository).not_to receive(:archive_repo)
subject.perform(project.id, "master", "zip")
end
end
context "when the archive file doesn't exist yet" do
before do
allow(File).to receive(:exist?).with(file_path).and_return(false)
allow(File).to receive(:exist?).with(pid_file_path).and_return(true)
end
context "when the archive pid file doesn't exist yet" do
before do
allow(File).to receive(:exist?).with(pid_file_path).and_return(false)
end
it "archives the repo" do
expect(project.repository).to receive(:archive_repo)
subject.perform(project.id, "master", "zip")
end
end
context "when the archive pid file already exists" do
it "doesn't archive the repo" do
expect(project.repository).not_to receive(:archive_repo)
subject.perform(project.id, "master", "zip")
end
end
end
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment