Commit 34148d15 authored by Douwe Maan's avatar Douwe Maan

Merge branch 'master' into rs-redactor-filter

parents 95f0440a bd3689e9
...@@ -25,7 +25,6 @@ config/initializers/rack_attack.rb ...@@ -25,7 +25,6 @@ config/initializers/rack_attack.rb
config/initializers/smtp_settings.rb config/initializers/smtp_settings.rb
config/resque.yml config/resque.yml
config/unicorn.rb config/unicorn.rb
config/mail_room.yml
config/secrets.yml config/secrets.yml
coverage/* coverage/*
db/*.sqlite3 db/*.sqlite3
......
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
v 8.1.0 (unreleased) 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
- Make diff file view easier to use on mobile screens (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) - Add support for creating directories from Files page (Stan Hu)
- Allow removing of project without confirmation when JavaScript is disabled (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) - Support filtering by "Any" milestone or issue and fix "No Milestone" and "No Label" filters (Stan Hu)
...@@ -17,6 +21,8 @@ v 8.1.0 (unreleased) ...@@ -17,6 +21,8 @@ v 8.1.0 (unreleased)
- Fix cases where Markdown did not render links in activity feed (Stan Hu) - Fix cases where Markdown did not render links in activity feed (Stan Hu)
- Add first and last to pagination (Zeger-Jan van de Weg) - Add first and last to pagination (Zeger-Jan van de Weg)
- Added Commit Status API - Added Commit Status API
- Added Builds View
- Added when to .gitlab-ci.yml
- Show CI status on commit page - Show CI status on commit page
- Added CI_BUILD_TAG, _STAGE, _NAME and _TRIGGERED to CI builds - Added CI_BUILD_TAG, _STAGE, _NAME and _TRIGGERED to CI builds
- Show CI status on Your projects page and Starred projects page - Show CI status on Your projects page and Starred projects page
......
source "https://rubygems.org" 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' gem 'rails', '4.1.12'
# Specify a sprockets version due to security issue # Specify a sprockets version due to security issue
...@@ -47,7 +39,7 @@ gem "browser", '~> 1.0.0' ...@@ -47,7 +39,7 @@ gem "browser", '~> 1.0.0'
# Extracting information from a git repository # Extracting information from a git repository
# Provide access to Gitlab::Git library # Provide access to Gitlab::Git library
gem "gitlab_git", '~> 7.2.18' gem "gitlab_git", '~> 7.2.19'
# LDAP Auth # LDAP Auth
# GitLab fork with several improvements to original library. For full list of changes # GitLab fork with several improvements to original library. For full list of changes
...@@ -102,7 +94,7 @@ gem "seed-fu", '~> 2.3.5' ...@@ -102,7 +94,7 @@ gem "seed-fu", '~> 2.3.5'
gem 'html-pipeline', '~> 1.11.0' gem 'html-pipeline', '~> 1.11.0'
gem 'task_list', '~> 1.0.2', require: 'task_list/railtie' gem 'task_list', '~> 1.0.2', require: 'task_list/railtie'
gem 'github-markup', '~> 1.3.1' gem 'github-markup', '~> 1.3.1'
gem 'redcarpet', '~> 3.3.2' gem 'redcarpet', '~> 3.3.3'
gem 'RedCloth', '~> 4.2.9' gem 'RedCloth', '~> 4.2.9'
gem 'rdoc', '~>3.6' gem 'rdoc', '~>3.6'
gem 'org-ruby', '~> 0.9.12' gem 'org-ruby', '~> 0.9.12'
...@@ -196,7 +188,7 @@ gem 'charlock_holmes', '~> 0.6.9.4' ...@@ -196,7 +188,7 @@ gem 'charlock_holmes', '~> 0.6.9.4'
gem "sass-rails", '~> 4.0.5' gem "sass-rails", '~> 4.0.5'
gem "coffee-rails", '~> 4.1.0' gem "coffee-rails", '~> 4.1.0'
gem "uglifier", '~> 2.3.2' gem "uglifier", '~> 2.7.2'
gem 'turbolinks', '~> 2.5.0' gem 'turbolinks', '~> 2.5.0'
gem 'jquery-turbolinks', '~> 2.0.1' gem 'jquery-turbolinks', '~> 2.0.1'
...@@ -224,6 +216,9 @@ group :development do ...@@ -224,6 +216,9 @@ group :development do
gem 'quiet_assets', '~> 1.0.2' gem 'quiet_assets', '~> 1.0.2'
gem 'rack-mini-profiler', '~> 0.9.0', require: false gem 'rack-mini-profiler', '~> 0.9.0', require: false
gem 'rerun', '~> 0.10.0' gem 'rerun', '~> 0.10.0'
gem 'bullet', require: false
gem 'active_record_query_trace', require: false
gem 'rack-lineprof', platform: :mri
# Better errors handler # Better errors handler
gem 'better_errors', '~> 1.0.1' gem 'better_errors', '~> 1.0.1'
...@@ -290,7 +285,7 @@ gem 'newrelic-grape' ...@@ -290,7 +285,7 @@ gem 'newrelic-grape'
gem 'octokit', '~> 3.7.0' gem 'octokit', '~> 3.7.0'
gem "mail_room", "~> 0.6.0" gem "mail_room", "~> 0.6.1"
gem 'email_reply_parser', '~> 0.5.8' gem 'email_reply_parser', '~> 0.5.8'
...@@ -304,11 +299,3 @@ gem 'oauth2', '~> 1.0.0' ...@@ -304,11 +299,3 @@ gem 'oauth2', '~> 1.0.0'
# Soft deletion # Soft deletion
gem "paranoia", "~> 2.0" 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 ...@@ -17,6 +17,7 @@ GEM
activesupport (= 4.1.12) activesupport (= 4.1.12)
builder (~> 3.1) builder (~> 3.1)
erubis (~> 2.7.0) erubis (~> 2.7.0)
active_record_query_trace (1.5)
activemodel (4.1.12) activemodel (4.1.12)
activesupport (= 4.1.12) activesupport (= 4.1.12)
builder (~> 3.1) builder (~> 3.1)
...@@ -87,6 +88,9 @@ GEM ...@@ -87,6 +88,9 @@ GEM
terminal-table (~> 1.4) terminal-table (~> 1.4)
browser (1.0.0) browser (1.0.0)
builder (3.2.2) builder (3.2.2)
bullet (4.14.9)
activesupport (>= 3.0.0)
uniform_notifier (~> 1.9.0)
byebug (6.0.2) byebug (6.0.2)
cal-heatmap-rails (0.0.1) cal-heatmap-rails (0.0.1)
capybara (2.4.4) capybara (2.4.4)
...@@ -134,6 +138,7 @@ GEM ...@@ -134,6 +138,7 @@ GEM
daemons (1.2.3) daemons (1.2.3)
database_cleaner (1.4.1) database_cleaner (1.4.1)
debug_inspector (0.0.2) debug_inspector (0.0.2)
debugger-ruby_core_source (1.3.8)
default_value_for (3.0.1) default_value_for (3.0.1)
activerecord (>= 3.2.0, < 5.0) activerecord (>= 3.2.0, < 5.0)
descendants_tracker (0.0.4) descendants_tracker (0.0.4)
...@@ -278,7 +283,7 @@ GEM ...@@ -278,7 +283,7 @@ GEM
mime-types (~> 1.19) mime-types (~> 1.19)
gitlab_emoji (0.1.1) gitlab_emoji (0.1.1)
gemojione (~> 2.0) gemojione (~> 2.0)
gitlab_git (7.2.18) gitlab_git (7.2.19)
activesupport (~> 4.0) activesupport (~> 4.0)
charlock_holmes (~> 0.6) charlock_holmes (~> 0.6)
gitlab-linguist (~> 3.0) gitlab-linguist (~> 3.0)
...@@ -314,19 +319,6 @@ GEM ...@@ -314,19 +319,6 @@ GEM
grape-entity (0.4.8) grape-entity (0.4.8)
activesupport activesupport
multi_json (>= 1.3.2) 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) haml (4.0.7)
tilt tilt
haml-rails (0.9.0) haml-rails (0.9.0)
...@@ -387,12 +379,11 @@ GEM ...@@ -387,12 +379,11 @@ GEM
celluloid (~> 0.16.0) celluloid (~> 0.16.0)
rb-fsevent (>= 0.9.3) rb-fsevent (>= 0.9.3)
rb-inotify (>= 0.9) rb-inotify (>= 0.9)
lumberjack (1.0.9)
macaddr (1.7.1) macaddr (1.7.1)
systemu (~> 2.6.2) systemu (~> 2.6.2)
mail (2.6.3) mail (2.6.3)
mime-types (>= 1.16, < 3) mime-types (>= 1.16, < 3)
mail_room (0.6.0) mail_room (0.6.1)
method_source (0.8.2) method_source (0.8.2)
mime-types (1.25.1) mime-types (1.25.1)
mimemagic (0.3.0) mimemagic (0.3.0)
...@@ -403,7 +394,6 @@ GEM ...@@ -403,7 +394,6 @@ GEM
multi_xml (0.5.5) multi_xml (0.5.5)
multipart-post (2.0.0) multipart-post (2.0.0)
mysql2 (0.3.20) mysql2 (0.3.20)
nenv (0.2.0)
nested_form (0.3.2) nested_form (0.3.2)
net-ldap (0.11) net-ldap (0.11)
net-scp (1.2.1) net-scp (1.2.1)
...@@ -416,9 +406,6 @@ GEM ...@@ -416,9 +406,6 @@ GEM
newrelic_rpm (3.9.4.245) newrelic_rpm (3.9.4.245)
nokogiri (1.6.6.2) nokogiri (1.6.6.2)
mini_portile (~> 0.6.0) mini_portile (~> 0.6.0)
notiffany (0.0.7)
nenv (~> 0.1)
shellany (~> 0.0)
nprogress-rails (0.1.2.3) nprogress-rails (0.1.2.3)
oauth (0.4.7) oauth (0.4.7)
oauth2 (1.0.0) oauth2 (1.0.0)
...@@ -502,6 +489,10 @@ GEM ...@@ -502,6 +489,10 @@ GEM
rack-attack (4.3.0) rack-attack (4.3.0)
rack rack
rack-cors (0.4.0) 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-mini-profiler (0.9.7)
rack (>= 1.1.3) rack (>= 1.1.3)
rack-mount (0.8.3) rack-mount (0.8.3)
...@@ -540,13 +531,15 @@ GEM ...@@ -540,13 +531,15 @@ GEM
rb-fsevent (0.9.5) rb-fsevent (0.9.5)
rb-inotify (0.9.5) rb-inotify (0.9.5)
ffi (>= 0.5.0) ffi (>= 0.5.0)
rblineprof (0.3.6)
debugger-ruby_core_source (~> 1.3)
rbvmomi (1.8.2) rbvmomi (1.8.2)
builder builder
nokogiri (>= 1.4.1) nokogiri (>= 1.4.1)
trollop trollop
rdoc (3.12.2) rdoc (3.12.2)
json (~> 1.4) json (~> 1.4)
redcarpet (3.3.2) redcarpet (3.3.3)
redis (3.2.1) redis (3.2.1)
redis-actionpack (4.0.0) redis-actionpack (4.0.0)
actionpack (~> 4) actionpack (~> 4)
...@@ -647,7 +640,6 @@ GEM ...@@ -647,7 +640,6 @@ GEM
sexp_processor (4.6.0) sexp_processor (4.6.0)
sham_rack (1.3.6) sham_rack (1.3.6)
rack rack
shellany (0.0.1)
shoulda-matchers (2.8.0) shoulda-matchers (2.8.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
sidekiq (3.3.0) sidekiq (3.3.0)
...@@ -741,7 +733,7 @@ GEM ...@@ -741,7 +733,7 @@ GEM
simple_oauth (~> 0.1.4) simple_oauth (~> 0.1.4)
tzinfo (1.2.2) tzinfo (1.2.2)
thread_safe (~> 0.1) thread_safe (~> 0.1)
uglifier (2.3.3) uglifier (2.7.2)
execjs (>= 0.3.0) execjs (>= 0.3.0)
json (>= 1.8.0) json (>= 1.8.0)
underscore-rails (1.4.4) underscore-rails (1.4.4)
...@@ -755,6 +747,7 @@ GEM ...@@ -755,6 +747,7 @@ GEM
unicorn-worker-killer (0.4.3) unicorn-worker-killer (0.4.3)
get_process_mem (~> 0) get_process_mem (~> 0)
unicorn (~> 4) unicorn (~> 4)
uniform_notifier (1.9.0)
uuid (2.3.8) uuid (2.3.8)
macaddr (~> 1.0) macaddr (~> 1.0)
version_sorter (2.0.0) version_sorter (2.0.0)
...@@ -784,6 +777,7 @@ PLATFORMS ...@@ -784,6 +777,7 @@ PLATFORMS
DEPENDENCIES DEPENDENCIES
RedCloth (~> 4.2.9) RedCloth (~> 4.2.9)
ace-rails-ap (~> 2.0.1) ace-rails-ap (~> 2.0.1)
active_record_query_trace
activerecord-deprecated_finders (~> 1.0.3) activerecord-deprecated_finders (~> 1.0.3)
activerecord-session_store (~> 0.1.0) activerecord-session_store (~> 0.1.0)
acts-as-taggable-on (~> 3.4) acts-as-taggable-on (~> 3.4)
...@@ -800,6 +794,7 @@ DEPENDENCIES ...@@ -800,6 +794,7 @@ DEPENDENCIES
bootstrap-sass (~> 3.0) bootstrap-sass (~> 3.0)
brakeman (= 3.0.1) brakeman (= 3.0.1)
browser (~> 1.0.0) browser (~> 1.0.0)
bullet
byebug byebug
cal-heatmap-rails (~> 0.0.1) cal-heatmap-rails (~> 0.0.1)
capybara (~> 2.4.0) capybara (~> 2.4.0)
...@@ -834,15 +829,13 @@ DEPENDENCIES ...@@ -834,15 +829,13 @@ DEPENDENCIES
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-linguist (~> 3.0.1) gitlab-linguist (~> 3.0.1)
gitlab_emoji (~> 0.1) gitlab_emoji (~> 0.1)
gitlab_git (~> 7.2.18) gitlab_git (~> 7.2.19)
gitlab_meta (= 7.0) gitlab_meta (= 7.0)
gitlab_omniauth-ldap (~> 1.2.1) gitlab_omniauth-ldap (~> 1.2.1)
gollum-lib (~> 4.0.2) gollum-lib (~> 4.0.2)
gon (~> 5.0.0) gon (~> 5.0.0)
grape (~> 0.6.1) grape (~> 0.6.1)
grape-entity (~> 0.4.2) grape-entity (~> 0.4.2)
growl
guard-rspec (~> 4.2.0)
haml-rails (~> 0.9.0) haml-rails (~> 0.9.0)
hipchat (~> 1.5.0) hipchat (~> 1.5.0)
html-pipeline (~> 1.11.0) html-pipeline (~> 1.11.0)
...@@ -854,7 +847,7 @@ DEPENDENCIES ...@@ -854,7 +847,7 @@ DEPENDENCIES
jquery-ui-rails (~> 4.2.1) jquery-ui-rails (~> 4.2.1)
kaminari (~> 0.16.3) kaminari (~> 0.16.3)
letter_opener (~> 1.1.2) letter_opener (~> 1.1.2)
mail_room (~> 0.6.0) mail_room (~> 0.6.1)
minitest (~> 5.7.0) minitest (~> 5.7.0)
mousetrap-rails (~> 1.4.6) mousetrap-rails (~> 1.4.6)
mysql2 (~> 0.3.16) mysql2 (~> 0.3.16)
...@@ -882,14 +875,13 @@ DEPENDENCIES ...@@ -882,14 +875,13 @@ DEPENDENCIES
quiet_assets (~> 1.0.2) quiet_assets (~> 1.0.2)
rack-attack (~> 4.3.0) rack-attack (~> 4.3.0)
rack-cors (~> 0.4.0) rack-cors (~> 0.4.0)
rack-lineprof
rack-mini-profiler (~> 0.9.0) rack-mini-profiler (~> 0.9.0)
rack-oauth2 (~> 1.0.5) rack-oauth2 (~> 1.0.5)
rails (= 4.1.12) rails (= 4.1.12)
raphael-rails (~> 2.1.2) raphael-rails (~> 2.1.2)
rb-fsevent
rb-inotify
rdoc (~> 3.6) rdoc (~> 3.6)
redcarpet (~> 3.3.2) redcarpet (~> 3.3.3)
redis-rails (~> 4.0.0) redis-rails (~> 4.0.0)
request_store (~> 1.2.0) request_store (~> 1.2.0)
rerun (~> 0.10.0) rerun (~> 0.10.0)
...@@ -926,7 +918,7 @@ DEPENDENCIES ...@@ -926,7 +918,7 @@ DEPENDENCIES
thin (~> 1.6.1) thin (~> 1.6.1)
tinder (~> 1.10.0) tinder (~> 1.10.0)
turbolinks (~> 2.5.0) turbolinks (~> 2.5.0)
uglifier (~> 2.3.2) uglifier (~> 2.7.2)
underscore-rails (~> 1.4.4) underscore-rails (~> 1.4.4)
unf (~> 0.1.4) unf (~> 0.1.4)
unicorn (~> 4.8.2) unicorn (~> 4.8.2)
......
...@@ -68,8 +68,8 @@ class @MergeRequestTabs ...@@ -68,8 +68,8 @@ class @MergeRequestTabs
scrollToElement: (container) -> scrollToElement: (container) ->
if window.location.hash if window.location.hash
top = $(container + " " + window.location.hash).offset().top $el = $("#{container} #{window.location.hash}")
$('body').scrollTo(top) $('body').scrollTo($el.offset().top) if $el.length
# Activate a tab based on the current action # Activate a tab based on the current action
activateTab: (action) -> activateTab: (action) ->
...@@ -127,7 +127,7 @@ class @MergeRequestTabs ...@@ -127,7 +127,7 @@ class @MergeRequestTabs
document.getElementById('commits').innerHTML = data.html document.getElementById('commits').innerHTML = data.html
$('.js-timeago').timeago() $('.js-timeago').timeago()
@commitsLoaded = true @commitsLoaded = true
@scrollToElement(".commits") @scrollToElement("#commits")
loadDiff: (source) -> loadDiff: (source) ->
return if @diffsLoaded return if @diffsLoaded
...@@ -137,7 +137,7 @@ class @MergeRequestTabs ...@@ -137,7 +137,7 @@ class @MergeRequestTabs
success: (data) => success: (data) =>
document.getElementById('diffs').innerHTML = data.html document.getElementById('diffs').innerHTML = data.html
@diffsLoaded = true @diffsLoaded = true
@scrollToElement(".diffs") @scrollToElement("#diffs")
# Show or hide the loading spinner # Show or hide the loading spinner
# #
......
...@@ -7,6 +7,7 @@ class @ShortcutsNavigation extends Shortcuts ...@@ -7,6 +7,7 @@ class @ShortcutsNavigation extends Shortcuts
Mousetrap.bind('g e', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-project-activity')) Mousetrap.bind('g e', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-project-activity'))
Mousetrap.bind('g f', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-tree')) Mousetrap.bind('g f', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-tree'))
Mousetrap.bind('g c', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-commits')) 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 n', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-network'))
Mousetrap.bind('g g', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-graphs')) Mousetrap.bind('g g', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-graphs'))
Mousetrap.bind('g i', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-issues')) Mousetrap.bind('g i', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-issues'))
......
...@@ -101,9 +101,9 @@ ...@@ -101,9 +101,9 @@
pre { pre {
margin: 12px 0 12px 0 !important; margin: 12px 0 12px 0 !important;
background-color: #f8fafc !important; background-color: #f8fafc;
font-size: 13px !important; font-size: 13px !important;
color: #5b6169 !important; color: #5b6169;
line-height: 1.6em !important; line-height: 1.6em !important;
@include border-radius(2px); @include border-radius(2px);
} }
......
/* https://github.com/MozMorris/tomorrow-pygments */ /* https://github.com/MozMorris/tomorrow-pygments */
pre.code.highlight.dark,
.code.dark { .code.dark {
background-color: #1d1f21; background-color: #1d1f21 !important;
color: #c5c8c6; color: #c5c8c6 !important;
pre.code, pre.highlight,
.line-numbers, .line-numbers,
.line-numbers a { .line-numbers a {
background-color: #1d1f21 !important; background-color: #1d1f21 !important;
...@@ -23,8 +22,8 @@ pre.code.highlight.dark, ...@@ -23,8 +22,8 @@ pre.code.highlight.dark,
// Search result highlight // Search result highlight
span.highlight_word { span.highlight_word {
background: #ffe792; background-color: #ffe792 !important;
color: #000000; color: #000000 !important;
} }
.hll { background-color: #373b41 } .hll { background-color: #373b41 }
......
/* https://github.com/richleland/pygments-css/blob/master/monokai.css */ /* https://github.com/richleland/pygments-css/blob/master/monokai.css */
pre.code.monokai,
.code.monokai { .code.monokai {
background: #272822; background-color: #272822 !important;
color: #f8f8f2; color: #f8f8f2 !important;
pre.highlight, pre.highlight,
.line-numbers, .line-numbers,
.line-numbers a { .line-numbers a {
background:#272822 !important; background-color :#272822 !important;
color:#f8f8f2 !important; color: #f8f8f2 !important;
} }
pre.code { pre.code {
...@@ -23,8 +22,8 @@ pre.code.monokai, ...@@ -23,8 +22,8 @@ pre.code.monokai,
// Search result highlight // Search result highlight
span.highlight_word { span.highlight_word {
background: #ffe792; background-color: #ffe792 !important;
color: #000000; color: #000000 !important;
} }
.hll { background-color: #49483e } .hll { background-color: #49483e }
......
/* https://gist.github.com/qguv/7936275 */ /* https://gist.github.com/qguv/7936275 */
pre.code.highlight.solarized-dark,
.code.solarized-dark { .code.solarized-dark {
background-color: #002b36; background-color: #002b36 !important;
color: #93a1a1; color: #93a1a1 !important;
pre.code, pre.highlight,
.line-numbers, .line-numbers,
.line-numbers a { .line-numbers a {
background-color: #002b36 !important; background-color: #002b36 !important;
...@@ -23,7 +22,7 @@ pre.code.highlight.solarized-dark, ...@@ -23,7 +22,7 @@ pre.code.highlight.solarized-dark,
// Search result highlight // Search result highlight
span.highlight_word { span.highlight_word {
background: #094554; background-color: #094554 !important;
} }
/* Solarized Dark /* Solarized Dark
......
/* https://gist.github.com/qguv/7936275 */ /* https://gist.github.com/qguv/7936275 */
pre.code.highlight.solarized-light,
.code.solarized-light { .code.solarized-light {
background-color: #fdf6e3; background-color: #fdf6e3 !important;
color: #586e75; color: #586e75 !important;
pre.code, pre.highlight,
.line-numbers, .line-numbers,
.line-numbers a { .line-numbers a {
background-color: #fdf6e3 !important; background-color: #fdf6e3 !important;
...@@ -23,7 +22,7 @@ pre.code.highlight.solarized-light, ...@@ -23,7 +22,7 @@ pre.code.highlight.solarized-light,
// Search result highlight // Search result highlight
span.highlight_word { span.highlight_word {
background: #eee8d5; background-color: #eee8d5 !important;
} }
/* Solarized Light /* Solarized Light
......
/* https://github.com/aahan/pygments-github-style */ /* https://github.com/aahan/pygments-github-style */
pre.code.highlight.white,
.code.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,
.line-numbers a { .line-numbers a {
background-color: $background-color !important; background-color: $background-color !important;
color: $gl-gray !important; color: $gl-gray !important;
} }
pre.highlight {
background-color: #fff !important;
color: #333 !important;
}
pre.code { pre.code {
border-left: 1px solid $border-color; border-left: 1px solid $border-color;
background-color: #fff !important;
color: #333 !important;
} }
// highlight line via anchor // highlight line via anchor
...@@ -28,7 +24,7 @@ pre.code.highlight.white, ...@@ -28,7 +24,7 @@ pre.code.highlight.white,
// Search result highlight // Search result highlight
span.highlight_word { span.highlight_word {
background: #fafe3d; background-color: #fafe3d !important;
} }
.hll { background-color: #f8f8f8 } .hll { background-color: #f8f8f8 }
......
...@@ -39,7 +39,13 @@ class Admin::ServicesController < Admin::ApplicationController ...@@ -39,7 +39,13 @@ class Admin::ServicesController < Admin::ApplicationController
end end
def application_services_params def application_services_params
params.permit(:id, application_services_params = params.permit(:id,
service: Projects::ServicesController::ALLOWED_PARAMS) 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
end end
...@@ -150,7 +150,7 @@ class ApplicationController < ActionController::Base ...@@ -150,7 +150,7 @@ class ApplicationController < ActionController::Base
end end
def git_not_found! def git_not_found!
render "errors/git_not_found", layout: "errors", status: 404 render html: "errors/git_not_found", layout: "errors", status: 404
end end
def method_missing(method_sym, *arguments, &block) def method_missing(method_sym, *arguments, &block)
......
class Projects::BuildsController < Projects::ApplicationController class Projects::BuildsController < Projects::ApplicationController
before_action :ci_project 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" 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 def show
@builds = @ci_project.commits.find_by_sha(@build.sha).builds.order('id DESC') @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) @builds = @builds.where("id not in (?)", @build.id).page(params[:page]).per(20)
......
...@@ -57,7 +57,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -57,7 +57,7 @@ class Projects::IssuesController < Projects::ApplicationController
def show def show
@participants = @issue.participants(current_user) @participants = @issue.participants(current_user)
@note = @project.notes.new(noteable: @issue) @note = @project.notes.new(noteable: @issue)
@notes = @issue.notes.inc_author.fresh @notes = @issue.notes.with_associations.fresh
@noteable = @issue @noteable = @issue
respond_with(@issue) respond_with(@issue)
......
...@@ -3,6 +3,7 @@ class Projects::RefsController < Projects::ApplicationController ...@@ -3,6 +3,7 @@ class Projects::RefsController < Projects::ApplicationController
include TreeHelper include TreeHelper
before_action :require_non_empty_project before_action :require_non_empty_project
before_action :validate_ref_id
before_action :assign_ref_vars before_action :assign_ref_vars
before_action :authorize_download_code! before_action :authorize_download_code!
...@@ -71,4 +72,10 @@ class Projects::RefsController < Projects::ApplicationController ...@@ -71,4 +72,10 @@ class Projects::RefsController < Projects::ApplicationController
format.js format.js
end end
end end
private
def validate_ref_id
return not_found! if params[:id].present? && params[:id] !~ Gitlab::Regex.git_reference_regex
end
end end
...@@ -11,18 +11,9 @@ class Projects::RepositoriesController < Projects::ApplicationController ...@@ -11,18 +11,9 @@ class Projects::RepositoriesController < Projects::ApplicationController
end end
def archive def archive
begin render json: ArchiveRepositoryService.new(@project, params[:ref], params[:format]).execute
file_path = ArchiveRepositoryService.new(@project, params[:ref], params[:format]).execute rescue => ex
rescue logger.error("#{self.class.name}: #{ex}")
return head :not_found return git_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
end end
end end
...@@ -9,6 +9,10 @@ class Projects::ServicesController < Projects::ApplicationController ...@@ -9,6 +9,10 @@ class Projects::ServicesController < Projects::ApplicationController
:note_events, :send_from_committer_email, :disable_diffs, :external_wiki_url, :note_events, :send_from_committer_email, :disable_diffs, :external_wiki_url,
:notify, :color, :notify, :color,
:server_host, :server_port, :default_irc_uri, :enable_ssl_verification] :server_host, :server_port, :default_irc_uri, :enable_ssl_verification]
# Parameters to ignore if no value is specified
FILTER_BLANK_PARAMS = [:password]
# Authorize # Authorize
before_action :authorize_admin_project! before_action :authorize_admin_project!
before_action :service, only: [:edit, :update, :test] before_action :service, only: [:edit, :update, :test]
...@@ -59,7 +63,9 @@ class Projects::ServicesController < Projects::ApplicationController ...@@ -59,7 +63,9 @@ class Projects::ServicesController < Projects::ApplicationController
def service_params def service_params
service_params = params.require(:service).permit(ALLOWED_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 service_params
end end
end end
...@@ -68,13 +68,17 @@ module ApplicationHelper ...@@ -68,13 +68,17 @@ module ApplicationHelper
end end
end end
def avatar_icon(user_email = '', size = nil) def avatar_icon(user_or_email = nil, size = nil)
user = User.find_by(email: user_email) if user_or_email.is_a?(User)
user = user_or_email
else
user = User.find_by(email: user_or_email)
end
if user if user
user.avatar_url(size) || default_avatar user.avatar_url(size) || default_avatar
else else
gravatar_icon(user_email, size) gravatar_icon(user_or_email, size)
end end
end end
......
...@@ -25,6 +25,10 @@ module GitlabRoutingHelper ...@@ -25,6 +25,10 @@ module GitlabRoutingHelper
namespace_project_commits_path(project.namespace, project, @ref || project.repository.root_ref) namespace_project_commits_path(project.namespace, project, @ref || project.repository.root_ref)
end end
def project_builds_path(project, *args)
namespace_project_builds_path(project.namespace, project, *args)
end
def activity_project_path(project, *args) def activity_project_path(project, *args)
activity_namespace_project_path(project.namespace, project, *args) activity_namespace_project_path(project.namespace, project, *args)
end end
......
...@@ -47,7 +47,7 @@ module MergeRequestsHelper ...@@ -47,7 +47,7 @@ module MergeRequestsHelper
end end
def issues_sentence(issues) def issues_sentence(issues)
issues.map { |i| "##{i.iid}" }.to_sentence issues.map(&:to_reference).to_sentence
end end
def mr_change_branches_path(merge_request) def mr_change_branches_path(merge_request)
......
...@@ -29,7 +29,7 @@ module ProjectsHelper ...@@ -29,7 +29,7 @@ module ProjectsHelper
author_html = "" author_html = ""
# Build avatar image tag # 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 # Build name span tag
author_html << content_tag(:span, sanitize(author.name), class: opts[:author_class]) if opts[:name] author_html << content_tag(:span, sanitize(author.name), class: opts[:author_class]) if opts[:name]
...@@ -113,6 +113,10 @@ module ProjectsHelper ...@@ -113,6 +113,10 @@ module ProjectsHelper
nav_tabs << :merge_requests nav_tabs << :merge_requests
end end
if can?(current_user, :read_build, project)
nav_tabs << :builds
end
if can?(current_user, :admin_project, project) if can?(current_user, :admin_project, project)
nav_tabs << :settings nav_tabs << :settings
end end
......
...@@ -13,4 +13,17 @@ module RunnersHelper ...@@ -13,4 +13,17 @@ module RunnersHelper
title: "Runner is #{status}, last contact was #{time_ago_in_words(runner.contacted_at)} ago" title: "Runner is #{status}, last contact was #{time_ago_in_words(runner.contacted_at)} ago"
end end
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 end
...@@ -41,6 +41,7 @@ class Ability ...@@ -41,6 +41,7 @@ class Ability
:read_project_member, :read_project_member,
:read_merge_request, :read_merge_request,
:read_note, :read_note,
:read_build,
:download_code :download_code
] ]
...@@ -127,6 +128,7 @@ class Ability ...@@ -127,6 +128,7 @@ class Ability
:read_project_member, :read_project_member,
:read_merge_request, :read_merge_request,
:read_note, :read_note,
:read_build,
:create_project, :create_project,
:create_issue, :create_issue,
:create_note :create_note
......
...@@ -93,10 +93,7 @@ module Ci ...@@ -93,10 +93,7 @@ module Ci
Ci::WebHookService.new.build_end(build) Ci::WebHookService.new.build_end(build)
end end
if build.commit.should_create_next_builds?(build) build.commit.create_next_builds(build)
build.commit.create_next_builds(build.ref, build.tag, build.user, build.trigger_request)
end
project.execute_services(build) project.execute_services(build)
if project.coverage_enabled? if project.coverage_enabled?
......
...@@ -24,6 +24,8 @@ module Ci ...@@ -24,6 +24,8 @@ module Ci
has_many :builds, class_name: 'Ci::Build' has_many :builds, class_name: 'Ci::Build'
has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest' 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 validates_presence_of :sha
validate :valid_commit_sha validate :valid_commit_sha
...@@ -89,19 +91,28 @@ module Ci ...@@ -89,19 +91,28 @@ module Ci
def create_builds(ref, tag, user, trigger_request = nil) def create_builds(ref, tag, user, trigger_request = nil)
return unless config_processor return unless config_processor
config_processor.stages.any? do |stage| 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
end end
def create_next_builds(ref, tag, user, trigger_request) def create_next_builds(build)
return unless config_processor 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| # get list of stages after this build
unless stages.include?(stage) next_stages = config_processor.stages.drop_while { |stage| stage != build.stage }
CreateBuildsService.new.execute(self, stage, ref, tag, user, trigger_request).present? next_stages.delete(build.stage)
end
# 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
end end
...@@ -130,24 +141,7 @@ module Ci ...@@ -130,24 +141,7 @@ module Ci
return 'failed' return 'failed'
end end
@status ||= begin @status ||= Ci::Status.get_status(latest_statuses)
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
end end
def pending? def pending?
...@@ -217,16 +211,6 @@ module Ci ...@@ -217,16 +211,6 @@ module Ci
update!(committed_at: DateTime.now) update!(committed_at: DateTime.now)
end 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 private
def save_yaml_error(error) def save_yaml_error(error)
......
...@@ -205,7 +205,7 @@ module Ci ...@@ -205,7 +205,7 @@ module Ci
end end
def commits def commits
gl_project.ci_commits gl_project.ci_commits.ordered
end end
def builds def builds
......
...@@ -59,7 +59,7 @@ module Ci ...@@ -59,7 +59,7 @@ module Ci
end end
def display_name def display_name
return token unless !description.blank? return short_sha unless !description.blank?
description description
end end
...@@ -95,7 +95,7 @@ module Ci ...@@ -95,7 +95,7 @@ module Ci
end end
def short_sha def short_sha
token[0...10] token[0...8] if token
end end
end end
end end
...@@ -16,6 +16,7 @@ class CommitStatus < ActiveRecord::Base ...@@ -16,6 +16,7 @@ class CommitStatus < ActiveRecord::Base
scope :success, -> { where(status: 'success') } scope :success, -> { where(status: 'success') }
scope :failed, -> { where(status: 'failed') } scope :failed, -> { where(status: 'failed') }
scope :running_or_pending, -> { where(status:[:running, :pending]) } 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 :latest, -> { where(id: unscope(:select).select('max(id)').group(:name, :ref)) }
scope :ordered, -> { order(:ref, :stage_idx, :name) } scope :ordered, -> { order(:ref, :stage_idx, :name) }
scope :for_ref, ->(ref) { where(ref: ref) } scope :for_ref, ->(ref) { where(ref: ref) }
...@@ -27,7 +28,7 @@ class CommitStatus < ActiveRecord::Base ...@@ -27,7 +28,7 @@ class CommitStatus < ActiveRecord::Base
end end
event :drop do event :drop do
transition running: :failed transition [:pending, :running] => :failed
end end
event :success do event :success do
......
...@@ -47,7 +47,7 @@ module Issuable ...@@ -47,7 +47,7 @@ module Issuable
prefix: true prefix: true
attr_mentionable :title, :description attr_mentionable :title, :description
participant :author, :assignee, :notes participant :author, :assignee, :notes_with_associations
end end
module ClassMethods module ClassMethods
...@@ -176,6 +176,10 @@ module Issuable ...@@ -176,6 +176,10 @@ module Issuable
self.class.to_s.underscore self.class.to_s.underscore
end end
def notes_with_associations
notes.includes(:author, :project)
end
private private
def filter_superceded_votes(votes, notes) def filter_superceded_votes(votes, notes)
......
...@@ -64,7 +64,7 @@ class Group < Namespace ...@@ -64,7 +64,7 @@ class Group < Namespace
end end
def owners def owners
@owners ||= group_members.owners.map(&:user) @owners ||= group_members.owners.includes(:user).map(&:user)
end end
def add_users(user_ids, access_level, current_user = nil) def add_users(user_ids, access_level, current_user = nil)
......
...@@ -60,6 +60,11 @@ class Note < ActiveRecord::Base ...@@ -60,6 +60,11 @@ class Note < ActiveRecord::Base
scope :inc_author_project, ->{ includes(:project, :author) } scope :inc_author_project, ->{ includes(:project, :author) }
scope :inc_author, ->{ includes(: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 serialize :st_diff
before_create :set_diff, if: ->(n) { n.line_code.present? } before_create :set_diff, if: ->(n) { n.line_code.present? }
......
...@@ -119,7 +119,7 @@ class Project < ActiveRecord::Base ...@@ -119,7 +119,7 @@ class Project < ActiveRecord::Base
has_many :deploy_keys, through: :deploy_keys_projects has_many :deploy_keys, through: :deploy_keys_projects
has_many :users_star_projects, dependent: :destroy has_many :users_star_projects, dependent: :destroy
has_many :starrers, through: :users_star_projects, source: :user 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_many :ci_builds, through: :ci_commits, source: :builds, dependent: :destroy, class_name: 'Ci::Build'
has_one :import_data, dependent: :destroy, class_name: "ProjectImportData" has_one :import_data, dependent: :destroy, class_name: "ProjectImportData"
......
...@@ -48,7 +48,7 @@ class BambooService < CiService ...@@ -48,7 +48,7 @@ class BambooService < CiService
end end
def reset_password def reset_password
if prop_updated?(:bamboo_url) if bamboo_url_changed? && !password_touched?
self.password = nil self.password = nil
end end
end end
......
...@@ -45,7 +45,7 @@ class TeamcityService < CiService ...@@ -45,7 +45,7 @@ class TeamcityService < CiService
end end
def reset_password def reset_password
if prop_updated?(:teamcity_url) if teamcity_url_changed? && !password_touched?
self.password = nil self.password = nil
end end
end end
......
...@@ -139,15 +139,28 @@ class ProjectTeam ...@@ -139,15 +139,28 @@ class ProjectTeam
Gitlab::Access.options.key max_member_access(user_id) Gitlab::Access.options.key max_member_access(user_id)
end end
# This method assumes project and group members are eager loaded for optimal
# performance.
def max_member_access(user_id) def max_member_access(user_id)
access = [] 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 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 end
access.compact.max access.max
end end
private private
......
...@@ -33,6 +33,8 @@ class Service < ActiveRecord::Base ...@@ -33,6 +33,8 @@ class Service < ActiveRecord::Base
after_initialize :initialize_properties after_initialize :initialize_properties
after_commit :reset_updated_properties
belongs_to :project belongs_to :project
has_one :service_hook has_one :service_hook
...@@ -103,6 +105,7 @@ class Service < ActiveRecord::Base ...@@ -103,6 +105,7 @@ class Service < ActiveRecord::Base
# Provide convenient accessor methods # Provide convenient accessor methods
# for each serialized property. # for each serialized property.
# Also keep track of updated properties in a similar way as ActiveModel::Dirty
def self.prop_accessor(*args) def self.prop_accessor(*args)
args.each do |arg| args.each do |arg|
class_eval %{ class_eval %{
...@@ -111,21 +114,39 @@ class Service < ActiveRecord::Base ...@@ -111,21 +114,39 @@ class Service < ActiveRecord::Base
end end
def #{arg}=(value) def #{arg}=(value)
updated_properties['#{arg}'] = #{arg} unless #{arg}_changed?
self.properties['#{arg}'] = value self.properties['#{arg}'] = value
end 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
end end
# ActiveRecord does not provide a mechanism to track changes in serialized keys. # Returns a hash of the properties that have been assigned a new value since last save,
# This is why we need to perform extra query to do it mannually. # indicating their original values (attr => original value).
def prop_updated?(prop_name) # ActiveRecord does not provide a mechanism to track changes in serialized keys,
relation_name = self.type.underscore # so we need a specific implementation for service properties.
previous_value = project.send(relation_name).send(prop_name) # This allows to track changes to properties set with the accessor methods,
return false if previous_value.nil? # but not direct manipulation of properties hash.
previous_value != send(prop_name) def updated_properties
@updated_properties ||= ActiveSupport::HashWithIndifferentAccess.new
end end
def reset_updated_properties
@updated_properties = nil
end
def async_execute(data) def async_execute(data)
return unless supported_events.include?(data[:object_kind]) return unless supported_events.include?(data[:object_kind])
......
...@@ -68,6 +68,7 @@ class User < ActiveRecord::Base ...@@ -68,6 +68,7 @@ class User < ActiveRecord::Base
include Referable include Referable
include Sortable include Sortable
include TokenAuthenticatable include TokenAuthenticatable
include CaseSensitivity
default_value_for :admin, false default_value_for :admin, false
default_value_for :can_create_group, gitlab_config.default_can_create_group default_value_for :can_create_group, gitlab_config.default_can_create_group
...@@ -273,8 +274,13 @@ class User < ActiveRecord::Base ...@@ -273,8 +274,13 @@ class User < ActiveRecord::Base
end end
def by_login(login) def by_login(login)
where('lower(username) = :value OR lower(email) = :value', return nil unless login
value: login.to_s.downcase).first
if login.include?('@'.freeze)
unscoped.iwhere(email: login).take
else
unscoped.iwhere(username: login).take
end
end end
def find_by_username!(username) def find_by_username!(username)
......
...@@ -9,17 +9,10 @@ class ArchiveRepositoryService ...@@ -9,17 +9,10 @@ class ArchiveRepositoryService
def execute(options = {}) def execute(options = {})
project.repository.clean_old_archives 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? metadata
unless archiving?
RepositoryArchiveWorker.perform_async(project.id, ref, format)
end
archived = wait_until_archived(options[:timeout] || 5.0)
file_path if archived
end end
private private
...@@ -27,36 +20,4 @@ class ArchiveRepositoryService ...@@ -27,36 +20,4 @@ class ArchiveRepositoryService
def storage_path def storage_path
Gitlab.config.gitlab.repository_downloads_path Gitlab.config.gitlab.repository_downloads_path
end 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 end
module Ci module Ci
class CreateBuildsService 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) 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| builds_attrs.map do |build_attrs|
# don't create the same build twice # don't create the same build twice
unless commit.builds.find_by(ref: ref, tag: tag, trigger_request: trigger_request, name: build_attrs[:name]) unless commit.builds.find_by(ref: ref, tag: tag, trigger_request: trigger_request, name: build_attrs[:name])
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
= @user.name = @user.name
%ul.well-list %ul.well-list
%li %li
= image_tag avatar_icon(@user.email, 60), class: "avatar s60" = image_tag avatar_icon(@user, 60), class: "avatar s60"
%li %li
%span.light Profile page: %span.light Profile page:
%strong %strong
......
...@@ -7,4 +7,4 @@ ...@@ -7,4 +7,4 @@
= link_to_gfm issue.title, [project.namespace.becomes(Namespace), project, issue], title: issue.title = link_to_gfm issue.title, [project.namespace.becomes(Namespace), project, issue], title: issue.title
.pull-right.assignee-icon .pull-right.assignee-icon
- if issue.assignee - 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 @@ ...@@ -7,4 +7,4 @@
= link_to_gfm merge_request.title, [project.namespace.becomes(Namespace), project, merge_request], title: merge_request.title = link_to_gfm merge_request.title, [project.namespace.becomes(Namespace), project, merge_request], title: merge_request.title
.pull-right.assignee-icon .pull-right.assignee-icon
- if merge_request.assignee - 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 @@ ...@@ -79,7 +79,7 @@
- @dashboard_milestone.participants.each do |user| - @dashboard_milestone.participants.each do |user|
%li %li
= link_to user, title: user.name, class: "darken" do = 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) %strong= truncate(user.name, lenght: 40)
%br %br
%small.cgray= user.username %small.cgray= user.username
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
%li{class: "#{dom_class(member)} js-toggle-container", id: dom_id(member)} %li{class: "#{dom_class(member)} js-toggle-container", id: dom_id(member)}
%span{class: ("list-item-name" if show_controls)} %span{class: ("list-item-name" if show_controls)}
- if member.user - 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 %strong
= link_to user.name, user_path(user) = link_to user.name, user_path(user)
%span.cgray= user.username %span.cgray= user.username
......
...@@ -7,4 +7,4 @@ ...@@ -7,4 +7,4 @@
= link_to_gfm issue.title, [project.namespace.becomes(Namespace), project, issue], title: issue.title = link_to_gfm issue.title, [project.namespace.becomes(Namespace), project, issue], title: issue.title
.pull-right.assignee-icon .pull-right.assignee-icon
- if issue.assignee - 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 @@ ...@@ -7,4 +7,4 @@
= link_to_gfm merge_request.title, [project.namespace.becomes(Namespace), project, merge_request], title: merge_request.title = link_to_gfm merge_request.title, [project.namespace.becomes(Namespace), project, merge_request], title: merge_request.title
.pull-right.assignee-icon .pull-right.assignee-icon
- if merge_request.assignee - 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 @@ ...@@ -87,7 +87,7 @@
- @group_milestone.participants.each do |user| - @group_milestone.participants.each do |user|
%li %li
= link_to user, title: user.name, class: "darken" do = 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) %strong= truncate(user.name, lenght: 40)
%br %br
%small.cgray= user.username %small.cgray= user.username
...@@ -99,6 +99,12 @@ ...@@ -99,6 +99,12 @@
.key c .key c
%td %td
Go to commits Go to commits
%tr
%td.shortcut
.key g
.key b
%td
Go to builds
%tr %tr
%td.shortcut %td.shortcut
.key g .key g
......
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
= render partial: 'layouts/collapse_button' = render partial: 'layouts/collapse_button'
- if current_user - if current_user
= link_to current_user, class: 'sidebar-user' do = 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 .username
= current_user.username = current_user.username
.content-wrapper .content-wrapper
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
= render partial: 'layouts/collapse_button' = render partial: 'layouts/collapse_button'
- if current_user - if current_user
= link_to current_user, class: 'sidebar-user' do = 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 .username
= current_user.username = current_user.username
.content-wrapper .content-wrapper
......
...@@ -32,12 +32,20 @@ ...@@ -32,12 +32,20 @@
Files Files
- if project_nav_tab? :commits - 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 = link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits', data: {placement: 'right'} do
= icon('history fw') = icon('history fw')
%span %span
Commits 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 - if project_nav_tab? :network
= nav_link(controller: %w(network)) do = 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 = link_to namespace_project_network_path(@project.namespace, @project, current_ref), title: 'Network', class: 'shortcuts-network', data: {placement: 'right'} do
......
...@@ -68,7 +68,7 @@ ...@@ -68,7 +68,7 @@
.col-md-5 .col-md-5
.light-well .light-well
= image_tag avatar_icon(@user.email, 160), alt: '', class: 'avatar s160' = image_tag avatar_icon(@user, 160), alt: '', class: 'avatar s160'
.clearfix .clearfix
.profile-avatar-form-option .profile-avatar-form-option
......
...@@ -2,10 +2,10 @@ ...@@ -2,10 +2,10 @@
.md-header.clearfix .md-header.clearfix
%ul.center-top-menu %ul.center-top-menu
%li.active %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 Write
%li %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 Preview
- if defined?(referenced_users) && referenced_users - if defined?(referenced_users) && referenced_users
......
.zennable .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 .zen-backdrop
- classes << ' js-gfm-input markdown-area' - classes << ' js-gfm-input markdown-area'
= f.text_area attr, class: classes, placeholder: '' = 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 %i.fa.fa-expand
Edit in fullscreen Edit in fullscreen
= link_to nil, class: 'zen-leave-link' do %a.zen-leave-link(href="#")
%i.fa.fa-compress %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
...@@ -44,16 +44,14 @@ ...@@ -44,16 +44,14 @@
.bs-callout.bs-callout-warning .bs-callout.bs-callout-warning
%p %p
- if no_runners_for_project?(@build.project) - if no_runners_for_project?(@build.project)
This build is stuck, because the project doesn't have runners assigned. This build is stuck, because the project doesn't have any runners online assigned to it.
- elsif @build.tags.any? - elsif @build.tags.any?
This build is stuck. This build is stuck, because you don't have any active runners online with any of these tags assigned to them:
%br
This build is stuck, because you don't have any active runners online with these tags assigned to the project:
- @build.tags.each do |tag| - @build.tags.each do |tag|
%span.label.label-primary %span.label.label-primary
= tag = tag
- else - else
This build is stuck, because you don't have any active runners online that can run this build. This build is stuck, because you don't have any active runners that can run this build.
%br %br
Go to Go to
......
...@@ -36,7 +36,8 @@ ...@@ -36,7 +36,8 @@
- if @merge_request.open? && @merge_request.can_be_merged? - if @merge_request.open? && @merge_request.can_be_merged?
.light.append-bottom-20 .light.append-bottom-20
You can also accept this merge request manually using the You can also accept this merge request manually using the
= link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal" = 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? - if @commits.present?
%ul.merge-request-tabs %ul.merge-request-tabs
......
%li{ id: dom_id(issue, 'sortable'), class: 'issue-row', 'data-iid' => issue.iid, 'data-url' => issue_path(issue) } %li{ id: dom_id(issue, 'sortable'), class: 'issue-row', 'data-iid' => issue.iid, 'data-url' => issue_path(issue) }
.pull-right.assignee-icon .pull-right.assignee-icon
- if issue.assignee - 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 %span
= link_to [@project.namespace.becomes(Namespace), @project, issue] do = link_to [@project.namespace.becomes(Namespace), @project, issue] do
%span.cgray ##{issue.iid} %span.cgray ##{issue.iid}
......
...@@ -5,4 +5,4 @@ ...@@ -5,4 +5,4 @@
= link_to_gfm merge_request.title, [@project.namespace.becomes(Namespace), @project, merge_request], title: merge_request.title = link_to_gfm merge_request.title, [@project.namespace.becomes(Namespace), @project, merge_request], title: merge_request.title
.pull-right.assignee-icon .pull-right.assignee-icon
- if merge_request.assignee - 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 @@ ...@@ -104,7 +104,7 @@
- @users.each do |user| - @users.each do |user|
%li %li
= link_to user, title: user.name, class: "darken" do = 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) %strong= truncate(user.name, lenght: 40)
%br %br
%small.cgray= user.username %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 } } %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-entry-inner
.timeline-icon .timeline-icon
= link_to user_path(note.author) do %a{href: user_path(note.author)}
= image_tag avatar_icon(note.author_email), class: 'avatar s40', alt: '' %img.avatar.s40{src: avatar_icon(note.author), alt: ''}
.timeline-content .timeline-content
.note-header .note-header
- if note_editable?(note) - if note_editable?(note)
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
= '@' + note.author.username = '@' + note.author.username
%span.note-last-update %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') = time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note_created_ago')
- if note.updated_at != note.created_at - if note.updated_at != note.created_at
%span %span
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
%li{class: "#{dom_class(member)} js-toggle-container project_member_row access-#{member.human_access.downcase}", id: dom_id(member)} %li{class: "#{dom_class(member)} js-toggle-container project_member_row access-#{member.human_access.downcase}", id: dom_id(member)}
%span.list-item-name %span.list-item-name
- if member.user - 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 %strong
= link_to user.name, user_path(user) = link_to user.name, user_path(user)
%span.cgray= user.username %span.cgray= user.username
......
...@@ -9,8 +9,8 @@ ...@@ -9,8 +9,8 @@
.row .row
%section.col-md-7 %section.col-md-7
.header-with-avatar .header-with-avatar
= link_to avatar_icon(@user.email, 400), target: '_blank' do = link_to avatar_icon(@user, 400), target: '_blank' do
= image_tag avatar_icon(@user.email, 90), class: "avatar avatar-tile s90", alt: '' = image_tag avatar_icon(@user, 90), class: "avatar avatar-tile s90", alt: ''
%h3 %h3
= @user.name = @user.name
- if @user == current_user - if @user == current_user
......
...@@ -24,7 +24,7 @@ Gitlab::Application.configure do ...@@ -24,7 +24,7 @@ Gitlab::Application.configure do
# Expands the lines which load the assets # Expands the lines which load the assets
# config.assets.debug = true # config.assets.debug = true
# Adds additional error checking when serving assets at runtime. # Adds additional error checking when serving assets at runtime.
# Checks for improperly declared sprockets dependencies. # Checks for improperly declared sprockets dependencies.
# Raises helpful error messages. # Raises helpful error messages.
......
...@@ -99,7 +99,29 @@ production: &base ...@@ -99,7 +99,29 @@ production: &base
# For documentation on how to set this up, see http://doc.gitlab.com/ce/incoming_email/README.html # For documentation on how to set this up, see http://doc.gitlab.com/ce/incoming_email/README.html
incoming_email: incoming_email:
enabled: false 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 ## Gravatar
## For Libravatar see: http://doc.gitlab.com/ce/customization/libravatar.html ## For Libravatar see: http://doc.gitlab.com/ce/customization/libravatar.html
......
...@@ -187,7 +187,11 @@ Settings.gitlab_ci['builds_path'] = File.expand_path(Settings.gitlab_ci[ ...@@ -187,7 +187,11 @@ Settings.gitlab_ci['builds_path'] = File.expand_path(Settings.gitlab_ci[
# Reply by email # Reply by email
# #
Settings['incoming_email'] ||= Settingslogic.new({}) Settings['incoming_email'] ||= Settingslogic.new({})
Settings.incoming_email['enabled'] = false if Settings.incoming_email['enabled'].nil? 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 # 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 ...@@ -543,8 +543,10 @@ Gitlab::Application.routes.draw do
member do member do
# tree viewer logs # tree viewer logs
get 'logs_tree', constraints: { id: Gitlab::Regex.git_reference_regex } 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: { get 'logs_tree/*path' => 'refs#logs_tree', as: :logs_file, constraints: {
id: Gitlab::Regex.git_reference_regex, id: /.*/,
path: /.*/ path: /.*/
} }
end end
...@@ -585,7 +587,11 @@ Gitlab::Application.routes.draw do ...@@ -585,7 +587,11 @@ Gitlab::Application.routes.draw do
end end
end end
resources :builds, only: [:show] do resources :builds, only: [:index, :show] do
collection do
get :cancel_all
end
member do member do
get :cancel get :cancel
get :status 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
...@@ -140,6 +140,7 @@ job_name: ...@@ -140,6 +140,7 @@ job_name:
| except | optional | Defines a list of git refs for which build is not created | | 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 | | 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 | | 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
`script` is a shell script which is executed by runner. The shell script is prepended with `before_script`. `script` is a shell script which is executed by runner. The shell script is prepended with `before_script`.
...@@ -196,9 +197,58 @@ job: ...@@ -196,9 +197,58 @@ job:
The above specification will make sure that `job` is built by a runner that have `ruby` AND `postgres` tags defined. 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 ## Validate the .gitlab-ci.yml
Each instance of GitLab CI has an embedded debug tool called Lint. 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`. You can find the link to the Lint in the project's settings page or use short url `/lint`.
## Skipping builds ## Skipping builds
There is one more way to skip all builds, if your commit message contains tag [ci skip]. In this case, commit will be created but builds will be skipped There is one more way to skip all builds, if your commit message contains tag [ci skip]. In this case, commit will be created but builds will be skipped
\ No newline at end of file
# 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 ...@@ -4,9 +4,9 @@ GitLab can be set up to allow users to comment on issues and merge requests by r
## Get a mailbox ## 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). 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 ...@@ -14,30 +14,62 @@ To set up a basic Postfix mail server with IMAP access on Ubuntu, follow [these
### Omnibus package installations ### 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 ```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 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_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 # Email account username
gitlab_rails['incoming_email_ssl'] = false # Whether the IMAP server uses SSL # With third party providers, this is usually the full email address.
gitlab_rails['incoming_email_email'] = "incoming" # Email account username. Usually the full email address. # With self-hosted email servers, this is usually the user part of the email address.
gitlab_rails['incoming_email_password'] = "[REDACTED]" # Email account password gitlab_rails['incoming_email_email'] = "incoming"
gitlab_rails['incoming_email_mailbox_name'] = "inbox" # The name of the mailbox where incoming mail will end up. Usually "inbox". # 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 ```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 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_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 # Email account username
gitlab_rails['incoming_email_ssl'] = true # Whether the IMAP server uses SSL # With third party providers, this is usually the full email address.
gitlab_rails['incoming_email_email'] = "gitlab-incoming@gmail.com" # Email account username. Usually the full email address. # With self-hosted email servers, this is usually the user part of the email address.
gitlab_rails['incoming_email_password'] = "[REDACTED]" # Email account password gitlab_rails['incoming_email_email'] = "gitlab-incoming@gmail.com"
gitlab_rails['incoming_email_mailbox_name'] = "inbox" # The name of the mailbox where incoming mail will end up. Usually "inbox". # 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`. 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 ...@@ -64,229 +96,146 @@ To set up a basic Postfix mail server with IMAP access on Ubuntu, follow [these
cd /home/git/gitlab 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 ```sh
sudo editor config/gitlab.yml sudo editor config/gitlab.yml
``` ```
```yaml ```yaml
# Postfix mail server, assumes mailbox incoming@gitlab.example.com # Configuration for Postfix mail server, assumes mailbox incoming@gitlab.example.com
incoming_email: incoming_email:
enabled: true 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: "incoming+%{key}@gitlab.example.com" address: "incoming+%{key}@gitlab.example.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: "incoming"
# Email account password
password: "[REDACTED]"
# IMAP server host
host: "gitlab.example.com"
# IMAP server port
port: 143
# Whether the IMAP server uses SSL
ssl: false
# Whether the IMAP server uses StartTLS
start_tls: false
# The mailbox where incoming mail will end up. Usually "inbox".
mailbox: "inbox"
``` ```
```yaml ```yaml
# Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com # Configuration for Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com
incoming_email: incoming_email:
enabled: true 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`: # 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"
```sh # Email account username
sudo cp config/mail_room.yml.example config/mail_room.yml # 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]"
3. Uncomment the configuration options in `config/mail_room.yml` and fill in the details for your specific IMAP server and email account: # 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
```sh # The mailbox where incoming mail will end up. Usually "inbox".
sudo editor config/mail_room.yml mailbox: "inbox"
```
```yaml
# Postfix mail server
:mailboxes:
-
# IMAP server host
:host: "gitlab.example.com"
# IMAP server port
:port: 143
# Whether the IMAP server uses SSL
: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
``` ```
```yaml 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`.
# Gmail / Google Apps
: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: "[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
```
5. Edit the init script configuration at `/etc/default/gitlab` to enable `mail_room`: 1. Enable `mail_room` in the init script at `/etc/default/gitlab`:
```sh ```sh
sudo mkdir -p /etc/default sudo mkdir -p /etc/default
echo 'mail_room_enabled=true' | sudo tee -a /etc/default/gitlab echo 'mail_room_enabled=true' | sudo tee -a /etc/default/gitlab
``` ```
6. Restart GitLab: 1. Restart GitLab:
```sh ```sh
sudo service gitlab restart sudo service gitlab restart
``` ```
7. Verify that everything is configured correctly: 1. Verify that everything is configured correctly:
```sh ```sh
sudo -u git -H bundle exec rake gitlab:incoming_email:check RAILS_ENV=production 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 ### Development
1. Go to the GitLab installation directory. 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 ```yaml
# Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com # Configuration for Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com
incoming_email: incoming_email:
enabled: true 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 `@`.
address: "gitlab-incoming+%{key}@gmail.com" 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`. # 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]"
2. Copy `config/mail_room.yml.example` to `config/mail_room.yml`: # 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
```sh # The mailbox where incoming mail will end up. Usually "inbox".
sudo cp config/mail_room.yml.example config/mail_room.yml mailbox: "inbox"
``` ```
3. Uncomment the configuration options in `config/mail_room.yml` and fill in the details for your specific IMAP server and email account: As mentioned, the part after `+` is ignored, and this will end up in the mailbox for `gitlab-incoming@gmail.com`.
```yaml
# Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com
: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: "[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
```
4. Uncomment the `mail_room` line in your `Procfile`: 1. Uncomment the `mail_room` line in your `Procfile`:
```yaml ```yaml
mail_room: bundle exec mail_room -q -c config/mail_room.yml mail_room: bundle exec mail_room -q -c config/mail_room.yml
``` ```
6. Restart GitLab: 1. Restart GitLab:
```sh ```sh
bundle exec foreman start bundle exec foreman start
``` ```
7. Verify that everything is configured correctly: 1. Verify that everything is configured correctly:
```sh ```sh
bundle exec rake gitlab:incoming_email:check RAILS_ENV=development 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 ...@@ -211,9 +211,9 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
### Clone the Source ### Clone the Source
# Clone GitLab repository # 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 ### Configure It
......
...@@ -16,7 +16,7 @@ You need to keep a separate copy of `/etc/gitlab/gitlab-secrets.json` ...@@ -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 from source). This file contains the database encryption key used
for two-factor authentication. If you restore a GitLab backup without for two-factor authentication. If you restore a GitLab backup without
restoring the database encryption key, users who have two-factor 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)* 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)*
......
# 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).
...@@ -205,3 +205,9 @@ Feature: Project Source Browse Files ...@@ -205,3 +205,9 @@ Feature: Project Source Browse Files
And I see the ref 'test' has been selected And I see the ref 'test' has been selected
And I visit the 'test' tree And I visit the 'test' tree
Then I see the commit data 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
...@@ -113,7 +113,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps ...@@ -113,7 +113,7 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
end end
step 'I click status link' do step 'I click status link' do
click_link "Builds" find('.commit-ci-menu').click_link "Builds"
end end
step 'I see builds list' do step 'I see builds list' do
......
...@@ -286,6 +286,10 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps ...@@ -286,6 +286,10 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
select "'test'", from: 'ref' select "'test'", from: 'ref'
end end
step "I switch ref to fix" do
select "fix", from: 'ref'
end
step "I see the ref 'test' has been selected" do step "I see the ref 'test' has been selected" do
expect(page).to have_selector '.select2-chosen', text: "'test'" expect(page).to have_selector '.select2-chosen', text: "'test'"
end end
...@@ -294,11 +298,20 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps ...@@ -294,11 +298,20 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
visit namespace_project_tree_path(@project.namespace, @project, "'test'") visit namespace_project_tree_path(@project.namespace, @project, "'test'")
end 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 step 'I see the commit data' do
expect(page).to have_css('.tree-commit-link', visible: true) expect(page).to have_css('.tree-commit-link', visible: true)
expect(page).not_to have_content('Loading commit data...') expect(page).not_to have_content('Loading commit data...')
end 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 private
def set_new_content def set_new_content
......
...@@ -249,8 +249,16 @@ module API ...@@ -249,8 +249,16 @@ module API
required_attributes! [:note] required_attributes! [:note]
merge_request = user_project.merge_requests.find(params[:merge_request_id]) 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 if note.save
present note, with: Entities::MRNote present note, with: Entities::MRNote
......
...@@ -133,7 +133,7 @@ module API ...@@ -133,7 +133,7 @@ module API
authorize! :download_code, user_project authorize! :download_code, user_project
begin begin
file_path = ArchiveRepositoryService.new( ArchiveRepositoryService.new(
user_project, user_project,
params[:sha], params[:sha],
params[:format] params[:format]
...@@ -141,17 +141,6 @@ module API ...@@ -141,17 +141,6 @@ module API
rescue rescue
not_found!('File') not_found!('File')
end 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 end
# Compare two branches, tags or commits # Compare two branches, tags or commits
......
...@@ -5,7 +5,7 @@ module Ci ...@@ -5,7 +5,7 @@ module Ci
DEFAULT_STAGES = %w(build test deploy) DEFAULT_STAGES = %w(build test deploy)
DEFAULT_STAGE = 'test' DEFAULT_STAGE = 'test'
ALLOWED_YAML_KEYS = [:before_script, :image, :services, :types, :stages, :variables] 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 attr_reader :before_script, :image, :services, :variables
...@@ -93,6 +93,7 @@ module Ci ...@@ -93,6 +93,7 @@ module Ci
only: job[:only], only: job[:only],
except: job[:except], except: job[:except],
allow_failure: job[:allow_failure] || false, allow_failure: job[:allow_failure] || false,
when: job[:when] || 'on_success',
options: { options: {
image: job[:image] || @image, image: job[:image] || @image,
services: job[:services] || @services services: job[:services] || @services
...@@ -184,6 +185,10 @@ module Ci ...@@ -184,6 +185,10 @@ module Ci
if job[:allow_failure] && !job[:allow_failure].in?([true, false]) if job[:allow_failure] && !job[:allow_failure].in?([true, false])
raise ValidationError, "#{name}: allow_failure parameter should be an boolean" raise ValidationError, "#{name}: allow_failure parameter should be an boolean"
end 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 end
private 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
...@@ -193,7 +193,14 @@ module Grack ...@@ -193,7 +193,14 @@ module Grack
end end
def render_grack_auth_ok 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 end
def render_not_found def render_not_found
......
...@@ -24,12 +24,12 @@ module Gitlab ...@@ -24,12 +24,12 @@ module Gitlab
match[1] match[1]
end end
private
def config def config
Gitlab.config.incoming_email Gitlab.config.incoming_email
end end
private
def address_regex def address_regex
wildcard_address = config.address wildcard_address = config.address
return nil unless wildcard_address return nil unless wildcard_address
......
...@@ -113,7 +113,25 @@ server { ...@@ -113,7 +113,25 @@ server {
proxy_pass http://gitlab; 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 ## If you use HTTPS make sure you disable gzip compression
## to be safe against BREACH attack. ## to be safe against BREACH attack.
# gzip off; # gzip off;
......
...@@ -160,7 +160,25 @@ server { ...@@ -160,7 +160,25 @@ server {
proxy_pass http://gitlab; 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 ## If you use HTTPS make sure you disable gzip compression
## to be safe against BREACH attack. ## to be safe against BREACH attack.
gzip off; gzip off;
......
...@@ -642,7 +642,6 @@ namespace :gitlab do ...@@ -642,7 +642,6 @@ namespace :gitlab do
if Gitlab.config.incoming_email.enabled if Gitlab.config.incoming_email.enabled
check_address_formatted_correctly check_address_formatted_correctly
check_mail_room_config_exists
check_imap_authentication check_imap_authentication
if Rails.env.production? if Rails.env.production?
...@@ -744,42 +743,16 @@ namespace :gitlab do ...@@ -744,42 +743,16 @@ namespace :gitlab do
end end
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 def check_imap_authentication
print "IMAP server credentials are correct? ... " print "IMAP server credentials are correct? ... "
mail_room_config_file = Rails.root.join("config", "mail_room.yml") config = Gitlab.config.incoming_email
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
if config if config
begin begin
imap = Net::IMAP.new(config[:host], port: config[:port], ssl: config[:ssl]) imap = Net::IMAP.new(config.host, port: config.port, ssl: config.ssl)
imap.login(config[:email], config[:password]) imap.starttls if config.start_tls
imap.login(config.user, config.password)
connected = true connected = true
rescue rescue
connected = false connected = false
...@@ -791,7 +764,7 @@ namespace :gitlab do ...@@ -791,7 +764,7 @@ namespace :gitlab do
else else
puts "no".red puts "no".red
try_fixing_it( 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( for_more_information(
"doc/incoming_email/README.md" "doc/incoming_email/README.md"
......
require Rails.root.join('db/migrate/20151007120511_namespaces_projects_path_lower_indexes') 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' desc 'GitLab | Sets up PostgreSQL'
task setup_postgresql: :environment do task setup_postgresql: :environment do
NamespacesProjectsPathLowerIndexes.new.up NamespacesProjectsPathLowerIndexes.new.up
AddUsersLowerUsernameEmailIndexes.new.up
end 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 ...@@ -11,7 +11,9 @@ describe User, benchmark: true do
end end
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 describe 'using a capitalized username' do
benchmark_subject { User.by_login('Alice') } benchmark_subject { User.by_login('Alice') }
......
...@@ -33,33 +33,5 @@ describe Projects::RepositoriesController do ...@@ -33,33 +33,5 @@ describe Projects::RepositoriesController do
expect(response.status).to eq(404) expect(response.status).to eq(404)
end end
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
end end
...@@ -9,6 +9,54 @@ describe "Builds" do ...@@ -9,6 +9,54 @@ describe "Builds" do
@gl_project.team << [@user, :master] @gl_project.team << [@user, :master]
end 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 describe "GET /:project/builds/:id" do
before do before do
visit namespace_project_build_path(@gl_project.namespace, @gl_project, @build) visit namespace_project_build_path(@gl_project.namespace, @gl_project, @build)
......
...@@ -99,6 +99,15 @@ describe ApplicationHelper do ...@@ -99,6 +99,15 @@ describe ApplicationHelper do
helper.avatar_icon('foo@example.com', 20) helper.avatar_icon('foo@example.com', 20)
end 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 end
describe 'gravatar_icon' do describe 'gravatar_icon' do
......
...@@ -70,4 +70,18 @@ describe ProjectsHelper do ...@@ -70,4 +70,18 @@ describe ProjectsHelper do
expect(helper.send(:readme_cache_key)).to eq("#{project.path_with_namespace}-nil-readme") expect(helper.send(:readme_cache_key)).to eq("#{project.path_with_namespace}-nil-readme")
end end
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 end
...@@ -24,7 +24,8 @@ module Ci ...@@ -24,7 +24,8 @@ module Ci
commands: "pwd\nrspec", commands: "pwd\nrspec",
tag_list: [], tag_list: [],
options: {}, options: {},
allow_failure: false allow_failure: false,
when: "on_success"
}) })
end end
...@@ -125,7 +126,8 @@ module Ci ...@@ -125,7 +126,8 @@ module Ci
image: "ruby:2.1", image: "ruby:2.1",
services: ["mysql"] services: ["mysql"]
}, },
allow_failure: false allow_failure: false,
when: "on_success"
}) })
end end
...@@ -152,7 +154,8 @@ module Ci ...@@ -152,7 +154,8 @@ module Ci
image: "ruby:2.5", image: "ruby:2.5",
services: ["postgresql"] services: ["postgresql"]
}, },
allow_failure: false allow_failure: false,
when: "on_success"
}) })
end end
end end
...@@ -174,6 +177,21 @@ module Ci ...@@ -174,6 +177,21 @@ module Ci
end end
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 describe "Error handling" do
it "indicates that object is invalid" do it "indicates that object is invalid" do
expect{GitlabCiYamlProcessor.new("invalid_yaml\n!ccdvlf%612334@@@@")}.to raise_error(GitlabCiYamlProcessor::ValidationError) expect{GitlabCiYamlProcessor.new("invalid_yaml\n!ccdvlf%612334@@@@")}.to raise_error(GitlabCiYamlProcessor::ValidationError)
...@@ -311,6 +329,13 @@ module Ci ...@@ -311,6 +329,13 @@ module Ci
GitlabCiYamlProcessor.new(config) GitlabCiYamlProcessor.new(config)
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "variables should be a map of key-valued strings") end.to raise_error(GitlabCiYamlProcessor::ValidationError, "variables should be a map of key-valued strings")
end 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 end
end end
...@@ -32,6 +32,24 @@ describe Ci::Commit do ...@@ -32,6 +32,24 @@ describe Ci::Commit do
it { is_expected.to respond_to :git_author_email } it { is_expected.to respond_to :git_author_email }
it { is_expected.to respond_to :short_sha } 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 describe :last_build do
subject { commit.last_build } subject { commit.last_build }
before do before do
...@@ -143,28 +161,28 @@ describe Ci::Commit do ...@@ -143,28 +161,28 @@ describe Ci::Commit do
end end
describe :create_builds do 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) def create_builds(trigger_request = nil)
commit.create_builds('master', false, nil, trigger_request) commit.create_builds('master', false, nil, trigger_request)
end end
def create_next_builds(trigger_request = nil) def create_next_builds
commit.create_next_builds('master', false, nil, trigger_request) commit.create_next_builds(commit.builds.order(:id).last)
end end
it 'creates builds' do it 'creates builds' do
expect(create_builds).to be_truthy expect(create_builds).to be_truthy
commit.builds.reload commit.builds.update_all(status: "success")
expect(commit.builds.size).to eq(2) expect(commit.builds.count(:all)).to eq(2)
expect(create_next_builds).to be_truthy expect(create_next_builds).to be_truthy
commit.builds.reload commit.builds.update_all(status: "success")
expect(commit.builds.size).to eq(4) expect(commit.builds.count(:all)).to eq(4)
expect(create_next_builds).to be_truthy expect(create_next_builds).to be_truthy
commit.builds.reload commit.builds.update_all(status: "success")
expect(commit.builds.size).to eq(5) expect(commit.builds.count(:all)).to eq(5)
expect(create_next_builds).to be_falsey expect(create_next_builds).to be_falsey
end end
...@@ -176,12 +194,12 @@ describe Ci::Commit do ...@@ -176,12 +194,12 @@ describe Ci::Commit do
it 'creates builds' do it 'creates builds' do
expect(create_builds).to be_truthy expect(create_builds).to be_truthy
commit.builds.reload commit.builds.update_all(status: "success")
expect(commit.builds.size).to eq(2) expect(commit.builds.count(:all)).to eq(2)
expect(create_develop_builds).to be_truthy expect(create_develop_builds).to be_truthy
commit.builds.reload commit.builds.update_all(status: "success")
expect(commit.builds.size).to eq(4) expect(commit.builds.count(:all)).to eq(4)
expect(commit.refs.size).to eq(2) expect(commit.refs.size).to eq(2)
expect(commit.builds.pluck(:name).uniq.size).to eq(2) expect(commit.builds.pluck(:name).uniq.size).to eq(2)
end end
...@@ -193,28 +211,24 @@ describe Ci::Commit do ...@@ -193,28 +211,24 @@ describe Ci::Commit do
it 'creates builds' do it 'creates builds' do
expect(create_builds(trigger_request)).to be_truthy expect(create_builds(trigger_request)).to be_truthy
commit.builds.reload expect(commit.builds.count(:all)).to eq(2)
expect(commit.builds.size).to eq(2)
end end
it 'rebuilds commit' do it 'rebuilds commit' do
expect(create_builds).to be_truthy expect(create_builds).to be_truthy
commit.builds.reload expect(commit.builds.count(:all)).to eq(2)
expect(commit.builds.size).to eq(2)
expect(create_builds(trigger_request)).to be_truthy expect(create_builds(trigger_request)).to be_truthy
commit.builds.reload expect(commit.builds.count(:all)).to eq(4)
expect(commit.builds.size).to eq(4)
end end
it 'creates next builds' do it 'creates next builds' do
expect(create_builds(trigger_request)).to be_truthy expect(create_builds(trigger_request)).to be_truthy
commit.builds.reload expect(commit.builds.count(:all)).to eq(2)
expect(commit.builds.size).to eq(2) commit.builds.update_all(status: "success")
expect(create_next_builds(trigger_request)).to be_truthy expect(create_next_builds).to be_truthy
commit.builds.reload expect(commit.builds.count(:all)).to eq(4)
expect(commit.builds.size).to eq(4)
end end
context 'for [ci skip]' do context 'for [ci skip]' do
...@@ -224,7 +238,7 @@ describe Ci::Commit do ...@@ -224,7 +238,7 @@ describe Ci::Commit do
it 'rebuilds commit' do it 'rebuilds commit' do
expect(commit.status).to eq('skipped') 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 # since everything in Ci::Commit is cached we need to fetch a new object
new_commit = Ci::Commit.find_by_id(commit.id) new_commit = Ci::Commit.find_by_id(commit.id)
...@@ -232,6 +246,129 @@ describe Ci::Commit do ...@@ -232,6 +246,129 @@ describe Ci::Commit do
end end
end 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 end
describe "#finished_at" do describe "#finished_at" do
...@@ -281,59 +418,4 @@ describe Ci::Commit do ...@@ -281,59 +418,4 @@ describe Ci::Commit do
expect(commit.coverage).to be_nil expect(commit.coverage).to be_nil
end end
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 end
...@@ -131,24 +131,6 @@ describe Ci::Project do ...@@ -131,24 +131,6 @@ describe Ci::Project do
end end
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 context :valid_project do
let(:commit) { FactoryGirl.create(:ci_commit) } let(:commit) { FactoryGirl.create(:ci_commit) }
......
...@@ -32,7 +32,7 @@ describe Ci::Runner do ...@@ -32,7 +32,7 @@ describe Ci::Runner do
end end
it 'should return the token if the description is an empty string' do 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 expect(runner.display_name).to eq runner.token
end end
end end
......
...@@ -30,27 +30,65 @@ describe BambooService, models: true do ...@@ -30,27 +30,65 @@ describe BambooService, models: true do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:project) } let(:project) { create(:project) }
before do context "when a password was previously set" do
@bamboo_service = BambooService.create( before do
project: create(:project), @bamboo_service = BambooService.create(
properties: { project: create(:project),
bamboo_url: 'http://gitlab.com', properties: {
username: 'mic', bamboo_url: 'http://gitlab.com',
password: "password" username: 'mic',
} password: "password"
) }
end )
end
it "reset password if url changed" do
@bamboo_service.bamboo_url = 'http://gitlab1.com'
@bamboo_service.save
expect(@bamboo_service.password).to be_nil
end
it "does not reset password if username changed" do
@bamboo_service.username = "some_name"
@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 "reset password if url changed" do 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 @bamboo_service.bamboo_url = 'http://gitlab1.com'
expect(@bamboo_service.password).to be_nil @bamboo_service.save
expect(@bamboo_service.password).to be_nil
end
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
it "does not reset password if username changed" do
@bamboo_service.username = "some_name"
@bamboo_service.save
expect(@bamboo_service.password).to eq("password")
end end
end end
end end
...@@ -30,27 +30,64 @@ describe TeamcityService, models: true do ...@@ -30,27 +30,64 @@ describe TeamcityService, models: true do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:project) } let(:project) { create(:project) }
before do context "when a password was previously set" do
@teamcity_service = TeamcityService.create( before do
project: create(:project), @teamcity_service = TeamcityService.create(
properties: { project: create(:project),
teamcity_url: 'http://gitlab.com', properties: {
username: 'mic', teamcity_url: 'http://gitlab.com',
password: "password" username: 'mic',
} password: "password"
) }
end )
end
it "reset password if url changed" do
@teamcity_service.teamcity_url = 'http://gitlab1.com'
@teamcity_service.save
expect(@teamcity_service.password).to be_nil
end
it "does not reset password if username changed" do
@teamcity_service.username = "some_name"
@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 "reset password if url changed" do 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 @teamcity_service.teamcity_url = 'http://gitlab1.com'
expect(@teamcity_service.password).to be_nil @teamcity_service.save
expect(@teamcity_service.password).to be_nil
end
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 "does not reset password if username changed" do it "saves password if new url is set together with password" do
@teamcity_service.username = "some_name" @teamcity_service.teamcity_url = 'http://gitlab_edited.com'
@teamcity_service.save @teamcity_service.password = 'password'
expect(@teamcity_service.password).to eq("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 end
end end
...@@ -104,7 +104,7 @@ describe Service do ...@@ -104,7 +104,7 @@ describe Service do
end end
end end
describe "#prop_updated?" do describe "{property}_changed?" do
let(:service) do let(:service) do
BambooService.create( BambooService.create(
project: create(:project), project: create(:project),
...@@ -116,14 +116,112 @@ describe Service do ...@@ -116,14 +116,112 @@ describe Service do
) )
end end
it "returns false" do it "returns false when the property has not been assigned a new value" do
service.username = "key_changed" service.username = "key_changed"
expect(service.prop_updated?(:bamboo_url)).to be_falsy expect(service.bamboo_url_changed?).to be_falsy
end end
it "returns true" do it "returns true when the property has been assigned a different value" do
service.bamboo_url = "http://other.com" service.bamboo_url = "http://example.com"
expect(service.prop_updated?(:bamboo_url)).to be_truthy 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 end
end end
...@@ -379,9 +379,14 @@ describe API::API, api: true do ...@@ -379,9 +379,14 @@ describe API::API, api: true do
describe "POST /projects/:id/merge_request/:merge_request_id/comments" do describe "POST /projects/:id/merge_request/:merge_request_id/comments" do
it "should return comment" 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" post api("/projects/#{project.id}/merge_request/#{merge_request.id}/comments", user), note: "My comment"
expect(response.status).to eq(201) expect(response.status).to eq(201)
expect(json_response['note']).to eq('My comment') 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 end
it "should return 400 if note is missing" do it "should return 400 if note is missing" do
......
...@@ -166,24 +166,21 @@ describe API::API, api: true do ...@@ -166,24 +166,21 @@ describe API::API, api: true do
get api("/projects/#{project.id}/repository/archive", user) get api("/projects/#{project.id}/repository/archive", user)
repo_name = project.repository.name.gsub("\.git", "") repo_name = project.repository.name.gsub("\.git", "")
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(response.headers['Content-Disposition']).to match(/filename\=\"#{repo_name}\-[^\.]+\.tar.gz\"/) expect(json_response['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.tar.gz/)
expect(response.content_type).to eq(MIME::Types.type_for('file.tar.gz').first.content_type)
end end
it "should get the archive.zip" do it "should get the archive.zip" do
get api("/projects/#{project.id}/repository/archive.zip", user) get api("/projects/#{project.id}/repository/archive.zip", user)
repo_name = project.repository.name.gsub("\.git", "") repo_name = project.repository.name.gsub("\.git", "")
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(response.headers['Content-Disposition']).to match(/filename\=\"#{repo_name}\-[^\.]+\.zip\"/) expect(json_response['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.zip/)
expect(response.content_type).to eq(MIME::Types.type_for('file.zip').first.content_type)
end end
it "should get the archive.tar.bz2" do it "should get the archive.tar.bz2" do
get api("/projects/#{project.id}/repository/archive.tar.bz2", user) get api("/projects/#{project.id}/repository/archive.tar.bz2", user)
repo_name = project.repository.name.gsub("\.git", "") repo_name = project.repository.name.gsub("\.git", "")
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(response.headers['Content-Disposition']).to match(/filename\=\"#{repo_name}\-[^\.]+\.tar.bz2\"/) expect(json_response['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.tar.bz2/)
expect(response.content_type).to eq(MIME::Types.type_for('file.tar.bz2').first.content_type)
end end
it "should return 404 for invalid sha" do it "should return 404 for invalid sha" do
......
...@@ -13,7 +13,7 @@ describe ArchiveRepositoryService do ...@@ -13,7 +13,7 @@ describe ArchiveRepositoryService do
context "when the repository doesn't have an archive file path" do context "when the repository doesn't have an archive file path" do
before 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 end
it "raises an error" do it "raises an error" do
...@@ -21,70 +21,5 @@ describe ArchiveRepositoryService do ...@@ -21,70 +21,5 @@ describe ArchiveRepositoryService do
end end
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
end end
...@@ -9,7 +9,7 @@ module TestEnv ...@@ -9,7 +9,7 @@ module TestEnv
'flatten-dir' => 'e56497b', 'flatten-dir' => 'e56497b',
'feature' => '0b4bc9a', 'feature' => '0b4bc9a',
'feature_conflict' => 'bb5206f', 'feature_conflict' => 'bb5206f',
'fix' => '12d65c8', 'fix' => '48f0be4',
'improve/awesome' => '5937ac0', 'improve/awesome' => '5937ac0',
'markdown' => '0ed8c6c', 'markdown' => '0ed8c6c',
'master' => '5937ac0', '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