Commit 4d194502 authored by Timm Drevensek's avatar Timm Drevensek

Merge branch 'master' into request/relative_submodules

parents cb659e4c 7611ce72
...@@ -6,6 +6,13 @@ v 6.8.0 ...@@ -6,6 +6,13 @@ v 6.8.0
- Drop all tables before restoring a Postgres backup - Drop all tables before restoring a Postgres backup
- Make the repository downloads path configurable - Make the repository downloads path configurable
- Create branches via API (sponsored by O'Reilly Media) - Create branches via API (sponsored by O'Reilly Media)
- Changed permission of gitlab-satellites directory not to be world accessible
- Protected branch does not allow force push
v 6.7.3
- Fix the merge notification email not being sent (Pierre de La Morinerie)
- Drop all tables before restoring a Postgres backup
- Remove yanked modernizr gem
v 6.7.2 v 6.7.2
- Fix upgrader script - Fix upgrader script
......
...@@ -29,7 +29,9 @@ If something is wrong but it is not a regression compared to older versions of G ...@@ -29,7 +29,9 @@ If something is wrong but it is not a regression compared to older versions of G
When submitting an issue please conform to the issue submission guidelines listed below. When submitting an issue please conform to the issue submission guidelines listed below.
Not all issues will be addressed and your issue is more likely to be addressed if you submit a merge request which partially or fully addresses the issue. Not all issues will be addressed and your issue is more likely to be addressed if you submit a merge request which partially or fully addresses the issue.
Do not use the issue tracker for feature requests. We have a specific [feature request forum](http://feedback.gitlab.com) for this purpose. Do not use the issue tracker for feature requests.
We have a specific [feature request forum](http://feedback.gitlab.com) for this purpose.
Please keep feature requests as small and simple as possible, complex ones might be edited to make them small and simple.
Please send a merge request with a tested solution or a merge request with a failing test instead of opening an issue if you can. If you're unsure where to post, post to the [mailing list](https://groups.google.com/forum/#!forum/gitlabhq) or [Stack Overflow](http://stackoverflow.com/questions/tagged/gitlab) first. There are a lot of helpful GitLab users there who may be able to help you quickly. If your particular issue turns out to be a bug, it will find its way from there. Please send a merge request with a tested solution or a merge request with a failing test instead of opening an issue if you can. If you're unsure where to post, post to the [mailing list](https://groups.google.com/forum/#!forum/gitlabhq) or [Stack Overflow](http://stackoverflow.com/questions/tagged/gitlab) first. There are a lot of helpful GitLab users there who may be able to help you quickly. If your particular issue turns out to be a bug, it will find its way from there.
......
...@@ -12,8 +12,6 @@ gem "rails", "~> 4.0.0" ...@@ -12,8 +12,6 @@ gem "rails", "~> 4.0.0"
gem "protected_attributes" gem "protected_attributes"
gem 'rails-observers' gem 'rails-observers'
gem 'actionpack-page_caching'
gem 'actionpack-action_caching'
# Default values for AR models # Default values for AR models
gem "default_value_for", "~> 3.0.0" gem "default_value_for", "~> 3.0.0"
...@@ -102,7 +100,7 @@ gem "acts-as-taggable-on" ...@@ -102,7 +100,7 @@ gem "acts-as-taggable-on"
# Background jobs # Background jobs
gem 'slim' gem 'slim'
gem 'sinatra', require: nil gem 'sinatra', require: nil
gem 'sidekiq' gem 'sidekiq', '2.17.0'
# HTTP requests # HTTP requests
gem "httparty" gem "httparty"
...@@ -161,7 +159,6 @@ gem 'select2-rails' ...@@ -161,7 +159,6 @@ gem 'select2-rails'
gem 'jquery-atwho-rails', "~> 0.3.3" gem 'jquery-atwho-rails', "~> 0.3.3"
gem "jquery-rails", "2.1.3" gem "jquery-rails", "2.1.3"
gem "jquery-ui-rails", "2.0.2" gem "jquery-ui-rails", "2.0.2"
gem "modernizr", "2.6.2"
gem "raphael-rails", "~> 2.1.2" gem "raphael-rails", "~> 2.1.2"
gem 'bootstrap-sass', '~> 3.0' gem 'bootstrap-sass', '~> 3.0'
gem "font-awesome-rails", '~> 3.2' gem "font-awesome-rails", '~> 3.2'
......
...@@ -27,10 +27,6 @@ GEM ...@@ -27,10 +27,6 @@ GEM
erubis (~> 2.7.0) erubis (~> 2.7.0)
rack (~> 1.5.2) rack (~> 1.5.2)
rack-test (~> 0.6.2) rack-test (~> 0.6.2)
actionpack-action_caching (1.1.0)
actionpack (>= 4.0.0, < 5.0)
actionpack-page_caching (1.0.2)
actionpack (>= 4.0.0, < 5)
activemodel (4.0.3) activemodel (4.0.3)
activesupport (= 4.0.3) activesupport (= 4.0.3)
builder (~> 3.1.0) builder (~> 3.1.0)
...@@ -289,8 +285,6 @@ GEM ...@@ -289,8 +285,6 @@ GEM
method_source (0.8.2) method_source (0.8.2)
mime-types (1.25.1) mime-types (1.25.1)
minitest (4.7.5) minitest (4.7.5)
modernizr (2.6.2)
sprockets (~> 2.0)
multi_json (1.8.4) multi_json (1.8.4)
multi_xml (0.5.5) multi_xml (0.5.5)
multipart-post (1.2.0) multipart-post (1.2.0)
...@@ -567,8 +561,6 @@ PLATFORMS ...@@ -567,8 +561,6 @@ PLATFORMS
DEPENDENCIES DEPENDENCIES
ace-rails-ap ace-rails-ap
actionpack-action_caching
actionpack-page_caching
acts-as-taggable-on acts-as-taggable-on
annotate (~> 2.6.0.beta2) annotate (~> 2.6.0.beta2)
asciidoctor asciidoctor
...@@ -622,7 +614,6 @@ DEPENDENCIES ...@@ -622,7 +614,6 @@ DEPENDENCIES
launchy launchy
letter_opener letter_opener
minitest (~> 4.7.0) minitest (~> 4.7.0)
modernizr (= 2.6.2)
mysql2 mysql2
nprogress-rails nprogress-rails
omniauth (~> 1.1.3) omniauth (~> 1.1.3)
...@@ -653,7 +644,7 @@ DEPENDENCIES ...@@ -653,7 +644,7 @@ DEPENDENCIES
select2-rails select2-rails
settingslogic settingslogic
shoulda-matchers (~> 2.1.0) shoulda-matchers (~> 2.1.0)
sidekiq sidekiq (= 2.17.0)
simplecov simplecov
sinatra sinatra
six six
......
...@@ -18,7 +18,6 @@ ...@@ -18,7 +18,6 @@
#= require turbolinks #= require turbolinks
#= require jquery.turbolinks #= require jquery.turbolinks
#= require bootstrap #= require bootstrap
#= require modernizr
#= require select2 #= require select2
#= require raphael #= require raphael
#= require g.raphael-min #= require g.raphael-min
......
class CommitFile class CommitFile
constructor: (file) -> constructor: (file) ->
if $('.image', file).length if $('.image', file).length
new ImageFile(file) new ImageFile(file)
this.CommitFile = CommitFile @CommitFile = CommitFile
\ No newline at end of file
...@@ -125,4 +125,4 @@ class ImageFile ...@@ -125,4 +125,4 @@ class ImageFile
img.on 'load', => img.on 'load', =>
callback.call(this, domImg.naturalWidth, domImg.naturalHeight) callback.call(this, domImg.naturalWidth, domImg.naturalHeight)
this.ImageFile = ImageFile @ImageFile = ImageFile
\ No newline at end of file
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
* This is a manifest file that'll automatically include all the stylesheets available in this directory * This is a manifest file that'll automatically include all the stylesheets available in this directory
* and any sub-directories. You're free to add application-wide styles to this file and they'll appear at * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
* the top of the compiled file, but it's generally better to create a new file per style scope. * the top of the compiled file, but it's generally better to create a new file per style scope.
*= require jquery.ui.gitlab *= require jquery.ui.datepicker
*= require jquery.atwho *= require jquery.atwho
*= require select2 *= require select2
*= require highlightjs.min *= require highlightjs.min
...@@ -43,6 +43,7 @@ ...@@ -43,6 +43,7 @@
@import "generic/forms.scss"; @import "generic/forms.scss";
@import "generic/selects.scss"; @import "generic/selects.scss";
@import "generic/highlight.scss"; @import "generic/highlight.scss";
@import "generic/jquery.scss";
/** /**
* Page specific styles (issues, projects etc): * Page specific styles (issues, projects etc):
......
.ui-widget {
font-family: $regular_font;
font-size: $font-size-base;
&.ui-datepicker-inline {
border: 1px solid #DDD;
padding: 10px;
width: 270px;
.ui-datepicker-header {
background: #EEE;
border-color: #DDD;
}
.ui-datepicker-calendar td a {
padding: 5px;
text-align: center;
}
}
}
/* Interaction Cues
----------------------------------*/
.ui-state-disabled { cursor: default !important; }
/* Icons
----------------------------------*/
/* states and images */
.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; }
/* Misc visuals
----------------------------------*/
/* Overlays */
.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
/*
* jQuery UI CSS Framework 1.8.7
*
* Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Theming/API
*
* To view and modify this theme, visit http://jqueryui.com/themeroller/?ctl=themeroller
*/
/* Component containers
----------------------------------*/
.ui-widget { font-family: Arial,sans-serif; font-size: 1.1em; }
.ui-widget .ui-widget { font-size: 1em; }
.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Arial,sans-serif; font-size: 1em; }
.ui-widget-content { border: 1px solid #CCC; background: #ffffff; color: #4F4F4F; }
.ui-widget-content a { color: #4F4F4F; }
.ui-widget-header { border: 1px solid #B6B6B6; color: #4F4F4F; font-weight: bold; }
.ui-widget-header {
background: #ededed url(bg_fallback.png) 0 0 repeat-x; /* Old browsers */
background: -moz-linear-gradient(top, #ededed 0%, #c4c4c4 100%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ededed), color-stop(100%,#c4c4c4)); /* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, #ededed 0%,#c4c4c4 100%); /* Chrome10+,Safari5.1+ */
background: -o-linear-gradient(top, #ededed 0%,#c4c4c4 100%); /* Opera11.10+ */
background: -ms-linear-gradient(top, #ededed 0%,#c4c4c4 100%); /* IE10+ */
background: linear-gradient(top, #ededed 0%,#c4c4c4 100%); /* W3C */
}
.ui-widget-header a { color: #4F4F4F; }
/* Interaction states
----------------------------------*/
.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #B6B6B6; font-weight: normal; color: #4F4F4F; }
.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default {
background: #ededed url(bg_fallback.png) 0 0 repeat-x; /* Old browsers */
background: -moz-linear-gradient(top, #ededed 0%, #c4c4c4 100%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ededed), color-stop(100%,#c4c4c4)); /* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, #ededed 0%,#c4c4c4 100%); /* Chrome10+,Safari5.1+ */
background: -o-linear-gradient(top, #ededed 0%,#c4c4c4 100%); /* Opera11.10+ */
background: -ms-linear-gradient(top, #ededed 0%,#c4c4c4 100%); /* IE10+ */
background: linear-gradient(top, #ededed 0%,#c4c4c4 100%); /* W3C */
-webkit-box-shadow: 0 1px 0 rgba(255,255,255,0.6) inset;
-moz-box-shadow: 0 1px 0 rgba(255,255,255,0.6) inset;
box-shadow: 0 1px 0 rgba(255,255,255,0.6) inset;
}
.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #4F4F4F; text-decoration: none; }
.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #9D9D9D; font-weight: normal; color: #313131; }
.ui-state-hover a, .ui-state-hover a:hover { color: #313131; text-decoration: none; }
.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active {
outline: none;
color: #1c4257; border: 1px solid #7096ab;
background: #ededed url(bg_fallback.png) 0 -50px repeat-x; /* Old browsers */
background: -moz-linear-gradient(top, #b9e0f5 0%, #92bdd6 100%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#b9e0f5), color-stop(100%,#92bdd6)); /* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, #b9e0f5 0%,#92bdd6 100%); /* Chrome10+,Safari5.1+ */
background: -o-linear-gradient(top, #b9e0f5 0%,#92bdd6 100%); /* Opera11.10+ */
background: -ms-linear-gradient(top, #b9e0f5 0%,#92bdd6 100%); /* IE10+ */
background: linear-gradient(top, #b9e0f5 0%,#92bdd6 100%); /* W3C */
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
}
.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #313131; text-decoration: none; }
.ui-widget :active { outline: none; }
/* Interaction Cues
----------------------------------*/
.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight { border: 1px solid #d2dbf4; background: #f4f8fd; color: #0d2054; -moz-border-radius: 0 !important; -webkit-border-radius: 0 !important; border-radius: 0 !important; }
.ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #363636; }
.ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error { border: 1px solid #e2d0d0; background: #fcf0f0; color: #280b0b; -moz-border-radius: 0 !important; -webkit-border-radius: 0 !important; border-radius: 0 !important; }
.ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #cd0a0a; }
.ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #cd0a0a; }
.ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; }
.ui-priority-secondary, .ui-widget-content .ui-priority-secondary, .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; }
.ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; }
/* Icons
----------------------------------*/
/* states and images */
.ui-icon { width: 16px; height: 16px; background-image: url(ui-icons_222222_256x240.png); }
.ui-widget-content .ui-icon {background-image: url(ui-icons_222222_256x240.png); }
.ui-widget-header .ui-icon {background-image: url(ui-icons_222222_256x240.png); }
.ui-state-default .ui-icon { background-image: url(ui-icons_454545_256x240.png); }
.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(ui-icons_454545_256x240.png); }
.ui-state-active .ui-icon {background-image: url(ui-icons_454545_256x240.png); }
.ui-state-highlight .ui-icon {background-image: url(ui-icons_454545_256x240.png); }
.ui-state-error .ui-icon, .ui-state-error-text .ui-icon { background: url(icon_sprite.png) -16px 0 no-repeat !important; }
.ui-state-highlight .ui-icon, .ui-state-error .ui-icon { margin-top: -1px; }
/* Misc visuals
----------------------------------*/
/* Overlays */
.ui-widget-overlay { background: #262b33; opacity: .70;filter:Alpha(Opacity=70); }
.ui-widget-shadow { margin: -8px 0 0 -8px; padding: 8px; background: #000000; opacity: .30;filter:Alpha(Opacity=30); -moz-border-radius: 8px; -webkit-border-radius: 8px; border-radius: 8px; }
/*
* jQuery UI Selectable 1.8.7
*
* Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Selectable#theming
*/
.ui-selectable-helper { position: absolute; z-index: 100; border:1px dotted black; }
/*
* jQuery UI Autocomplete 1.8.7
*
* Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Autocomplete#theming
*/
.ui-autocomplete {
position: absolute; cursor: default; z-index: 3;
-moz-border-radius: 0;
-webkit-border-radius: 0;
border-radius: 0;
-moz-box-shadow: 0 1px 5px rgba(0,0,0,0.3);
-webkit-box-shadow: 0 1px 5px rgba(0,0,0,0.3);
box-shadow: 0 1px 5px rgba(0,0,0,0.3);
}
/* workarounds */
* html .ui-autocomplete { width:1px; } /* without this, the menu expands to 100% in IE6 */
/*
* jQuery UI Menu 1.8.7
*
* Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Menu#theming
*/
.ui-menu {
list-style:none;
padding: 1px;
margin: 0;
display:block;
float: left;
}
.ui-menu .ui-menu {
margin-top: -3px;
}
.ui-menu .ui-menu-item {
margin:0;
padding: 0;
zoom: 1;
float: left;
clear: left;
width: 100%;
}
.ui-menu .ui-menu-item a {
text-decoration:none;
display:block;
padding:.2em .4em;
line-height:1.5;
zoom:1;
color: #666;
font-size: 13px;
}
.ui-menu .ui-menu-item a.ui-state-hover,
.ui-menu .ui-menu-item a.ui-state-active {
font-weight: normal;
margin: -1px;
background: #D9EDF7;
color: #3A89A3;
text-shadow: 0px 1px 1px #fff;
border: none;
border: 1px solid #ADE;
cursor: pointer;
font-weight: bold;
}
/*
* jQuery UI Datepicker 1.8.7
*
* Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Datepicker#theming
*/
.ui-datepicker {
width: 17em;
padding: 0;
display: none;
border-color: #DDDDDD;
border: none;
box-shadow: none;
}
.ui-datepicker .ui-datepicker-header {
position:relative;
padding:.35em 0;
border: none;
border-bottom: 1px solid #B6B6B6;
-moz-border-radius: 0;
-webkit-border-radius: 0;
border-radius: 0;
margin-bottom: 10px;
border: 1px solid #bbb;
-webkit-box-shadow: 0 0 0 3px #F1F1F1;
-moz-box-shadow: 0 0 0 3px #f1f1f1;
-ms-box-shadow: 0 0 0 3px #f1f1f1;
-o-box-shadow: 0 0 0 3px #f1f1f1;
box-shadow: 0 0 0 3px #F1F1F1;
}
.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 6px; width: 1.8em; height: 1.8em; }
.ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { border: 1px none; }
.ui-datepicker .ui-datepicker-prev { left:2px; }
.ui-datepicker .ui-datepicker-next { right:2px; }
.ui-datepicker .ui-datepicker-prev span { background-position: 0px -32px !important; }
.ui-datepicker .ui-datepicker-next span { background-position: -16px -32px !important; }
.ui-datepicker .ui-datepicker-prev-hover span { background-position: 0px -48px !important; }
.ui-datepicker .ui-datepicker-next-hover span { background-position: -16px -48px !important; }
.ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px; background: url(icon_sprite.png) no-repeat; }
.ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; font-size: 12px; text-shadow: 0 1px 0 rgba(255,255,255,0.6); }
.ui-datepicker .ui-datepicker-title select { font-size:1em; margin:1px 0; }
.ui-datepicker select.ui-datepicker-month-year {width: 100%;}
.ui-datepicker select.ui-datepicker-month,
.ui-datepicker select.ui-datepicker-year { width: 49%;}
.ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; }
.ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0; }
.ui-datepicker td { border: 0; padding: 1px; line-height: 24px; background-color: #FFF!important; }
.ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; }
.ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; }
.ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; }
.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; }
.ui-datepicker table .ui-state-highlight { border-color: #ADE; }
.ui-datepicker-calendar .ui-state-default { background: transparent; border-color: #FFF; }
.ui-datepicker-calendar .ui-state-active { background: #D9EDF7; border-color: #ADE; color: #3A89A3; font-weight: bold; text-shadow: 0 1px 1px #fff; }
/** Typo **/ /** Typo **/
$monospace_font: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono', 'lucida console', monospace; $monospace_font: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono', 'lucida console', monospace;
$regular_font: "Helvetica Neue", Helvetica, Arial, sans-serif;
...@@ -59,9 +59,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -59,9 +59,7 @@ class Projects::IssuesController < Projects::ApplicationController
end end
def create def create
@issue = @project.issues.new(params[:issue]) @issue = Issues::CreateService.new(project, current_user, params[:issue]).execute
@issue.author = current_user
@issue.save
respond_to do |format| respond_to do |format|
format.html do format.html do
...@@ -76,8 +74,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -76,8 +74,7 @@ class Projects::IssuesController < Projects::ApplicationController
end end
def update def update
@issue.update_attributes(params[:issue]) @issue = Issues::UpdateService.new(project, current_user, params[:issue]).execute(issue)
@issue.reset_events_cache
respond_to do |format| respond_to do |format|
format.js format.js
......
...@@ -76,10 +76,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -76,10 +76,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end end
def create def create
@merge_request = MergeRequest.new(params[:merge_request])
@merge_request.author = current_user
@target_branches ||= [] @target_branches ||= []
if @merge_request.save @merge_request = MergeRequests::CreateService.new(project, current_user, params[:merge_request]).execute
if @merge_request.valid?
redirect_to [@merge_request.target_project, @merge_request], notice: 'Merge request was successfully created.' redirect_to [@merge_request.target_project, @merge_request], notice: 'Merge request was successfully created.'
else else
@source_project = @merge_request.source_project @source_project = @merge_request.source_project
...@@ -89,29 +89,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -89,29 +89,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end end
def update def update
# If we close MergeRequest we want to ignore validation @merge_request = MergeRequests::UpdateService.new(project, current_user, params[:merge_request]).execute(@merge_request)
# so we can close broken one (Ex. fork project removed)
if params[:merge_request] == {"state_event"=>"close"}
@merge_request.allow_broken = true
if @merge_request.close
opts = { notice: 'Merge request was successfully closed.' }
else
opts = { alert: 'Failed to close merge request.' }
end
redirect_to [@merge_request.target_project, @merge_request], opts
return
end
# We dont allow change of source/target projects
# after merge request was created
params[:merge_request].delete(:source_project_id)
params[:merge_request].delete(:target_project_id)
if @merge_request.update_attributes(params[:merge_request])
@merge_request.reset_events_cache
if @merge_request.valid?
respond_to do |format| respond_to do |format|
format.js format.js
format.html do format.html do
......
...@@ -20,7 +20,7 @@ module MergeRequestsHelper ...@@ -20,7 +20,7 @@ module MergeRequestsHelper
target_project_id: target_project.id, target_project_id: target_project.id,
source_branch: event.branch_name, source_branch: event.branch_name,
target_branch: target_project.repository.root_ref, target_branch: target_project.repository.root_ref,
title: event.branch_name.humanize title: event.branch_name.titleize.humanize
} }
end end
......
...@@ -13,14 +13,15 @@ class Email < ActiveRecord::Base ...@@ -13,14 +13,15 @@ class Email < ActiveRecord::Base
# Relations # Relations
# #
belongs_to :user belongs_to :user
# #
# Validations # Validations
# #
validates :user_id, presence: true validates :user_id, presence: true
validates :email, presence: true, email: { strict_mode: true }, uniqueness: true validates :email, presence: true, email: { strict_mode: true }, uniqueness: true
validate :unique_email, if: ->(email) { email.email_changed? } validate :unique_email, if: ->(email) { email.email_changed? }
after_create :notify
before_validation :cleanup_email before_validation :cleanup_email
def cleanup_email def cleanup_email
...@@ -30,4 +31,8 @@ class Email < ActiveRecord::Base ...@@ -30,4 +31,8 @@ class Email < ActiveRecord::Base
def unique_email def unique_email
self.errors.add(:email, 'has already been taken') if User.exists?(email: self.email) self.errors.add(:email, 'has already been taken') if User.exists?(email: self.email)
end end
end
\ No newline at end of file def notify
NotificationService.new.new_email(self)
end
end
...@@ -29,6 +29,10 @@ class Key < ActiveRecord::Base ...@@ -29,6 +29,10 @@ class Key < ActiveRecord::Base
delegate :name, :email, to: :user, prefix: true delegate :name, :email, to: :user, prefix: true
after_create :add_to_shell
after_create :notify_user
after_destroy :remove_from_shell
def strip_white_space def strip_white_space
self.key = key.strip unless key.blank? self.key = key.strip unless key.blank?
end end
...@@ -42,6 +46,26 @@ class Key < ActiveRecord::Base ...@@ -42,6 +46,26 @@ class Key < ActiveRecord::Base
"key-#{id}" "key-#{id}"
end end
def add_to_shell
GitlabShellWorker.perform_async(
:add_key,
shell_id,
key
)
end
def notify_user
NotificationService.new.new_key(self)
end
def remove_from_shell
GitlabShellWorker.perform_async(
:remove_key,
shell_id,
key,
)
end
private private
def generate_fingerpint def generate_fingerpint
......
...@@ -97,6 +97,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -97,6 +97,7 @@ class MergeRequest < ActiveRecord::Base
validates :target_project, presence: true validates :target_project, presence: true
validates :target_branch, presence: true validates :target_branch, presence: true
validate :validate_branches validate :validate_branches
validate :validate_fork
scope :of_group, ->(group) { where("source_project_id in (:group_project_ids) OR target_project_id in (:group_project_ids)", group_project_ids: group.project_ids) } scope :of_group, ->(group) { where("source_project_id in (:group_project_ids) OR target_project_id in (:group_project_ids)", group_project_ids: group.project_ids) }
scope :of_user_team, ->(team) { where("(source_project_id in (:team_project_ids) OR target_project_id in (:team_project_ids) AND assignee_id in (:team_member_ids))", team_project_ids: team.project_ids, team_member_ids: team.member_ids) } scope :of_user_team, ->(team) { where("(source_project_id in (:team_project_ids) OR target_project_id in (:team_project_ids) AND assignee_id in (:team_member_ids))", team_project_ids: team.project_ids, team_member_ids: team.member_ids) }
...@@ -125,6 +126,22 @@ class MergeRequest < ActiveRecord::Base ...@@ -125,6 +126,22 @@ class MergeRequest < ActiveRecord::Base
end end
end end
def validate_fork
return true unless target_project && source_project
if target_project == source_project
true
else
# If source and target projects are different
# we should check if source project is actually a fork of target project
if source_project.forked_from?(target_project)
true
else
errors.add :base, "Source project is not a fork of target project"
end
end
end
def update_merge_request_diff def update_merge_request_diff
if source_branch_changed? || target_branch_changed? if source_branch_changed? || target_branch_changed?
reload_code reload_code
......
...@@ -552,4 +552,8 @@ class Project < ActiveRecord::Base ...@@ -552,4 +552,8 @@ class Project < ActiveRecord::Base
gitlab_shell.update_repository_head(self.path_with_namespace, branch) gitlab_shell.update_repository_head(self.path_with_namespace, branch)
reload_default_branch reload_default_branch
end end
def forked_from?(project)
forked? && project == forked_from_project
end
end end
class EmailObserver < BaseObserver
def after_create(email)
notification.new_email(email)
end
end
class IssueObserver < BaseObserver
def after_create(issue)
notification.new_issue(issue, current_user)
event_service.open_issue(issue, current_user)
issue.create_cross_references!(issue.project, current_user)
execute_hooks(issue)
end
def after_close(issue, transition)
notification.close_issue(issue, current_user)
event_service.close_issue(issue, current_user)
create_note(issue)
execute_hooks(issue)
end
def after_reopen(issue, transition)
event_service.reopen_issue(issue, current_user)
create_note(issue)
execute_hooks(issue)
end
def after_update(issue)
if issue.is_being_reassigned?
notification.reassigned_issue(issue, current_user)
create_assignee_note(issue)
end
issue.notice_added_references(issue.project, current_user)
execute_hooks(issue)
end
protected
# Create issue note with service comment like 'Status changed to closed'
def create_note(issue)
Note.create_status_change_note(issue, issue.project, current_user, issue.state, current_commit)
end
def create_assignee_note(issue)
Note.create_assignee_change_note(issue, issue.project, current_user, issue.assignee)
end
def execute_hooks(issue)
issue.project.execute_hooks(issue.to_hook_data, :issue_hooks)
end
end
class KeyObserver < BaseObserver
def after_create(key)
GitlabShellWorker.perform_async(
:add_key,
key.shell_id,
key.key
)
notification.new_key(key)
end
def after_destroy(key)
GitlabShellWorker.perform_async(
:remove_key,
key.shell_id,
key.key,
)
end
end
class MergeRequestObserver < BaseObserver
def after_create(merge_request)
event_service.open_mr(merge_request, current_user)
notification.new_merge_request(merge_request, current_user)
merge_request.create_cross_references!(merge_request.project, current_user)
execute_hooks(merge_request)
end
def after_close(merge_request, transition)
event_service.close_mr(merge_request, current_user)
notification.close_mr(merge_request, current_user)
create_note(merge_request)
execute_hooks(merge_request)
end
def after_reopen(merge_request, transition)
event_service.reopen_mr(merge_request, current_user)
create_note(merge_request)
execute_hooks(merge_request)
merge_request.reload_code
merge_request.mark_as_unchecked
end
def after_update(merge_request)
notification.reassigned_merge_request(merge_request, current_user) if merge_request.is_being_reassigned?
merge_request.notice_added_references(merge_request.project, current_user)
execute_hooks(merge_request)
end
private
# Create merge request note with service comment like 'Status changed to closed'
def create_note(merge_request)
Note.create_status_change_note(merge_request, merge_request.target_project, current_user, merge_request.state, nil)
end
def execute_hooks(merge_request)
if merge_request.project
merge_request.project.execute_hooks(merge_request.to_hook_data, :merge_request_hooks)
end
end
end
...@@ -10,7 +10,7 @@ class UsersProjectObserver < BaseObserver ...@@ -10,7 +10,7 @@ class UsersProjectObserver < BaseObserver
end end
def after_update(users_project) def after_update(users_project)
notification.update_team_member(users_project) notification.update_team_member(users_project) if users_project.project_access_changed?
end end
def after_destroy(users_project) def after_destroy(users_project)
......
...@@ -16,4 +16,16 @@ class BaseService ...@@ -16,4 +16,16 @@ class BaseService
def can?(object, action, subject) def can?(object, action, subject)
abilities.allowed?(object, action, subject) abilities.allowed?(object, action, subject)
end end
def notification_service
NotificationService.new
end
def event_service
EventCreateService.new
end
def log_info message
Gitlab::AppLogger.info message
end
end end
...@@ -86,10 +86,9 @@ class GitPushService ...@@ -86,10 +86,9 @@ class GitPushService
author = commit_user(commit) author = commit_user(commit)
if !issues_to_close.empty? && is_default_branch if !issues_to_close.empty? && is_default_branch
Thread.current[:current_user] = author issues_to_close.each do |issue|
Thread.current[:current_commit] = commit Issues::CloseService.new(project, author, {}).execute(issue, commit)
end
issues_to_close.each { |i| i.close && i.save }
end end
# Create cross-reference notes for any other references. Omit any issues that were referenced in an # Create cross-reference notes for any other references. Omit any issues that were referenced in an
......
module Issues
class BaseService < ::BaseService
private
def create_assignee_note(issue)
Note.create_assignee_change_note(issue, issue.project, current_user, issue.assignee)
end
def execute_hooks(issue)
issue.project.execute_hooks(issue.to_hook_data, :issue_hooks)
end
end
end
module Issues
class CloseService < Issues::BaseService
def execute(issue, commit = nil)
if issue.close
notification_service.close_issue(issue, current_user)
event_service.close_issue(issue, current_user)
create_note(issue, commit)
execute_hooks(issue)
end
issue
end
private
def create_note(issue, current_commit)
Note.create_status_change_note(issue, issue.project, current_user, issue.state, current_commit)
end
end
end
module Issues
class CreateService < Issues::BaseService
def execute
issue = project.issues.new(params)
issue.author = current_user
if issue.save
notification_service.new_issue(issue, current_user)
event_service.open_issue(issue, current_user)
issue.create_cross_references!(issue.project, current_user)
execute_hooks(issue)
end
issue
end
end
end
module Issues
class ReopenService < Issues::BaseService
def execute(issue)
if issue.reopen
event_service.reopen_issue(issue, current_user)
create_note(issue)
execute_hooks(issue)
end
issue
end
private
def create_note(issue)
Note.create_status_change_note(issue, issue.project, current_user, issue.state, nil)
end
end
end
module Issues
class UpdateService < Issues::BaseService
def execute(issue)
state = params.delete('state_event')
case state
when 'reopen'
Issues::ReopenService.new(project, current_user, {}).execute(issue)
when 'close'
Issues::CloseService.new(project, current_user, {}).execute(issue)
end
if params.present? && issue.update_attributes(params)
issue.reset_events_cache
if issue.previous_changes.include?('assignee_id')
notification_service.reassigned_issue(issue, current_user)
create_assignee_note(issue)
end
issue.notice_added_references(issue.project, current_user)
execute_hooks(issue)
end
issue
end
end
end
module MergeRequests
class BaseService < ::BaseService
private
def create_assignee_note(merge_request)
Note.create_assignee_change_note(merge_request, merge_request.project, current_user, merge_request.assignee)
end
def create_note(merge_request)
Note.create_status_change_note(merge_request, merge_request.target_project, current_user, merge_request.state, nil)
end
def execute_hooks(merge_request)
if merge_request.project
merge_request.project.execute_hooks(merge_request.to_hook_data, :merge_request_hooks)
end
end
end
end
module MergeRequests
class CloseService < MergeRequests::BaseService
def execute(merge_request, commit = nil)
# If we close MergeRequest we want to ignore validation
# so we can close broken one (Ex. fork project removed)
merge_request.allow_broken = true
if merge_request.close
event_service.close_mr(merge_request, current_user)
notification_service.close_mr(merge_request, current_user)
create_note(merge_request)
execute_hooks(merge_request)
end
merge_request
end
end
end
module MergeRequests
class CreateService < MergeRequests::BaseService
def execute
merge_request = MergeRequest.new(params)
merge_request.source_project = project
merge_request.target_project ||= project
merge_request.author = current_user
if merge_request.save
event_service.open_mr(merge_request, current_user)
notification_service.new_merge_request(merge_request, current_user)
merge_request.create_cross_references!(merge_request.project, current_user)
execute_hooks(merge_request)
end
merge_request
end
end
end
module MergeRequests
class ReopenService < MergeRequests::BaseService
def execute(merge_request)
if merge_request.reopen
event_service.reopen_mr(merge_request, current_user)
create_note(merge_request)
execute_hooks(merge_request)
merge_request.reload_code
merge_request.mark_as_unchecked
end
merge_request
end
end
end
require_relative 'base_service'
require_relative 'reopen_service'
require_relative 'close_service'
module MergeRequests
class UpdateService < MergeRequests::BaseService
def execute(merge_request)
# We dont allow change of source/target projects
# after merge request was created
params.delete(:source_project_id)
params.delete(:target_project_id)
state = params.delete('state_event')
case state
when 'reopen'
MergeRequests::ReopenService.new(project, current_user, {}).execute(merge_request)
when 'close'
MergeRequests::CloseService.new(project, current_user, {}).execute(merge_request)
end
if params.present? && merge_request.update_attributes(params)
merge_request.reset_events_cache
if merge_request.previous_changes.include?('assignee_id')
notification_service.reassigned_merge_request(merge_request, current_user)
create_assignee_note(merge_request)
end
merge_request.notice_added_references(merge_request.project, current_user)
execute_hooks(merge_request)
end
merge_request
end
end
end
...@@ -178,29 +178,29 @@ class NotificationService ...@@ -178,29 +178,29 @@ class NotificationService
# Get project users with WATCH notification level # Get project users with WATCH notification level
def project_watchers(project) def project_watchers(project)
# Gather all user ids that have WATCH notification setting for project project_members = users_project_notification(project)
project_notification_uids = project_notification_list(project, Notification::N_WATCH)
# Gather all user ids that have WATCH notification setting for group users_with_project_level_global = users_project_notification(project, Notification::N_GLOBAL)
group_notification_uids = group_notification_list(project, Notification::N_WATCH) users_with_group_level_global = users_group_notification(project, Notification::N_GLOBAL)
users = users_with_global_level_watch([users_with_project_level_global, users_with_group_level_global].flatten.uniq)
# Gather all user ids that have GLOBAL setting users_with_project_setting = select_users_project_setting(project, users_with_project_level_global, users)
global_notification_uids = global_notification_list(project) users_with_group_setting = select_users_group_setting(project, project_members, users_with_group_level_global, users)
project_and_group_uids = [project_notification_uids, group_notification_uids].flatten.uniq User.where(id: users_with_project_setting.concat(users_with_group_setting).uniq).to_a
group_and_project_watchers = User.where(id: project_and_group_uids)
# Find all users that have WATCH as their GLOBAL setting
global_watchers = User.where(id: global_notification_uids, notification_level: Notification::N_WATCH)
[group_and_project_watchers, global_watchers].flatten.uniq
end end
def project_notification_list(project, notification_level) def users_project_notification(project, notification_level=nil)
project.users_projects.where(notification_level: notification_level).pluck(:user_id) project_members = project.users_projects
if notification_level
project_members.where(notification_level: notification_level).pluck(:user_id)
else
project_members.pluck(:user_id)
end
end end
def group_notification_list(project, notification_level) def users_group_notification(project, notification_level)
if project.group if project.group
project.group.users_groups.where(notification_level: notification_level).pluck(:user_id) project.group.users_groups.where(notification_level: notification_level).pluck(:user_id)
else else
...@@ -208,11 +208,47 @@ class NotificationService ...@@ -208,11 +208,47 @@ class NotificationService
end end
end end
def global_notification_list(project) def users_with_global_level_watch(ids)
[ User.where(
project_notification_list(project, Notification::N_GLOBAL), id: ids,
group_notification_list(project, Notification::N_GLOBAL) notification_level: Notification::N_WATCH
].flatten ).pluck(:id)
end
# Build a list of users based on project notifcation settings
def select_users_project_setting(project, global_setting, users_global_level_watch)
users = users_project_notification(project, Notification::N_WATCH)
# If project setting is global, add to watch list if global setting is watch
global_setting.each do |user_id|
if users_global_level_watch.include?(user_id)
users << user_id
end
end
users
end
# Build a list of users based on group notifcation settings
def select_users_group_setting(project, project_members, global_setting, users_global_level_watch)
uids = users_group_notification(project, Notification::N_WATCH)
# Group setting is watch, add to users list if user is not project member
users = []
uids.each do |user_id|
if project_members.exclude?(user_id)
users << user_id
end
end
# Group setting is global, add to users list if global setting is watch
global_setting.each do |user_id|
if project_members.exclude?(user_id) && users_global_level_watch.include?(user_id)
users << user_id
end
end
users
end end
# Remove users with disabled notifications from array # Remove users with disabled notifications from array
......
...@@ -20,6 +20,11 @@ ...@@ -20,6 +20,11 @@
= form_tag project_issues_path(@project), method: :get, id: "issue_search_form", class: 'pull-left issue-search-form' do = form_tag project_issues_path(@project), method: :get, id: "issue_search_form", class: 'pull-left issue-search-form' do
.append-right-10.hidden-xs.hidden-sm .append-right-10.hidden-xs.hidden-sm
= search_field_tag :issue_search, nil, { placeholder: 'Filter by title or description', class: 'form-control issue_search search-text-input input-mn-300' } = search_field_tag :issue_search, nil, { placeholder: 'Filter by title or description', class: 'form-control issue_search search-text-input input-mn-300' }
= hidden_field_tag :state, params['state']
= hidden_field_tag :scope, params['scope']
= hidden_field_tag :assignee_id, params['assignee_id']
= hidden_field_tag :milestone_id, params['milestone_id']
= hidden_field_tag :label_id, params['label_id']
- if can? current_user, :write_issue, @project - if can? current_user, :write_issue, @project
= link_to new_project_issue_path(@project, issue: { assignee_id: params[:assignee_id], milestone_id: params[:milestone_id]}), class: "btn btn-new pull-left", title: "New Issue", id: "new_issue_link" do = link_to new_project_issue_path(@project, issue: { assignee_id: params[:assignee_id], milestone_id: params[:milestone_id]}), class: "btn btn-new pull-left", title: "New Issue", id: "new_issue_link" do
%i.icon-plus %i.icon-plus
......
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
.col-sm-10 .col-sm-10
.clearfix .clearfix
.pull-left .pull-left
- projects = @project.forked_from_project.nil? ? [@project] : [ @project,@project.forked_from_project] - projects = @project.forked_from_project.nil? ? [@project] : [@project, @project.forked_from_project]
= f.select(:target_project_id, options_from_collection_for_select(projects, 'id', 'path_with_namespace', f.object.target_project_id), {}, { class: 'target_project select2 span3', disabled: @merge_request.persisted? }) = f.select(:target_project_id, options_from_collection_for_select(projects, 'id', 'path_with_namespace', f.object.target_project_id), {}, { class: 'target_project select2 span3', disabled: @merge_request.persisted? })
.pull-left .pull-left
&nbsp; &nbsp;
......
...@@ -3,5 +3,5 @@ ...@@ -3,5 +3,5 @@
var mrTitle = $('#merge_request_title'); var mrTitle = $('#merge_request_title');
if(mrTitle.val().length == 0) { if(mrTitle.val().length == 0) {
mrTitle.val("#{params[:ref].humanize}"); mrTitle.val("#{params[:ref].titleize.humanize}");
} }
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
%ul %ul
%li keep stable branches secured %li keep stable branches secured
%li forced code review before merge to protected branches %li forced code review before merge to protected branches
%li prevents branch from force push
%p Read more about project permissions #{link_to "here", help_permissions_path, class: "underlined-link"} %p Read more about project permissions #{link_to "here", help_permissions_path, class: "underlined-link"}
- if can? current_user, :admin_project, @project - if can? current_user, :admin_project, @project
......
...@@ -21,9 +21,6 @@ module Gitlab ...@@ -21,9 +21,6 @@ module Gitlab
# Activate observers that should always be running. # Activate observers that should always be running.
config.active_record.observers = :milestone_observer, config.active_record.observers = :milestone_observer,
:project_activity_cache_observer, :project_activity_cache_observer,
:issue_observer,
:key_observer,
:merge_request_observer,
:note_observer, :note_observer,
:project_observer, :project_observer,
:system_hook_observer, :system_hook_observer,
...@@ -64,6 +61,7 @@ module Gitlab ...@@ -64,6 +61,7 @@ module Gitlab
config.assets.enabled = true config.assets.enabled = true
config.assets.paths << Emoji.images_path config.assets.paths << Emoji.images_path
config.assets.precompile << "emoji/*.png" config.assets.precompile << "emoji/*.png"
config.assets.precompile << "print.css"
# Version of your assets, change this if you want to expire all your assets # Version of your assets, change this if you want to expire all your assets
config.assets.version = '1.0' config.assets.version = '1.0'
......
## The GitLab Documentation covers the following subjects **User documentation**
+ [API](api/README.md) + [API](api/README.md) Explore how you can access GitLab via a simple and powerful API.
+ [Development](development/README.md) + [Markdown](markdown/markdown.md) Learn what you can do with GitLab's advanced formatting system.
+ [Install](install/README.md) + [Permissions](permissions/permissions.md) Learn what each role in a project (guest/reporter/developer/master/owner) can do.
+ [Integration](integration/external-issue-tracker.md) + [Public access](public_access/public_access.md) Learn how you can allow public and internal access to a project.
+ [Legal](legal/README.md) + [SSH](ssh/README.md) Setup your ssh keys and deploy keys for secure access to your projects.
+ [Markdown](markdown/markdown.md) + [Web hooks](web_hooks/web_hooks.md) Let GitLab notify you when new code has been pushed to your project.
+ [Permissions](permissions/permissions.md) + [Workflow](workflow/workflow.md) Learn how to use Git and GitLab together.
+ [Public access](public_access/public_access.md)
+ [Raketasks](raketasks/README.md) **Administrator documentation**
+ [Release](release/README.md)
+ [Security](security/README.md) + [Install](install/README.md) Requirements, directory structures and manual installation.
+ [SSH](ssh/README.md) + [Integration](integration/external-issue-tracker.md) How to integrate JIRA and Redmine.
+ [System hooks](system_hooks/system_hooks.md) + [Raketasks](raketasks/README.md) Explore what GitLab has in store for you to make administration easier.
+ [Update](update/README.md) + [System hooks](system_hooks/system_hooks.md) Let GitLab notify you when certain management tasks need to be carried out.
+ [Web hooks](web_hooks/web_hooks.md) + [Security](security/README.md) Learn what you can do to further secure your GitLab instance.
+ [Workflow](workflow/workflow.md) + [Update](update/README.md) Update guides to upgrade your installation.
**Contributor documentation**
+ [Development](development/README.md) Explains the architecture and the guidelines for shell commands.
+ [Legal](legal/README.md) Contributor license agreements.
+ [Release](release/README.md) How to make the monthly and security releases.
...@@ -93,7 +93,7 @@ Then select 'Internet Site' and press enter to confirm the hostname. ...@@ -93,7 +93,7 @@ Then select 'Internet Site' and press enter to confirm the hostname.
# 2. Ruby # 2. Ruby
The use of ruby version managers such as [RVM](http://rvm.io/), [rbenv](https://github.com/sstephenson/rbenv) or [chruby](https://github.com/postmodern/chruby) with GitLab in production frequently leads to hard to diagnose problems. Version managers are not supported and we stronly advise everyone to follow the instructions below to use a system ruby. The use of ruby version managers such as [RVM](http://rvm.io/), [rbenv](https://github.com/sstephenson/rbenv) or [chruby](https://github.com/postmodern/chruby) with GitLab in production frequently leads to hard to diagnose problems. For example, GitLab Shell is called from OpenSSH and having a version manager can prevent pushing and pulling over SSH. Version managers are not supported and we stronly advise everyone to follow the instructions below to use a system ruby.
Remove the old Ruby 1.8 if present Remove the old Ruby 1.8 if present
...@@ -202,6 +202,7 @@ You can change `6-6-stable` to `master` if you want the *bleeding edge* version, ...@@ -202,6 +202,7 @@ You can change `6-6-stable` to `master` if you want the *bleeding edge* version,
# Create directory for satellites # Create directory for satellites
sudo -u git -H mkdir /home/git/gitlab-satellites sudo -u git -H mkdir /home/git/gitlab-satellites
sudo chmod u+rwx,g+rx,o-rwx /home/git/gitlab-satellites
# Create directories for sockets/pids and make sure GitLab can write to them # Create directories for sockets/pids and make sure GitLab can write to them
sudo -u git -H mkdir tmp/pids/ sudo -u git -H mkdir tmp/pids/
......
...@@ -2,7 +2,7 @@ GitLab has a great issue tracker but you can also use an external issue tracker ...@@ -2,7 +2,7 @@ GitLab has a great issue tracker but you can also use an external issue tracker
- the 'Issues' link on the GitLab project pages takes you to the appropriate JIRA issue index; - the 'Issues' link on the GitLab project pages takes you to the appropriate JIRA issue index;
- clicking 'New issue' on the project dashboard creates a new JIRA issue; - clicking 'New issue' on the project dashboard creates a new JIRA issue;
- textual references to PROJECT-1234 in comments, commit messages get turned into HTML links to the corresponding JIRA issue. - To reference JIRA issue PROJECT-1234 in comments, use syntax #PROJECT-1234. Commit messages get turned into HTML links to the corresponding JIRA issue.
![jira screenshot](jira-intergration-points.png) ![jira screenshot](jira-intergration-points.png)
......
# GitLab LDAP integration
GitLab can be configured to allow your users to sign with their LDAP credentials to integrate with e.g. Active Directory.
The first time a user signs in with LDAP credentials, GitLab will create a new GitLab user associated with the LDAP Distinguished Name (DN) of the LDAP user.
GitLab user attributes such as nickname and email will be copied from the LDAP user entry.
## Enabling LDAP sign-in for existing GitLab users
When a user signs in to GitLab with LDAP for the first time, and their LDAP email address is the primary email address of an existing GitLab user, then the LDAP DN will be associated with the existing user.
If the LDAP email attribute is not found in GitLab's database, a new user is created.
In other words, if an existing GitLab user wants to enable LDAP sign-in for themselves, they should check that their GitLab email address matches their LDAP email address, and then sign into GitLab via their LDAP credentials.
GitLab recognizes the following LDAP attributes as email addresses: `mail`, `email` and `userPrincipalName`.
If multiple LDAP email attributes are present, e.g. `mail: foo@bar.com` and `email: foo@example.com`, then the first attribute found wins -- in this case `foo@bar.com`.
...@@ -61,9 +61,9 @@ After making the release branch new commits are cherry-picked from master. When ...@@ -61,9 +61,9 @@ After making the release branch new commits are cherry-picked from master. When
* 1-7th: official merge window (see contributing guide) * 1-7th: official merge window (see contributing guide)
* 8-14th: work on bugfixes, sponsored features and GitLab EE * 8-14th: work on bugfixes, sponsored features and GitLab EE
* 15th: code freeze (stop merging into master except essential bugfixes) * 15th: code freeze (stop merging into master except essential bugfixes)
* 18th: release candidate 1 (VERSION x.x.0.rc1, tag and tweet about x.x.0.rc1, release on GitLab Cloud) * 18th: release candidate 1 (VERSION x.x.0.rc1, annotated tag and tweet about x.x.0.rc1, release on GitLab Cloud)
* 20st: optional release candidate 2 (x.x.0.rc2, only if rc1 had problems) * 20st: optional release candidate 2 (x.x.0.rc2, only if rc1 had problems)
* 22nd: release (VERSION x.x.0, create x-x-stable branch, tag, blog and tweet) * 22nd: release (VERSION x.x.0, create x-x-stable branch, annotated tag tag, blog and tweet)
* 23nd: optional patch releases (x.x.1, x.x.2, etc., only if there are serious problems) * 23nd: optional patch releases (x.x.1, x.x.2, etc., only if there are serious problems)
* 24-end of month: release GitLab EE and GitLab CI * 24-end of month: release GitLab EE and GitLab CI
......
...@@ -18,7 +18,7 @@ Please report suspected security vulnerabilities in private to support@gitlab.co ...@@ -18,7 +18,7 @@ Please report suspected security vulnerabilities in private to support@gitlab.co
1. Create feature branches for the blog post on GitLab.com and link them from the code branch 1. Create feature branches for the blog post on GitLab.com and link them from the code branch
1. Merge the code feature branch into master 1. Merge the code feature branch into master
1. Cherry-pick the code into the latest stable branch 1. Cherry-pick the code into the latest stable branch
1. Create a git tag vX.X.X for CE and another patch release for EE 1. Create an annotated tag vX.X.X for CE and another patch release for EE
1. Push the code and the tags to all the CE and EE repositories 1. Push the code and the tags to all the CE and EE repositories
1. Apply the patch to GitLab Cloud and the private GitLab development server 1. Apply the patch to GitLab Cloud and the private GitLab development server
1. Merge and publish the blog posts 1. Merge and publish the blog posts
......
...@@ -80,6 +80,9 @@ sudo -u git -H bundle exec rake migrate_iids RAILS_ENV=production ...@@ -80,6 +80,9 @@ sudo -u git -H bundle exec rake migrate_iids RAILS_ENV=production
# Clean up assets and cache # Clean up assets and cache
sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
# Close access to gitlab-satellites for others
sudo chmod u+rwx,g+rx,o-rwx /home/git/gitlab-satellites
``` ```
### 6. Update config files ### 6. Update config files
......
...@@ -63,6 +63,9 @@ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab ...@@ -63,6 +63,9 @@ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
# Update the logrotate configuration (keep logs for 90 days instead of 52 weeks) # Update the logrotate configuration (keep logs for 90 days instead of 52 weeks)
sudo cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab sudo cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab
# Close access to gitlab-satellites for others
sudo chmod u+rwx,g+rx,o-rwx /home/git/gitlab-satellites
``` ```
......
...@@ -40,3 +40,10 @@ To make sure you didn't miss anything run a more thorough check with: ...@@ -40,3 +40,10 @@ To make sure you didn't miss anything run a more thorough check with:
sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
If all items are green, then congratulations upgrade is complete! If all items are green, then congratulations upgrade is complete!
### One line upgrade command
You've read through the entire guide, and probably did all the steps manually. Here is a one liner for convenience, the next time you upgrade:
cd /home/git/gitlab; sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production; sudo service gitlab stop; sudo -u git -H ruby script/upgrade.rb -y; sudo service gitlab start; sudo service nginx restart; sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+ [Workflow](workflow/workflow.md)
+ [Project Features](workflow/project_features.md)
When in a Project -> Settings, you will find Features on the bottom of the page that you can toggle.
Below you will find a more elaborate explanation of each of these.
## Issues
Issues is a really powerful, but lightweight issue tracking system.
You can make tickets, assign them to people, file them under milestones, order them with labels and have discussion in them.
They integrate deeply into GitLab and are easily referenced from anywhere by using # and the issuenumber.
At GitLab.com, we use this for all our project management needs.
## Merge Requests
Using a merge request, you can review and discuss code before it is merged in the branch of your code.
As with issues, it can be assigned; people, issues, etc. can be refereced; milestones attached.
We see it as an integral part of working together on code and couldn't work without it.
## Wiki
This is a separate system for documentation, built right into GitLab.
It is source controlled and is very convenient if you don't want to keep you documentation in your source code, but you do want to keep it in your GitLab project.
## Wall
For simple, project specific conversations, the wall can be used.
It's very lightweight and simple and works well if you're not interested in using issues, but still want to occasionally communicate within a project.
## Snippets
Snippets are little bits of code or text.
This is a nice place to put code or text that is used semi-regularly within the project, but does not belong in source control.
For example, a specific config file that is used by > the team that is only valid for the people that work on the code.
...@@ -53,15 +53,15 @@ class DashboardMergeRequests < Spinach::FeatureSteps ...@@ -53,15 +53,15 @@ class DashboardMergeRequests < Spinach::FeatureSteps
end end
def assigned_merge_request def assigned_merge_request
@assigned_merge_request ||= create :merge_request, assignee: current_user, target_project: project @assigned_merge_request ||= create :merge_request, assignee: current_user, target_project: project, source_project: project
end end
def authored_merge_request def authored_merge_request
@authored_merge_request ||= create :merge_request, author: current_user, target_project: project @authored_merge_request ||= create :merge_request, source_branch: 'simple_merge_request', author: current_user, target_project: project, source_project: project
end end
def other_merge_request def other_merge_request
@other_merge_request ||= create :merge_request, target_project: project @other_merge_request ||= create :merge_request, source_branch: '2_3_notes_fix', target_project: project, source_project: project
end end
def project def project
......
...@@ -52,6 +52,4 @@ Spinach.hooks.before_run do ...@@ -52,6 +52,4 @@ Spinach.hooks.before_run do
RSpec::Mocks::setup self RSpec::Mocks::setup self
include FactoryGirl::Syntax::Methods include FactoryGirl::Syntax::Methods
MergeRequestObserver.any_instance.stub(current_user: create(:user))
end end
...@@ -10,6 +10,7 @@ module API ...@@ -10,6 +10,7 @@ module API
# project - project path with namespace # project - project path with namespace
# action - git action (git-upload-pack or git-receive-pack) # action - git action (git-upload-pack or git-receive-pack)
# ref - branch name # ref - branch name
# forced_push - forced_push
# #
get "/allowed" do get "/allowed" do
# Check for *.wiki repositories. # Check for *.wiki repositories.
...@@ -35,7 +36,8 @@ module API ...@@ -35,7 +36,8 @@ module API
project, project,
params[:ref], params[:ref],
params[:oldrev], params[:oldrev],
params[:newrev] params[:newrev],
params[:forced_push]
) )
end end
......
...@@ -48,17 +48,15 @@ module API ...@@ -48,17 +48,15 @@ module API
# Example Request: # Example Request:
# POST /projects/:id/issues # POST /projects/:id/issues
post ":id/issues" do post ":id/issues" do
set_current_user_for_thread do required_attributes! [:title]
required_attributes! [:title] attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id]
attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id] attrs[:label_list] = params[:labels] if params[:labels].present?
attrs[:label_list] = params[:labels] if params[:labels].present? issue = ::Issues::CreateService.new(user_project, current_user, attrs).execute
@issue = user_project.issues.new attrs
@issue.author = current_user if issue.valid?
if @issue.save present issue, with: Entities::Issue
present @issue, with: Entities::Issue else
else not_found!
not_found!
end
end end
end end
...@@ -76,18 +74,18 @@ module API ...@@ -76,18 +74,18 @@ module API
# Example Request: # Example Request:
# PUT /projects/:id/issues/:issue_id # PUT /projects/:id/issues/:issue_id
put ":id/issues/:issue_id" do put ":id/issues/:issue_id" do
set_current_user_for_thread do issue = user_project.issues.find(params[:issue_id])
@issue = user_project.issues.find(params[:issue_id]) authorize! :modify_issue, issue
authorize! :modify_issue, @issue
attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id, :state_event]
attrs[:label_list] = params[:labels] if params[:labels].present?
attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id, :state_event] issue = ::Issues::UpdateService.new(user_project, current_user, attrs).execute(issue)
attrs[:label_list] = params[:labels] if params[:labels].present?
if @issue.update_attributes attrs if issue.valid?
present @issue, with: Entities::Issue present issue, with: Entities::Issue
else else
not_found! not_found!
end
end end
end end
......
...@@ -13,14 +13,6 @@ module API ...@@ -13,14 +13,6 @@ module API
end end
not_found! not_found!
end end
def not_fork?(target_project_id, user_project)
target_project_id.nil? || target_project_id == user_project.id.to_s
end
def target_matches_fork(target_project_id,user_project)
user_project.forked? && user_project.forked_from_project.id.to_s == target_project_id
end
end end
# List merge requests # List merge requests
...@@ -70,29 +62,15 @@ module API ...@@ -70,29 +62,15 @@ module API
# POST /projects/:id/merge_requests # POST /projects/:id/merge_requests
# #
post ":id/merge_requests" do post ":id/merge_requests" do
set_current_user_for_thread do authorize! :write_merge_request, user_project
authorize! :write_merge_request, user_project required_attributes! [:source_branch, :target_branch, :title]
required_attributes! [:source_branch, :target_branch, :title] attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :target_project_id, :description]
attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :target_project_id, :description] merge_request = ::MergeRequests::CreateService.new(user_project, current_user, attrs).execute
merge_request = user_project.merge_requests.new(attrs)
merge_request.author = current_user if merge_request.valid?
merge_request.source_project = user_project present merge_request, with: Entities::MergeRequest
target_project_id = attrs[:target_project_id] else
if not_fork?(target_project_id, user_project) handle_merge_request_errors! merge_request.errors
merge_request.target_project = user_project
else
if target_matches_fork(target_project_id,user_project)
merge_request.target_project = Project.find_by(id: attrs[:target_project_id])
else
render_api_error!('(Bad Request) Specified target project that is not the source project, or the source fork of the project.', 400)
end
end
if merge_request.save
present merge_request, with: Entities::MergeRequest
else
handle_merge_request_errors! merge_request.errors
end
end end
end end
...@@ -111,17 +89,15 @@ module API ...@@ -111,17 +89,15 @@ module API
# PUT /projects/:id/merge_request/:merge_request_id # PUT /projects/:id/merge_request/:merge_request_id
# #
put ":id/merge_request/:merge_request_id" do put ":id/merge_request/:merge_request_id" do
set_current_user_for_thread do attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :state_event, :description]
attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :state_event, :description] merge_request = user_project.merge_requests.find(params[:merge_request_id])
merge_request = user_project.merge_requests.find(params[:merge_request_id]) authorize! :modify_merge_request, merge_request
merge_request = ::MergeRequests::UpdateService.new(user_project, current_user, attrs).execute(merge_request)
authorize! :modify_merge_request, merge_request
if merge_request.update_attributes attrs if merge_request.valid?
present merge_request, with: Entities::MergeRequest present merge_request, with: Entities::MergeRequest
else else
handle_merge_request_errors! merge_request.errors handle_merge_request_errors! merge_request.errors
end
end end
end end
......
...@@ -5,7 +5,7 @@ module Gitlab ...@@ -5,7 +5,7 @@ module Gitlab
attr_reader :params, :project, :git_cmd, :user attr_reader :params, :project, :git_cmd, :user
def allowed?(actor, cmd, project, ref = nil, oldrev = nil, newrev = nil) def allowed?(actor, cmd, project, ref = nil, oldrev = nil, newrev = nil, forced_push = false)
case cmd case cmd
when *DOWNLOAD_COMMANDS when *DOWNLOAD_COMMANDS
if actor.is_a? User if actor.is_a? User
...@@ -19,12 +19,12 @@ module Gitlab ...@@ -19,12 +19,12 @@ module Gitlab
end end
when *PUSH_COMMANDS when *PUSH_COMMANDS
if actor.is_a? User if actor.is_a? User
push_allowed?(actor, project, ref, oldrev, newrev) push_allowed?(actor, project, ref, oldrev, newrev, forced_push)
elsif actor.is_a? DeployKey elsif actor.is_a? DeployKey
# Deploy key not allowed to push # Deploy key not allowed to push
return false return false
elsif actor.is_a? Key elsif actor.is_a? Key
push_allowed?(actor.user, project, ref, oldrev, newrev) push_allowed?(actor.user, project, ref, oldrev, newrev, forced_push)
else else
raise 'Wrong actor' raise 'Wrong actor'
end end
...@@ -41,13 +41,17 @@ module Gitlab ...@@ -41,13 +41,17 @@ module Gitlab
end end
end end
def push_allowed?(user, project, ref, oldrev, newrev) def push_allowed?(user, project, ref, oldrev, newrev, forced_push)
if user && user_allowed?(user) if user && user_allowed?(user)
action = if project.protected_branch?(ref) action = if project.protected_branch?(ref)
:push_code_to_protected_branches if forced_push.to_s == 'true'
else :force_push_code_to_protected_branches
:push_code else
end :push_code_to_protected_branches
end
else
:push_code
end
user.can?(action, project) user.can?(action, project)
else else
false false
......
...@@ -342,6 +342,7 @@ namespace :gitlab do ...@@ -342,6 +342,7 @@ namespace :gitlab do
check_repo_base_is_not_symlink check_repo_base_is_not_symlink
check_repo_base_user_and_group check_repo_base_user_and_group
check_repo_base_permissions check_repo_base_permissions
check_satellites_permissions
check_update_hook_is_up_to_date check_update_hook_is_up_to_date
check_repos_update_hooks_is_link check_repos_update_hooks_is_link
check_gitlab_shell_self_test check_gitlab_shell_self_test
...@@ -443,6 +444,29 @@ namespace :gitlab do ...@@ -443,6 +444,29 @@ namespace :gitlab do
end end
end end
def check_satellites_permissions
print "Satellites access is drwxr-x---? ... "
satellites_path = Gitlab.config.satellites.path
unless File.exists?(satellites_path)
puts "can't check because of previous errors".magenta
return
end
if File.stat(satellites_path).mode.to_s(8).ends_with?("0750")
puts "yes".green
else
puts "no".red
try_fixing_it(
"sudo chmod u+rwx,g+rx,o-rwx #{satellites_path}",
)
for_more_information(
see_installation_guide_section "GitLab"
)
fix_and_rerun
end
end
def check_repo_base_user_and_group def check_repo_base_user_and_group
gitlab_shell_ssh_user = Gitlab.config.gitlab_shell.ssh_user gitlab_shell_ssh_user = Gitlab.config.gitlab_shell.ssh_user
gitlab_shell_owner_group = Gitlab.config.gitlab_shell.owner_group gitlab_shell_owner_group = Gitlab.config.gitlab_shell.owner_group
......
...@@ -24,6 +24,7 @@ namespace :gitlab do ...@@ -24,6 +24,7 @@ namespace :gitlab do
puts "Gem Version:\t#{gem_version || "unknown".red}" puts "Gem Version:\t#{gem_version || "unknown".red}"
puts "Bundler Version:#{bunder_version || "unknown".red}" puts "Bundler Version:#{bunder_version || "unknown".red}"
puts "Rake Version:\t#{rake_version || "unknown".red}" puts "Rake Version:\t#{rake_version || "unknown".red}"
puts "Sidekiq Version:#{Sidekiq::VERSION}"
# check database adapter # check database adapter
......
...@@ -37,7 +37,7 @@ function start_no_deamonize ...@@ -37,7 +37,7 @@ function start_no_deamonize
function start_sidekiq function start_sidekiq
{ {
bundle exec sidekiq -q post_receive,mailer,system_hook,project_web_hook,gitlab_shell,common,default -e $RAILS_ENV -P $sidekiq_pidfile $@ >> $sidekiq_logfile 2>&1 bundle exec sidekiq -q post_receive -q mailer -q system_hook -q project_web_hook -q gitlab_shell -q common -q default -e $RAILS_ENV -P $sidekiq_pidfile $@ >> $sidekiq_logfile 2>&1
} }
function load_ok function load_ok
......
...@@ -5,10 +5,10 @@ describe MergeRequestsFinder do ...@@ -5,10 +5,10 @@ describe MergeRequestsFinder do
let(:user2) { create :user } let(:user2) { create :user }
let(:project1) { create(:project) } let(:project1) { create(:project) }
let(:project2) { create(:project) } let(:project2) { create(:project, forked_from_project: project1) }
let!(:merge_request1) { create(:merge_request, :simple, author: user, source_project: project1, target_project: project2) } let!(:merge_request1) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project1) }
let!(:merge_request2) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project1) } let!(:merge_request2) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project1, state: 'closed') }
let!(:merge_request3) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project2) } let!(:merge_request3) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project2) }
before do before do
...@@ -21,7 +21,7 @@ describe MergeRequestsFinder do ...@@ -21,7 +21,7 @@ describe MergeRequestsFinder do
it 'should filter by scope' do it 'should filter by scope' do
params = { scope: 'authored', state: 'opened' } params = { scope: 'authored', state: 'opened' }
merge_requests = MergeRequestsFinder.new.execute(user, params) merge_requests = MergeRequestsFinder.new.execute(user, params)
merge_requests.size.should == 3 merge_requests.size.should == 2
end end
it 'should filter by project' do it 'should filter by project' do
......
...@@ -13,7 +13,7 @@ describe 'Gitlab::Satellite::MergeAction' do ...@@ -13,7 +13,7 @@ describe 'Gitlab::Satellite::MergeAction' do
end end
let(:project) { create(:project, namespace: create(:group)) } let(:project) { create(:project, namespace: create(:group)) }
let(:fork_project) { create(:project, namespace: create(:group)) } let(:fork_project) { create(:project, namespace: create(:group), forked_from_project: project) }
let(:merge_request) { create(:merge_request, source_project: project, target_project: project) } let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
let(:merge_request_fork) { create(:merge_request, source_project: fork_project, target_project: project) } let(:merge_request_fork) { create(:merge_request, source_project: fork_project, target_project: project) }
......
...@@ -68,4 +68,18 @@ describe Key do ...@@ -68,4 +68,18 @@ describe Key do
build(:invalid_key).should_not be_valid build(:invalid_key).should_not be_valid
end end
end end
context 'callbacks' do
it 'should add new key to authorized_file' do
@key = build(:personal_key, id: 7)
GitlabShellWorker.should_receive(:perform_async).with(:add_key, @key.shell_id, @key.key)
@key.save
end
it 'should remove key from authorized_file' do
@key = create(:personal_key)
GitlabShellWorker.should_receive(:perform_async).with(:remove_key, @key.shell_id, @key.key)
@key.destroy
end
end
end end
...@@ -209,7 +209,7 @@ describe Note do ...@@ -209,7 +209,7 @@ describe Note do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:author) { create(:user) } let(:author) { create(:user) }
let(:issue) { create(:issue, project: project) } let(:issue) { create(:issue, project: project) }
let(:mergereq) { create(:merge_request, target_project: project) } let(:mergereq) { create(:merge_request, :simple, target_project: project, source_project: project) }
let(:commit) { project.repository.commit } let(:commit) { project.repository.commit }
# Test all of {issue, merge request, commit} in both the referenced and referencing # Test all of {issue, merge request, commit} in both the referenced and referencing
......
require 'spec_helper'
describe EmailObserver do
let(:email) { create(:email) }
before { subject.stub(notification: double('NotificationService').as_null_object) }
subject { EmailObserver.instance }
describe '#after_create' do
it 'trigger notification to send emails' do
subject.should_receive(:notification)
subject.after_create(email)
end
end
end
require 'spec_helper'
describe IssueObserver do
let(:some_user) { create :user }
let(:assignee) { create :user }
let(:author) { create :user }
let(:mock_issue) { create(:issue, assignee: assignee, author: author) }
before { subject.stub(:current_user).and_return(some_user) }
before { subject.stub(:current_commit).and_return(nil) }
before { subject.stub(notification: double('NotificationService').as_null_object) }
before { mock_issue.project.stub_chain(:repository, :commit).and_return(nil) }
subject { IssueObserver.instance }
describe '#after_create' do
it 'trigger notification to send emails' do
subject.should_receive(:notification)
subject.after_create(mock_issue)
end
it 'should create cross-reference notes' do
other_issue = create(:issue)
mock_issue.stub(references: [other_issue])
Note.should_receive(:create_cross_reference_note).with(other_issue, mock_issue,
some_user, mock_issue.project)
subject.after_create(mock_issue)
end
end
context '#after_close' do
context 'a status "closed"' do
before { mock_issue.stub(state: 'closed') }
it 'note is created if the issue is being closed' do
Note.should_receive(:create_status_change_note).with(mock_issue, mock_issue.project, some_user, 'closed', nil)
subject.after_close(mock_issue, nil)
end
it 'trigger notification to send emails' do
subject.notification.should_receive(:close_issue).with(mock_issue, some_user)
subject.after_close(mock_issue, nil)
end
it 'appends a mention to the closing commit if one is present' do
commit = double('commit', gfm_reference: 'commit 123456')
subject.stub(current_commit: commit)
Note.should_receive(:create_status_change_note).with(mock_issue, mock_issue.project, some_user, 'closed', commit)
subject.after_close(mock_issue, nil)
end
end
context 'a status "reopened"' do
before { mock_issue.stub(state: 'reopened') }
it 'note is created if the issue is being reopened' do
Note.should_receive(:create_status_change_note).with(mock_issue, mock_issue.project, some_user, 'reopened', nil)
subject.after_reopen(mock_issue, nil)
end
end
end
context '#after_update' do
before(:each) do
mock_issue.stub(:is_being_reassigned?).and_return(false)
end
context 'notification' do
it 'triggered if the issue is being reassigned' do
mock_issue.should_receive(:is_being_reassigned?).and_return(true)
subject.should_receive(:notification)
subject.after_update(mock_issue)
end
it 'is not triggered if the issue is not being reassigned' do
mock_issue.should_receive(:is_being_reassigned?).and_return(false)
subject.should_not_receive(:notification)
subject.after_update(mock_issue)
end
end
context 'cross-references' do
it 'notices added references' do
mock_issue.should_receive(:notice_added_references)
subject.after_update(mock_issue)
end
end
end
end
require 'spec_helper'
describe KeyObserver do
before do
@key = create(:personal_key)
@observer = KeyObserver.instance
end
context :after_create do
it do
GitlabShellWorker.should_receive(:perform_async).with(:add_key, @key.shell_id, @key.key)
@observer.after_create(@key)
end
end
context :after_destroy do
it do
GitlabShellWorker.should_receive(:perform_async).with(:remove_key, @key.shell_id, @key.key)
@observer.after_destroy(@key)
end
end
end
require 'spec_helper'
describe MergeRequestObserver do
let(:some_user) { create :user }
let(:assignee) { create :user }
let(:author) { create :user }
let(:project) { create :project }
let(:mr_mock) { double(:merge_request, id: 42, assignee: assignee, author: author).as_null_object }
let(:assigned_mr) { create(:merge_request, assignee: assignee, author: author, source_project: project) }
let(:unassigned_mr) { create(:merge_request, author: author, source_project: project) }
let(:closed_assigned_mr) { create(:closed_merge_request, assignee: assignee, author: author, source_project: project) }
let(:closed_unassigned_mr) { create(:closed_merge_request, author: author, source_project: project) }
before { subject.stub(:current_user).and_return(some_user) }
before { subject.stub(notification: double('NotificationService').as_null_object) }
before { mr_mock.stub(:author_id) }
before { mr_mock.stub(:source_project) }
before { mr_mock.stub(:source_project) }
before { mr_mock.stub(:project) }
before { mr_mock.stub(:create_cross_references!).and_return(true) }
before { Repository.any_instance.stub(commit: nil) }
before(:each) { enable_observers }
after(:each) { disable_observers }
subject { MergeRequestObserver.instance }
describe '#after_create' do
it 'trigger notification service' do
subject.should_receive(:notification)
subject.after_create(mr_mock)
end
it 'creates cross-reference notes' do
project = create :project
mr_mock.stub(title: "this mr references !#{assigned_mr.id}", project: project)
mr_mock.should_receive(:create_cross_references!).with(project, some_user)
subject.after_create(mr_mock)
end
end
context '#after_update' do
before(:each) do
mr_mock.stub(:is_being_reassigned?).and_return(false)
mr_mock.stub(:notice_added_references)
end
it 'is called when a merge request is changed' do
changed = create(:merge_request, source_project: project)
subject.should_receive(:after_update)
MergeRequest.observers.enable :merge_request_observer do
changed.title = 'I changed'
changed.save
end
end
it 'checks for new references' do
mr_mock.should_receive(:notice_added_references)
subject.after_update(mr_mock)
end
context 'a notification' do
it 'is sent if the merge request is being reassigned' do
mr_mock.should_receive(:is_being_reassigned?).and_return(true)
subject.should_receive(:notification)
subject.after_update(mr_mock)
end
it 'is not sent if the merge request is not being reassigned' do
mr_mock.should_receive(:is_being_reassigned?).and_return(false)
subject.should_not_receive(:notification)
subject.after_update(mr_mock)
end
end
end
context '#after_close' do
context 'a status "closed"' do
it 'note is created if the merge request is being closed' do
Note.should_receive(:create_status_change_note).with(assigned_mr, assigned_mr.source_project, some_user, 'closed', nil)
assigned_mr.close
end
it 'notification is delivered only to author if the merge request is being closed' do
Note.should_receive(:create_status_change_note).with(unassigned_mr, unassigned_mr.source_project, some_user, 'closed', nil)
unassigned_mr.close
end
end
end
context '#after_reopen' do
context 'a status "reopened"' do
it 'note is created if the merge request is being reopened' do
Note.should_receive(:create_status_change_note).with(closed_assigned_mr, closed_assigned_mr.source_project, some_user, 'reopened', nil)
closed_assigned_mr.reopen
end
it 'notification is delivered only to author if the merge request is being reopened' do
Note.should_receive(:create_status_change_note).with(closed_unassigned_mr, closed_unassigned_mr.source_project, some_user, 'reopened', nil)
closed_unassigned_mr.reopen
end
end
end
describe "Merge Request created" do
def self.it_should_be_valid_event
it { @event.should_not be_nil }
it { @event.should_not be_nil }
it { @event.project.should == project }
it { @event.project.should == project }
end
before do
@merge_request = create(:merge_request, source_project: project, target_project: project)
@event = Event.last
end
it_should_be_valid_event
it { @event.action.should == Event::CREATED }
it { @event.target.should == @merge_request }
end
end
...@@ -21,7 +21,7 @@ describe UsersProjectObserver do ...@@ -21,7 +21,7 @@ describe UsersProjectObserver do
it "should send email to user" do it "should send email to user" do
subject.should_receive(:notification) subject.should_receive(:notification)
@users_project.update_attribute(:project_access, UsersProject::MASTER) @users_project.update_attribute(:project_access, UsersProject::OWNER)
end end
it "should not called after UsersProject destroyed" do it "should not called after UsersProject destroyed" do
......
...@@ -6,7 +6,7 @@ describe API::API do ...@@ -6,7 +6,7 @@ describe API::API do
after(:each) { ActiveRecord::Base.observers.disable(:user_observer) } after(:each) { ActiveRecord::Base.observers.disable(:user_observer) }
let(:user) { create(:user) } let(:user) { create(:user) }
let!(:project) {create(:project, creator_id: user.id, namespace: user.namespace) } let!(:project) {create(:project, creator_id: user.id, namespace: user.namespace) }
let!(:merge_request) { create(:merge_request, author: user, assignee: user, source_project: project, target_project: project, title: "Test") } let!(:merge_request) { create(:merge_request, :simple, author: user, assignee: user, source_project: project, target_project: project, title: "Test") }
let!(:note) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "a comment on a MR") } let!(:note) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "a comment on a MR") }
before { before {
project.team << [user, :reporters] project.team << [user, :reporters]
...@@ -79,16 +79,12 @@ describe API::API do ...@@ -79,16 +79,12 @@ describe API::API do
end end
context 'forked projects' do context 'forked projects' do
let!(:user2) {create(:user)} let!(:user2) { create(:user) }
let!(:forked_project_link) { build(:forked_project_link) } let!(:fork_project) { create(:project, forked_from_project: project, namespace: user2.namespace, creator_id: user2.id) }
let!(:fork_project) { create(:project, forked_project_link: forked_project_link, namespace: user2.namespace, creator_id: user2.id) } let!(:unrelated_project) { create(:project, namespace: create(:user).namespace, creator_id: user2.id) }
let!(:unrelated_project) { create(:project, namespace: create(:user).namespace, creator_id: user2.id) }
before :each do |each| before :each do |each|
fork_project.team << [user2, :reporters] fork_project.team << [user2, :reporters]
forked_project_link.forked_from_project = project
forked_project_link.forked_to_project = fork_project
forked_project_link.save!
end end
it "should return merge_request" do it "should return merge_request" do
...@@ -127,16 +123,16 @@ describe API::API do ...@@ -127,16 +123,16 @@ describe API::API do
response.status.should == 400 response.status.should == 400
end end
it "should return 400 when target_branch is specified and not a forked project" do it "should return 404 when target_branch is specified and not a forked project" do
post api("/projects/#{project.id}/merge_requests", user), post api("/projects/#{project.id}/merge_requests", user),
title: 'Test merge_request', target_branch: 'master', source_branch: 'stable', author: user, target_project_id: fork_project.id title: 'Test merge_request', target_branch: 'master', source_branch: 'stable', author: user, target_project_id: fork_project.id
response.status.should == 400 response.status.should == 404
end end
it "should return 400 when target_branch is specified and for a different fork" do it "should return 404 when target_branch is specified and for a different fork" do
post api("/projects/#{fork_project.id}/merge_requests", user2), post api("/projects/#{fork_project.id}/merge_requests", user2),
title: 'Test merge_request', target_branch: 'master', source_branch: 'stable', author: user2, target_project_id: unrelated_project.id title: 'Test merge_request', target_branch: 'master', source_branch: 'stable', author: user2, target_project_id: unrelated_project.id
response.status.should == 400 response.status.should == 404
end end
it "should return 201 when target_branch is specified and for the same project" do it "should return 201 when target_branch is specified and for the same project" do
......
...@@ -170,16 +170,10 @@ describe GitPushService do ...@@ -170,16 +170,10 @@ describe GitPushService do
Issue.find(issue.id).should be_closed Issue.find(issue.id).should be_closed
end end
it "passes the closing commit as a thread-local" do
service.execute(project, user, @oldrev, @newrev, @ref)
Thread.current[:current_commit].should == closing_commit
end
it "doesn't create cross-reference notes for a closing reference" do it "doesn't create cross-reference notes for a closing reference" do
expect { expect {
service.execute(project, user, @oldrev, @newrev, @ref) service.execute(project, user, @oldrev, @newrev, @ref)
}.not_to change { Note.where(project_id: project.id, system: true).count } }.not_to change { Note.where(project_id: project.id, system: true, commit_id: closing_commit.id).count }
end end
it "doesn't close issues when pushed to non-default branches" do it "doesn't close issues when pushed to non-default branches" do
......
require 'spec_helper'
describe Issues::CloseService do
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:issue) { create(:issue, assignee: user2) }
before do
project.team << [user, :master]
project.team << [user2, :developer]
end
describe :execute do
context "valid params" do
before do
@issue = Issues::CloseService.new(project, user, {}).execute(issue)
end
it { @issue.should be_valid }
it { @issue.should be_closed }
it 'should send email to user2 about assign of new issue' do
email = ActionMailer::Base.deliveries.last
email.to.first.should == user2.email
email.subject.should include(issue.title)
end
it 'should create system note about issue reassign' do
note = @issue.notes.last
note.note.should include "Status changed to closed"
end
end
end
end
require 'spec_helper'
describe Issues::CreateService do
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
describe :execute do
context "valid params" do
before do
project.team << [user, :master]
opts = {
title: 'Awesome issue',
description: 'please fix'
}
@issue = Issues::CreateService.new(project, user, opts).execute
end
it { @issue.should be_valid }
it { @issue.title.should == 'Awesome issue' }
end
end
end
require 'spec_helper'
describe Issues::UpdateService do
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:issue) { create(:issue) }
before do
project.team << [user, :master]
project.team << [user2, :developer]
end
describe :execute do
context "valid params" do
before do
opts = {
title: 'New title',
description: 'Also please fix',
assignee_id: user2.id,
state_event: 'close'
}
@issue = Issues::UpdateService.new(project, user, opts).execute(issue)
end
it { @issue.should be_valid }
it { @issue.title.should == 'New title' }
it { @issue.assignee.should == user2 }
it { @issue.should be_closed }
it 'should send email to user2 about assign of new issue' do
email = ActionMailer::Base.deliveries.last
email.to.first.should == user2.email
email.subject.should include(issue.title)
end
it 'should create system note about issue reassign' do
note = @issue.notes.last
note.note.should include "Reassigned to \@#{user2.username}"
end
end
end
end
require 'spec_helper'
describe MergeRequests::CloseService do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:merge_request) { create(:merge_request, assignee: user2) }
let(:project) { merge_request.project }
before do
project.team << [user, :master]
project.team << [user2, :developer]
end
describe :execute do
context "valid params" do
before do
@merge_request = MergeRequests::CloseService.new(project, user, {}).execute(merge_request)
end
it { @merge_request.should be_valid }
it { @merge_request.should be_closed }
it 'should send email to user2 about assign of new merge_request' do
email = ActionMailer::Base.deliveries.last
email.to.first.should == user2.email
email.subject.should include(merge_request.title)
end
it 'should create system note about merge_request reassign' do
note = @merge_request.notes.last
note.note.should include "Status changed to closed"
end
end
end
end
require 'spec_helper'
describe MergeRequests::CreateService do
let(:project) { create(:project) }
let(:user) { create(:user) }
describe :execute do
context "valid params" do
before do
project.team << [user, :master]
opts = {
title: 'Awesome merge_request',
description: 'please fix',
source_branch: 'stable',
target_branch: 'master'
}
@merge_request = MergeRequests::CreateService.new(project, user, opts).execute
end
it { @merge_request.should be_valid }
it { @merge_request.title.should == 'Awesome merge_request' }
end
end
end
require 'spec_helper'
describe MergeRequests::UpdateService do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:merge_request) { create(:merge_request, :simple) }
let(:project) { merge_request.project }
before do
project.team << [user, :master]
project.team << [user2, :developer]
end
describe :execute do
context "valid params" do
before do
opts = {
title: 'New title',
description: 'Also please fix',
assignee_id: user2.id,
state_event: 'close'
}
@merge_request = MergeRequests::UpdateService.new(project, user, opts).execute(merge_request)
end
it { @merge_request.should be_valid }
it { @merge_request.title.should == 'New title' }
it { @merge_request.assignee.should == user2 }
it { @merge_request.should be_closed }
it 'should send email to user2 about assign of new merge_request' do
email = ActionMailer::Base.deliveries.last
email.to.first.should == user2.email
email.subject.should include(merge_request.title)
end
it 'should create system note about merge_request reassign' do
note = @merge_request.notes.last
note.note.should include "Reassigned to \@#{user2.username}"
end
end
end
end
...@@ -5,7 +5,7 @@ describe NotificationService do ...@@ -5,7 +5,7 @@ describe NotificationService do
describe 'Keys' do describe 'Keys' do
describe :new_key do describe :new_key do
let(:key) { create(:personal_key) } let!(:key) { create(:personal_key) }
it { notification.new_key(key).should be_true } it { notification.new_key(key).should be_true }
...@@ -18,7 +18,7 @@ describe NotificationService do ...@@ -18,7 +18,7 @@ describe NotificationService do
describe 'Email' do describe 'Email' do
describe :new_email do describe :new_email do
let(:email) { create(:email) } let!(:email) { create(:email) }
it { notification.new_email(email).should be_true } it { notification.new_email(email).should be_true }
...@@ -57,15 +57,42 @@ describe NotificationService do ...@@ -57,15 +57,42 @@ describe NotificationService do
Notify.should_not_receive(:note_issue_email) Notify.should_not_receive(:note_issue_email)
notification.new_note(mentioned_note) notification.new_note(mentioned_note)
end end
end
def should_email(user_id) describe 'new note on issue in project that belongs to a group' do
Notify.should_receive(:note_issue_email).with(user_id, note.id) let(:group) { create(:group) }
before do
note.project.namespace_id = group.id
note.project.group.add_user(@u_watcher, UsersGroup::MASTER)
note.project.save
user_project = note.project.users_projects.find_by_user_id(@u_watcher.id)
user_project.notification_level = Notification::N_PARTICIPATING
user_project.save
user_group = note.project.group.users_groups.find_by_user_id(@u_watcher.id)
user_group.notification_level = Notification::N_GLOBAL
user_group.save
end end
def should_not_email(user_id) it do
Notify.should_not_receive(:note_issue_email).with(user_id, note.id) should_email(note.noteable.author_id)
should_email(note.noteable.assignee_id)
should_email(@u_mentioned.id)
should_not_email(@u_watcher.id)
should_not_email(note.author_id)
should_not_email(@u_participating.id)
should_not_email(@u_disabled.id)
notification.new_note(note)
end end
end end
def should_email(user_id)
Notify.should_receive(:note_issue_email).with(user_id, note.id)
end
def should_not_email(user_id)
Notify.should_not_receive(:note_issue_email).with(user_id, note.id)
end
end end
context 'commit note' do context 'commit note' do
......
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