Commit 4d1945028ebee62ff04896a8f1b9aec08f60df86

Authored by Timm Drevensek
2 parents cb659e4c 7611ce72

Merge branch 'master' into request/relative_submodules

Showing 79 changed files with 831 additions and 857 deletions   Show diff stats
CHANGELOG
... ... @@ -6,6 +6,13 @@ v 6.8.0
6 6 - Drop all tables before restoring a Postgres backup
7 7 - Make the repository downloads path configurable
8 8 - Create branches via API (sponsored by O'Reilly Media)
  9 + - Changed permission of gitlab-satellites directory not to be world accessible
  10 + - Protected branch does not allow force push
  11 +
  12 +v 6.7.3
  13 + - Fix the merge notification email not being sent (Pierre de La Morinerie)
  14 + - Drop all tables before restoring a Postgres backup
  15 + - Remove yanked modernizr gem
9 16  
10 17 v 6.7.2
11 18 - Fix upgrader script
... ...
CONTRIBUTING.md
... ... @@ -29,7 +29,9 @@ If something is wrong but it is not a regression compared to older versions of G
29 29 When submitting an issue please conform to the issue submission guidelines listed below.
30 30 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.
31 31  
32   -Do not use the issue tracker for feature requests. We have a specific [feature request forum](http://feedback.gitlab.com) for this purpose.
  32 +Do not use the issue tracker for feature requests.
  33 +We have a specific [feature request forum](http://feedback.gitlab.com) for this purpose.
  34 +Please keep feature requests as small and simple as possible, complex ones might be edited to make them small and simple.
33 35  
34 36 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.
35 37  
... ...
Gemfile
... ... @@ -12,8 +12,6 @@ gem "rails", "~> 4.0.0"
12 12  
13 13 gem "protected_attributes"
14 14 gem 'rails-observers'
15   -gem 'actionpack-page_caching'
16   -gem 'actionpack-action_caching'
17 15  
18 16 # Default values for AR models
19 17 gem "default_value_for", "~> 3.0.0"
... ... @@ -102,7 +100,7 @@ gem "acts-as-taggable-on"
102 100 # Background jobs
103 101 gem 'slim'
104 102 gem 'sinatra', require: nil
105   -gem 'sidekiq'
  103 +gem 'sidekiq', '2.17.0'
106 104  
107 105 # HTTP requests
108 106 gem "httparty"
... ... @@ -161,7 +159,6 @@ gem 'select2-rails'
161 159 gem 'jquery-atwho-rails', "~> 0.3.3"
162 160 gem "jquery-rails", "2.1.3"
163 161 gem "jquery-ui-rails", "2.0.2"
164   -gem "modernizr", "2.6.2"
165 162 gem "raphael-rails", "~> 2.1.2"
166 163 gem 'bootstrap-sass', '~> 3.0'
167 164 gem "font-awesome-rails", '~> 3.2'
... ...
Gemfile.lock
... ... @@ -27,10 +27,6 @@ GEM
27 27 erubis (~> 2.7.0)
28 28 rack (~> 1.5.2)
29 29 rack-test (~> 0.6.2)
30   - actionpack-action_caching (1.1.0)
31   - actionpack (>= 4.0.0, < 5.0)
32   - actionpack-page_caching (1.0.2)
33   - actionpack (>= 4.0.0, < 5)
34 30 activemodel (4.0.3)
35 31 activesupport (= 4.0.3)
36 32 builder (~> 3.1.0)
... ... @@ -289,8 +285,6 @@ GEM
289 285 method_source (0.8.2)
290 286 mime-types (1.25.1)
291 287 minitest (4.7.5)
292   - modernizr (2.6.2)
293   - sprockets (~> 2.0)
294 288 multi_json (1.8.4)
295 289 multi_xml (0.5.5)
296 290 multipart-post (1.2.0)
... ... @@ -567,8 +561,6 @@ PLATFORMS
567 561  
568 562 DEPENDENCIES
569 563 ace-rails-ap
570   - actionpack-action_caching
571   - actionpack-page_caching
572 564 acts-as-taggable-on
573 565 annotate (~> 2.6.0.beta2)
574 566 asciidoctor
... ... @@ -622,7 +614,6 @@ DEPENDENCIES
622 614 launchy
623 615 letter_opener
624 616 minitest (~> 4.7.0)
625   - modernizr (= 2.6.2)
626 617 mysql2
627 618 nprogress-rails
628 619 omniauth (~> 1.1.3)
... ... @@ -653,7 +644,7 @@ DEPENDENCIES
653 644 select2-rails
654 645 settingslogic
655 646 shoulda-matchers (~> 2.1.0)
656   - sidekiq
  647 + sidekiq (= 2.17.0)
657 648 simplecov
658 649 sinatra
659 650 six
... ...
app/assets/javascripts/application.js.coffee
... ... @@ -18,7 +18,6 @@
18 18 #= require turbolinks
19 19 #= require jquery.turbolinks
20 20 #= require bootstrap
21   -#= require modernizr
22 21 #= require select2
23 22 #= require raphael
24 23 #= require g.raphael-min
... ...
app/assets/javascripts/commit/file.js.coffee
1 1 class CommitFile
2   -
  2 +
3 3 constructor: (file) ->
4 4 if $('.image', file).length
5 5 new ImageFile(file)
6   -
7   -this.CommitFile = CommitFile
8 6 \ No newline at end of file
  7 +
  8 +@CommitFile = CommitFile
... ...
app/assets/javascripts/commit/image-file.js.coffee
... ... @@ -125,4 +125,4 @@ class ImageFile
125 125 img.on 'load', =>
126 126 callback.call(this, domImg.naturalWidth, domImg.naturalHeight)
127 127  
128   -this.ImageFile = ImageFile
129 128 \ No newline at end of file
  129 +@ImageFile = ImageFile
... ...
app/assets/stylesheets/application.scss
... ... @@ -2,7 +2,7 @@
2 2 * This is a manifest file that'll automatically include all the stylesheets available in this directory
3 3 * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
4 4 * the top of the compiled file, but it's generally better to create a new file per style scope.
5   - *= require jquery.ui.gitlab
  5 + *= require jquery.ui.datepicker
6 6 *= require jquery.atwho
7 7 *= require select2
8 8 *= require highlightjs.min
... ... @@ -43,6 +43,7 @@
43 43 @import "generic/forms.scss";
44 44 @import "generic/selects.scss";
45 45 @import "generic/highlight.scss";
  46 +@import "generic/jquery.scss";
46 47  
47 48 /**
48 49 * Page specific styles (issues, projects etc):
... ...
app/assets/stylesheets/generic/jquery.scss 0 → 100644
... ... @@ -0,0 +1,20 @@
  1 +.ui-widget {
  2 + font-family: $regular_font;
  3 + font-size: $font-size-base;
  4 +
  5 + &.ui-datepicker-inline {
  6 + border: 1px solid #DDD;
  7 + padding: 10px;
  8 + width: 270px;
  9 +
  10 + .ui-datepicker-header {
  11 + background: #EEE;
  12 + border-color: #DDD;
  13 + }
  14 +
  15 + .ui-datepicker-calendar td a {
  16 + padding: 5px;
  17 + text-align: center;
  18 + }
  19 + }
  20 +}
... ...
app/assets/stylesheets/jquery.ui.gitlab.css
... ... @@ -1,257 +0,0 @@
1   -/* Interaction Cues
2   -----------------------------------*/
3   -.ui-state-disabled { cursor: default !important; }
4   -
5   -
6   -/* Icons
7   -----------------------------------*/
8   -
9   -/* states and images */
10   -.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; }
11   -
12   -
13   -/* Misc visuals
14   -----------------------------------*/
15   -
16   -/* Overlays */
17   -.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
18   -
19   -
20   -/*
21   - * jQuery UI CSS Framework 1.8.7
22   - *
23   - * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
24   - * Dual licensed under the MIT or GPL Version 2 licenses.
25   - * http://jquery.org/license
26   - *
27   - * http://docs.jquery.com/UI/Theming/API
28   - *
29   - * To view and modify this theme, visit http://jqueryui.com/themeroller/?ctl=themeroller
30   - */
31   -
32   -
33   -/* Component containers
34   -----------------------------------*/
35   -.ui-widget { font-family: Arial,sans-serif; font-size: 1.1em; }
36   -.ui-widget .ui-widget { font-size: 1em; }
37   -.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Arial,sans-serif; font-size: 1em; }
38   -.ui-widget-content { border: 1px solid #CCC; background: #ffffff; color: #4F4F4F; }
39   -.ui-widget-content a { color: #4F4F4F; }
40   -.ui-widget-header { border: 1px solid #B6B6B6; color: #4F4F4F; font-weight: bold; }
41   -.ui-widget-header {
42   - background: #ededed url(bg_fallback.png) 0 0 repeat-x; /* Old browsers */
43   - background: -moz-linear-gradient(top, #ededed 0%, #c4c4c4 100%); /* FF3.6+ */
44   - background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ededed), color-stop(100%,#c4c4c4)); /* Chrome,Safari4+ */
45   - background: -webkit-linear-gradient(top, #ededed 0%,#c4c4c4 100%); /* Chrome10+,Safari5.1+ */
46   - background: -o-linear-gradient(top, #ededed 0%,#c4c4c4 100%); /* Opera11.10+ */
47   - background: -ms-linear-gradient(top, #ededed 0%,#c4c4c4 100%); /* IE10+ */
48   - background: linear-gradient(top, #ededed 0%,#c4c4c4 100%); /* W3C */
49   -}
50   -.ui-widget-header a { color: #4F4F4F; }
51   -
52   -/* Interaction states
53   -----------------------------------*/
54   -.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #B6B6B6; font-weight: normal; color: #4F4F4F; }
55   -.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default {
56   - background: #ededed url(bg_fallback.png) 0 0 repeat-x; /* Old browsers */
57   - background: -moz-linear-gradient(top, #ededed 0%, #c4c4c4 100%); /* FF3.6+ */
58   - background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ededed), color-stop(100%,#c4c4c4)); /* Chrome,Safari4+ */
59   - background: -webkit-linear-gradient(top, #ededed 0%,#c4c4c4 100%); /* Chrome10+,Safari5.1+ */
60   - background: -o-linear-gradient(top, #ededed 0%,#c4c4c4 100%); /* Opera11.10+ */
61   - background: -ms-linear-gradient(top, #ededed 0%,#c4c4c4 100%); /* IE10+ */
62   - background: linear-gradient(top, #ededed 0%,#c4c4c4 100%); /* W3C */
63   - -webkit-box-shadow: 0 1px 0 rgba(255,255,255,0.6) inset;
64   - -moz-box-shadow: 0 1px 0 rgba(255,255,255,0.6) inset;
65   - box-shadow: 0 1px 0 rgba(255,255,255,0.6) inset;
66   -}
67   -.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #4F4F4F; text-decoration: none; }
68   -.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; }
69   -.ui-state-hover a, .ui-state-hover a:hover { color: #313131; text-decoration: none; }
70   -.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active {
71   - outline: none;
72   - color: #1c4257; border: 1px solid #7096ab;
73   - background: #ededed url(bg_fallback.png) 0 -50px repeat-x; /* Old browsers */
74   - background: -moz-linear-gradient(top, #b9e0f5 0%, #92bdd6 100%); /* FF3.6+ */
75   - background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#b9e0f5), color-stop(100%,#92bdd6)); /* Chrome,Safari4+ */
76   - background: -webkit-linear-gradient(top, #b9e0f5 0%,#92bdd6 100%); /* Chrome10+,Safari5.1+ */
77   - background: -o-linear-gradient(top, #b9e0f5 0%,#92bdd6 100%); /* Opera11.10+ */
78   - background: -ms-linear-gradient(top, #b9e0f5 0%,#92bdd6 100%); /* IE10+ */
79   - background: linear-gradient(top, #b9e0f5 0%,#92bdd6 100%); /* W3C */
80   - -webkit-box-shadow: none;
81   - -moz-box-shadow: none;
82   - box-shadow: none;
83   -}
84   -.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #313131; text-decoration: none; }
85   -.ui-widget :active { outline: none; }
86   -
87   -/* Interaction Cues
88   -----------------------------------*/
89   -.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; }
90   -.ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #363636; }
91   -.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; }
92   -.ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #cd0a0a; }
93   -.ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #cd0a0a; }
94   -.ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; }
95   -.ui-priority-secondary, .ui-widget-content .ui-priority-secondary, .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; }
96   -.ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; }
97   -
98   -/* Icons
99   -----------------------------------*/
100   -
101   -/* states and images */
102   -.ui-icon { width: 16px; height: 16px; background-image: url(ui-icons_222222_256x240.png); }
103   -.ui-widget-content .ui-icon {background-image: url(ui-icons_222222_256x240.png); }
104   -.ui-widget-header .ui-icon {background-image: url(ui-icons_222222_256x240.png); }
105   -.ui-state-default .ui-icon { background-image: url(ui-icons_454545_256x240.png); }
106   -.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(ui-icons_454545_256x240.png); }
107   -.ui-state-active .ui-icon {background-image: url(ui-icons_454545_256x240.png); }
108   -.ui-state-highlight .ui-icon {background-image: url(ui-icons_454545_256x240.png); }
109   -.ui-state-error .ui-icon, .ui-state-error-text .ui-icon { background: url(icon_sprite.png) -16px 0 no-repeat !important; }
110   -.ui-state-highlight .ui-icon, .ui-state-error .ui-icon { margin-top: -1px; }
111   -
112   -
113   -/* Misc visuals
114   -----------------------------------*/
115   -
116   -/* Overlays */
117   -.ui-widget-overlay { background: #262b33; opacity: .70;filter:Alpha(Opacity=70); }
118   -.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; }
119   -/*
120   - * jQuery UI Selectable 1.8.7
121   - *
122   - * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
123   - * Dual licensed under the MIT or GPL Version 2 licenses.
124   - * http://jquery.org/license
125   - *
126   - * http://docs.jquery.com/UI/Selectable#theming
127   - */
128   -.ui-selectable-helper { position: absolute; z-index: 100; border:1px dotted black; }
129   -/*
130   - * jQuery UI Autocomplete 1.8.7
131   - *
132   - * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
133   - * Dual licensed under the MIT or GPL Version 2 licenses.
134   - * http://jquery.org/license
135   - *
136   - * http://docs.jquery.com/UI/Autocomplete#theming
137   - */
138   -.ui-autocomplete {
139   - position: absolute; cursor: default; z-index: 3;
140   - -moz-border-radius: 0;
141   - -webkit-border-radius: 0;
142   - border-radius: 0;
143   - -moz-box-shadow: 0 1px 5px rgba(0,0,0,0.3);
144   - -webkit-box-shadow: 0 1px 5px rgba(0,0,0,0.3);
145   - box-shadow: 0 1px 5px rgba(0,0,0,0.3);
146   -}
147   -
148   -/* workarounds */
149   -* html .ui-autocomplete { width:1px; } /* without this, the menu expands to 100% in IE6 */
150   -
151   -/*
152   - * jQuery UI Menu 1.8.7
153   - *
154   - * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
155   - * Dual licensed under the MIT or GPL Version 2 licenses.
156   - * http://jquery.org/license
157   - *
158   - * http://docs.jquery.com/UI/Menu#theming
159   - */
160   -.ui-menu {
161   - list-style:none;
162   - padding: 1px;
163   - margin: 0;
164   - display:block;
165   - float: left;
166   -}
167   -.ui-menu .ui-menu {
168   - margin-top: -3px;
169   -}
170   -.ui-menu .ui-menu-item {
171   - margin:0;
172   - padding: 0;
173   - zoom: 1;
174   - float: left;
175   - clear: left;
176   - width: 100%;
177   -}
178   -.ui-menu .ui-menu-item a {
179   - text-decoration:none;
180   - display:block;
181   - padding:.2em .4em;
182   - line-height:1.5;
183   - zoom:1;
184   - color: #666;
185   - font-size: 13px;
186   -}
187   -.ui-menu .ui-menu-item a.ui-state-hover,
188   -.ui-menu .ui-menu-item a.ui-state-active {
189   - font-weight: normal;
190   - margin: -1px;
191   - background: #D9EDF7;
192   - color: #3A89A3;
193   - text-shadow: 0px 1px 1px #fff;
194   - border: none;
195   - border: 1px solid #ADE;
196   - cursor: pointer;
197   - font-weight: bold;
198   -}
199   -
200   -/*
201   - * jQuery UI Datepicker 1.8.7
202   - *
203   - * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
204   - * Dual licensed under the MIT or GPL Version 2 licenses.
205   - * http://jquery.org/license
206   - *
207   - * http://docs.jquery.com/UI/Datepicker#theming
208   - */
209   -.ui-datepicker {
210   - width: 17em;
211   - padding: 0;
212   - display: none;
213   - border-color: #DDDDDD;
214   - border: none;
215   - box-shadow: none;
216   -}
217   -.ui-datepicker .ui-datepicker-header {
218   - position:relative;
219   - padding:.35em 0;
220   - border: none;
221   - border-bottom: 1px solid #B6B6B6;
222   - -moz-border-radius: 0;
223   - -webkit-border-radius: 0;
224   - border-radius: 0;
225   - margin-bottom: 10px;
226   - border: 1px solid #bbb;
227   - -webkit-box-shadow: 0 0 0 3px #F1F1F1;
228   - -moz-box-shadow: 0 0 0 3px #f1f1f1;
229   - -ms-box-shadow: 0 0 0 3px #f1f1f1;
230   - -o-box-shadow: 0 0 0 3px #f1f1f1;
231   - box-shadow: 0 0 0 3px #F1F1F1;
232   -}
233   -
234   -.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 6px; width: 1.8em; height: 1.8em; }
235   -.ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { border: 1px none; }
236   -.ui-datepicker .ui-datepicker-prev { left:2px; }
237   -.ui-datepicker .ui-datepicker-next { right:2px; }
238   -.ui-datepicker .ui-datepicker-prev span { background-position: 0px -32px !important; }
239   -.ui-datepicker .ui-datepicker-next span { background-position: -16px -32px !important; }
240   -.ui-datepicker .ui-datepicker-prev-hover span { background-position: 0px -48px !important; }
241   -.ui-datepicker .ui-datepicker-next-hover span { background-position: -16px -48px !important; }
242   -.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; }
243   -.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); }
244   -.ui-datepicker .ui-datepicker-title select { font-size:1em; margin:1px 0; }
245   -.ui-datepicker select.ui-datepicker-month-year {width: 100%;}
246   -.ui-datepicker select.ui-datepicker-month,
247   -.ui-datepicker select.ui-datepicker-year { width: 49%;}
248   -.ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; }
249   -.ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0; }
250   -.ui-datepicker td { border: 0; padding: 1px; line-height: 24px; background-color: #FFF!important; }
251   -.ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; }
252   -.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; }
253   -.ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; }
254   -.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; }
255   -.ui-datepicker table .ui-state-highlight { border-color: #ADE; }
256   -.ui-datepicker-calendar .ui-state-default { background: transparent; border-color: #FFF; }
257   -.ui-datepicker-calendar .ui-state-active { background: #D9EDF7; border-color: #ADE; color: #3A89A3; font-weight: bold; text-shadow: 0 1px 1px #fff; }
app/assets/stylesheets/main/fonts.scss
1 1 /** Typo **/
2 2 $monospace_font: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono', 'lucida console', monospace;
  3 +$regular_font: "Helvetica Neue", Helvetica, Arial, sans-serif;
... ...
app/controllers/projects/issues_controller.rb
... ... @@ -59,9 +59,7 @@ class Projects::IssuesController &lt; Projects::ApplicationController
59 59 end
60 60  
61 61 def create
62   - @issue = @project.issues.new(params[:issue])
63   - @issue.author = current_user
64   - @issue.save
  62 + @issue = Issues::CreateService.new(project, current_user, params[:issue]).execute
65 63  
66 64 respond_to do |format|
67 65 format.html do
... ... @@ -76,8 +74,7 @@ class Projects::IssuesController &lt; Projects::ApplicationController
76 74 end
77 75  
78 76 def update
79   - @issue.update_attributes(params[:issue])
80   - @issue.reset_events_cache
  77 + @issue = Issues::UpdateService.new(project, current_user, params[:issue]).execute(issue)
81 78  
82 79 respond_to do |format|
83 80 format.js
... ...
app/controllers/projects/merge_requests_controller.rb
... ... @@ -76,10 +76,10 @@ class Projects::MergeRequestsController &lt; Projects::ApplicationController
76 76 end
77 77  
78 78 def create
79   - @merge_request = MergeRequest.new(params[:merge_request])
80   - @merge_request.author = current_user
81 79 @target_branches ||= []
82   - if @merge_request.save
  80 + @merge_request = MergeRequests::CreateService.new(project, current_user, params[:merge_request]).execute
  81 +
  82 + if @merge_request.valid?
83 83 redirect_to [@merge_request.target_project, @merge_request], notice: 'Merge request was successfully created.'
84 84 else
85 85 @source_project = @merge_request.source_project
... ... @@ -89,29 +89,9 @@ class Projects::MergeRequestsController &lt; Projects::ApplicationController
89 89 end
90 90  
91 91 def update
92   - # If we close MergeRequest we want to ignore validation
93   - # so we can close broken one (Ex. fork project removed)
94   - if params[:merge_request] == {"state_event"=>"close"}
95   - @merge_request.allow_broken = true
96   -
97   - if @merge_request.close
98   - opts = { notice: 'Merge request was successfully closed.' }
99   - else
100   - opts = { alert: 'Failed to close merge request.' }
101   - end
102   -
103   - redirect_to [@merge_request.target_project, @merge_request], opts
104   - return
105   - end
106   -
107   - # We dont allow change of source/target projects
108   - # after merge request was created
109   - params[:merge_request].delete(:source_project_id)
110   - params[:merge_request].delete(:target_project_id)
111   -
112   - if @merge_request.update_attributes(params[:merge_request])
113   - @merge_request.reset_events_cache
  92 + @merge_request = MergeRequests::UpdateService.new(project, current_user, params[:merge_request]).execute(@merge_request)
114 93  
  94 + if @merge_request.valid?
115 95 respond_to do |format|
116 96 format.js
117 97 format.html do
... ...
app/helpers/merge_requests_helper.rb
... ... @@ -20,7 +20,7 @@ module MergeRequestsHelper
20 20 target_project_id: target_project.id,
21 21 source_branch: event.branch_name,
22 22 target_branch: target_project.repository.root_ref,
23   - title: event.branch_name.humanize
  23 + title: event.branch_name.titleize.humanize
24 24 }
25 25 end
26 26  
... ...
app/models/email.rb
... ... @@ -13,14 +13,15 @@ class Email &lt; ActiveRecord::Base
13 13 # Relations
14 14 #
15 15 belongs_to :user
16   -
  16 +
17 17 #
18 18 # Validations
19 19 #
20 20 validates :user_id, presence: true
21 21 validates :email, presence: true, email: { strict_mode: true }, uniqueness: true
22 22 validate :unique_email, if: ->(email) { email.email_changed? }
23   -
  23 +
  24 + after_create :notify
24 25 before_validation :cleanup_email
25 26  
26 27 def cleanup_email
... ... @@ -30,4 +31,8 @@ class Email &lt; ActiveRecord::Base
30 31 def unique_email
31 32 self.errors.add(:email, 'has already been taken') if User.exists?(email: self.email)
32 33 end
33   -end
34 34 \ No newline at end of file
  35 +
  36 + def notify
  37 + NotificationService.new.new_email(self)
  38 + end
  39 +end
... ...
app/models/key.rb
... ... @@ -29,6 +29,10 @@ class Key &lt; ActiveRecord::Base
29 29  
30 30 delegate :name, :email, to: :user, prefix: true
31 31  
  32 + after_create :add_to_shell
  33 + after_create :notify_user
  34 + after_destroy :remove_from_shell
  35 +
32 36 def strip_white_space
33 37 self.key = key.strip unless key.blank?
34 38 end
... ... @@ -42,6 +46,26 @@ class Key &lt; ActiveRecord::Base
42 46 "key-#{id}"
43 47 end
44 48  
  49 + def add_to_shell
  50 + GitlabShellWorker.perform_async(
  51 + :add_key,
  52 + shell_id,
  53 + key
  54 + )
  55 + end
  56 +
  57 + def notify_user
  58 + NotificationService.new.new_key(self)
  59 + end
  60 +
  61 + def remove_from_shell
  62 + GitlabShellWorker.perform_async(
  63 + :remove_key,
  64 + shell_id,
  65 + key,
  66 + )
  67 + end
  68 +
45 69 private
46 70  
47 71 def generate_fingerpint
... ...
app/models/merge_request.rb
... ... @@ -97,6 +97,7 @@ class MergeRequest &lt; ActiveRecord::Base
97 97 validates :target_project, presence: true
98 98 validates :target_branch, presence: true
99 99 validate :validate_branches
  100 + validate :validate_fork
100 101  
101 102 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) }
102 103 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 &lt; ActiveRecord::Base
125 126 end
126 127 end
127 128  
  129 + def validate_fork
  130 + return true unless target_project && source_project
  131 +
  132 + if target_project == source_project
  133 + true
  134 + else
  135 + # If source and target projects are different
  136 + # we should check if source project is actually a fork of target project
  137 + if source_project.forked_from?(target_project)
  138 + true
  139 + else
  140 + errors.add :base, "Source project is not a fork of target project"
  141 + end
  142 + end
  143 + end
  144 +
128 145 def update_merge_request_diff
129 146 if source_branch_changed? || target_branch_changed?
130 147 reload_code
... ...
app/models/project.rb
... ... @@ -552,4 +552,8 @@ class Project &lt; ActiveRecord::Base
552 552 gitlab_shell.update_repository_head(self.path_with_namespace, branch)
553 553 reload_default_branch
554 554 end
  555 +
  556 + def forked_from?(project)
  557 + forked? && project == forked_from_project
  558 + end
555 559 end
... ...
app/observers/email_observer.rb
... ... @@ -1,5 +0,0 @@
1   -class EmailObserver < BaseObserver
2   - def after_create(email)
3   - notification.new_email(email)
4   - end
5   -end
app/observers/issue_observer.rb
... ... @@ -1,46 +0,0 @@
1   -class IssueObserver < BaseObserver
2   - def after_create(issue)
3   - notification.new_issue(issue, current_user)
4   - event_service.open_issue(issue, current_user)
5   - issue.create_cross_references!(issue.project, current_user)
6   - execute_hooks(issue)
7   - end
8   -
9   - def after_close(issue, transition)
10   - notification.close_issue(issue, current_user)
11   - event_service.close_issue(issue, current_user)
12   - create_note(issue)
13   - execute_hooks(issue)
14   - end
15   -
16   - def after_reopen(issue, transition)
17   - event_service.reopen_issue(issue, current_user)
18   - create_note(issue)
19   - execute_hooks(issue)
20   - end
21   -
22   - def after_update(issue)
23   - if issue.is_being_reassigned?
24   - notification.reassigned_issue(issue, current_user)
25   - create_assignee_note(issue)
26   - end
27   -
28   - issue.notice_added_references(issue.project, current_user)
29   - execute_hooks(issue)
30   - end
31   -
32   - protected
33   -
34   - # Create issue note with service comment like 'Status changed to closed'
35   - def create_note(issue)
36   - Note.create_status_change_note(issue, issue.project, current_user, issue.state, current_commit)
37   - end
38   -
39   - def create_assignee_note(issue)
40   - Note.create_assignee_change_note(issue, issue.project, current_user, issue.assignee)
41   - end
42   -
43   - def execute_hooks(issue)
44   - issue.project.execute_hooks(issue.to_hook_data, :issue_hooks)
45   - end
46   -end
app/observers/key_observer.rb
... ... @@ -1,19 +0,0 @@
1   -class KeyObserver < BaseObserver
2   - def after_create(key)
3   - GitlabShellWorker.perform_async(
4   - :add_key,
5   - key.shell_id,
6   - key.key
7   - )
8   -
9   - notification.new_key(key)
10   - end
11   -
12   - def after_destroy(key)
13   - GitlabShellWorker.perform_async(
14   - :remove_key,
15   - key.shell_id,
16   - key.key,
17   - )
18   - end
19   -end
app/observers/merge_request_observer.rb
... ... @@ -1,43 +0,0 @@
1   -class MergeRequestObserver < BaseObserver
2   - def after_create(merge_request)
3   - event_service.open_mr(merge_request, current_user)
4   - notification.new_merge_request(merge_request, current_user)
5   - merge_request.create_cross_references!(merge_request.project, current_user)
6   - execute_hooks(merge_request)
7   - end
8   -
9   - def after_close(merge_request, transition)
10   - event_service.close_mr(merge_request, current_user)
11   - notification.close_mr(merge_request, current_user)
12   - create_note(merge_request)
13   - execute_hooks(merge_request)
14   - end
15   -
16   - def after_reopen(merge_request, transition)
17   - event_service.reopen_mr(merge_request, current_user)
18   - create_note(merge_request)
19   - execute_hooks(merge_request)
20   - merge_request.reload_code
21   - merge_request.mark_as_unchecked
22   - end
23   -
24   - def after_update(merge_request)
25   - notification.reassigned_merge_request(merge_request, current_user) if merge_request.is_being_reassigned?
26   -
27   - merge_request.notice_added_references(merge_request.project, current_user)
28   - execute_hooks(merge_request)
29   - end
30   -
31   - private
32   -
33   - # Create merge request note with service comment like 'Status changed to closed'
34   - def create_note(merge_request)
35   - Note.create_status_change_note(merge_request, merge_request.target_project, current_user, merge_request.state, nil)
36   - end
37   -
38   - def execute_hooks(merge_request)
39   - if merge_request.project
40   - merge_request.project.execute_hooks(merge_request.to_hook_data, :merge_request_hooks)
41   - end
42   - end
43   -end
app/observers/users_project_observer.rb
... ... @@ -10,7 +10,7 @@ class UsersProjectObserver &lt; BaseObserver
10 10 end
11 11  
12 12 def after_update(users_project)
13   - notification.update_team_member(users_project)
  13 + notification.update_team_member(users_project) if users_project.project_access_changed?
14 14 end
15 15  
16 16 def after_destroy(users_project)
... ...
app/services/base_service.rb
... ... @@ -16,4 +16,16 @@ class BaseService
16 16 def can?(object, action, subject)
17 17 abilities.allowed?(object, action, subject)
18 18 end
  19 +
  20 + def notification_service
  21 + NotificationService.new
  22 + end
  23 +
  24 + def event_service
  25 + EventCreateService.new
  26 + end
  27 +
  28 + def log_info message
  29 + Gitlab::AppLogger.info message
  30 + end
19 31 end
... ...
app/services/git_push_service.rb
... ... @@ -86,10 +86,9 @@ class GitPushService
86 86 author = commit_user(commit)
87 87  
88 88 if !issues_to_close.empty? && is_default_branch
89   - Thread.current[:current_user] = author
90   - Thread.current[:current_commit] = commit
91   -
92   - issues_to_close.each { |i| i.close && i.save }
  89 + issues_to_close.each do |issue|
  90 + Issues::CloseService.new(project, author, {}).execute(issue, commit)
  91 + end
93 92 end
94 93  
95 94 # Create cross-reference notes for any other references. Omit any issues that were referenced in an
... ...
app/services/issues/base_service.rb 0 → 100644
... ... @@ -0,0 +1,14 @@
  1 +module Issues
  2 + class BaseService < ::BaseService
  3 +
  4 + private
  5 +
  6 + def create_assignee_note(issue)
  7 + Note.create_assignee_change_note(issue, issue.project, current_user, issue.assignee)
  8 + end
  9 +
  10 + def execute_hooks(issue)
  11 + issue.project.execute_hooks(issue.to_hook_data, :issue_hooks)
  12 + end
  13 + end
  14 +end
... ...
app/services/issues/close_service.rb 0 → 100644
... ... @@ -0,0 +1,20 @@
  1 +module Issues
  2 + class CloseService < Issues::BaseService
  3 + def execute(issue, commit = nil)
  4 + if issue.close
  5 + notification_service.close_issue(issue, current_user)
  6 + event_service.close_issue(issue, current_user)
  7 + create_note(issue, commit)
  8 + execute_hooks(issue)
  9 + end
  10 +
  11 + issue
  12 + end
  13 +
  14 + private
  15 +
  16 + def create_note(issue, current_commit)
  17 + Note.create_status_change_note(issue, issue.project, current_user, issue.state, current_commit)
  18 + end
  19 + end
  20 +end
... ...
app/services/issues/create_service.rb 0 → 100644
... ... @@ -0,0 +1,17 @@
  1 +module Issues
  2 + class CreateService < Issues::BaseService
  3 + def execute
  4 + issue = project.issues.new(params)
  5 + issue.author = current_user
  6 +
  7 + if issue.save
  8 + notification_service.new_issue(issue, current_user)
  9 + event_service.open_issue(issue, current_user)
  10 + issue.create_cross_references!(issue.project, current_user)
  11 + execute_hooks(issue)
  12 + end
  13 +
  14 + issue
  15 + end
  16 + end
  17 +end
... ...
app/services/issues/reopen_service.rb 0 → 100644
... ... @@ -0,0 +1,19 @@
  1 +module Issues
  2 + class ReopenService < Issues::BaseService
  3 + def execute(issue)
  4 + if issue.reopen
  5 + event_service.reopen_issue(issue, current_user)
  6 + create_note(issue)
  7 + execute_hooks(issue)
  8 + end
  9 +
  10 + issue
  11 + end
  12 +
  13 + private
  14 +
  15 + def create_note(issue)
  16 + Note.create_status_change_note(issue, issue.project, current_user, issue.state, nil)
  17 + end
  18 + end
  19 +end
... ...
app/services/issues/update_service.rb 0 → 100644
... ... @@ -0,0 +1,28 @@
  1 +module Issues
  2 + class UpdateService < Issues::BaseService
  3 + def execute(issue)
  4 + state = params.delete('state_event')
  5 +
  6 + case state
  7 + when 'reopen'
  8 + Issues::ReopenService.new(project, current_user, {}).execute(issue)
  9 + when 'close'
  10 + Issues::CloseService.new(project, current_user, {}).execute(issue)
  11 + end
  12 +
  13 + if params.present? && issue.update_attributes(params)
  14 + issue.reset_events_cache
  15 +
  16 + if issue.previous_changes.include?('assignee_id')
  17 + notification_service.reassigned_issue(issue, current_user)
  18 + create_assignee_note(issue)
  19 + end
  20 +
  21 + issue.notice_added_references(issue.project, current_user)
  22 + execute_hooks(issue)
  23 + end
  24 +
  25 + issue
  26 + end
  27 + end
  28 +end
... ...
app/services/merge_requests/base_service.rb 0 → 100644
... ... @@ -0,0 +1,20 @@
  1 +module MergeRequests
  2 + class BaseService < ::BaseService
  3 +
  4 + private
  5 +
  6 + def create_assignee_note(merge_request)
  7 + Note.create_assignee_change_note(merge_request, merge_request.project, current_user, merge_request.assignee)
  8 + end
  9 +
  10 + def create_note(merge_request)
  11 + Note.create_status_change_note(merge_request, merge_request.target_project, current_user, merge_request.state, nil)
  12 + end
  13 +
  14 + def execute_hooks(merge_request)
  15 + if merge_request.project
  16 + merge_request.project.execute_hooks(merge_request.to_hook_data, :merge_request_hooks)
  17 + end
  18 + end
  19 + end
  20 +end
... ...
app/services/merge_requests/close_service.rb 0 → 100644
... ... @@ -0,0 +1,18 @@
  1 +module MergeRequests
  2 + class CloseService < MergeRequests::BaseService
  3 + def execute(merge_request, commit = nil)
  4 + # If we close MergeRequest we want to ignore validation
  5 + # so we can close broken one (Ex. fork project removed)
  6 + merge_request.allow_broken = true
  7 +
  8 + if merge_request.close
  9 + event_service.close_mr(merge_request, current_user)
  10 + notification_service.close_mr(merge_request, current_user)
  11 + create_note(merge_request)
  12 + execute_hooks(merge_request)
  13 + end
  14 +
  15 + merge_request
  16 + end
  17 + end
  18 +end
... ...
app/services/merge_requests/create_service.rb 0 → 100644
... ... @@ -0,0 +1,19 @@
  1 +module MergeRequests
  2 + class CreateService < MergeRequests::BaseService
  3 + def execute
  4 + merge_request = MergeRequest.new(params)
  5 + merge_request.source_project = project
  6 + merge_request.target_project ||= project
  7 + merge_request.author = current_user
  8 +
  9 + if merge_request.save
  10 + event_service.open_mr(merge_request, current_user)
  11 + notification_service.new_merge_request(merge_request, current_user)
  12 + merge_request.create_cross_references!(merge_request.project, current_user)
  13 + execute_hooks(merge_request)
  14 + end
  15 +
  16 + merge_request
  17 + end
  18 + end
  19 +end
... ...
app/services/merge_requests/reopen_service.rb 0 → 100644
... ... @@ -0,0 +1,15 @@
  1 +module MergeRequests
  2 + class ReopenService < MergeRequests::BaseService
  3 + def execute(merge_request)
  4 + if merge_request.reopen
  5 + event_service.reopen_mr(merge_request, current_user)
  6 + create_note(merge_request)
  7 + execute_hooks(merge_request)
  8 + merge_request.reload_code
  9 + merge_request.mark_as_unchecked
  10 + end
  11 +
  12 + merge_request
  13 + end
  14 + end
  15 +end
... ...
app/services/merge_requests/update_service.rb 0 → 100644
... ... @@ -0,0 +1,37 @@
  1 +require_relative 'base_service'
  2 +require_relative 'reopen_service'
  3 +require_relative 'close_service'
  4 +
  5 +module MergeRequests
  6 + class UpdateService < MergeRequests::BaseService
  7 + def execute(merge_request)
  8 + # We dont allow change of source/target projects
  9 + # after merge request was created
  10 + params.delete(:source_project_id)
  11 + params.delete(:target_project_id)
  12 +
  13 + state = params.delete('state_event')
  14 +
  15 + case state
  16 + when 'reopen'
  17 + MergeRequests::ReopenService.new(project, current_user, {}).execute(merge_request)
  18 + when 'close'
  19 + MergeRequests::CloseService.new(project, current_user, {}).execute(merge_request)
  20 + end
  21 +
  22 + if params.present? && merge_request.update_attributes(params)
  23 + merge_request.reset_events_cache
  24 +
  25 + if merge_request.previous_changes.include?('assignee_id')
  26 + notification_service.reassigned_merge_request(merge_request, current_user)
  27 + create_assignee_note(merge_request)
  28 + end
  29 +
  30 + merge_request.notice_added_references(merge_request.project, current_user)
  31 + execute_hooks(merge_request)
  32 + end
  33 +
  34 + merge_request
  35 + end
  36 + end
  37 +end
... ...
app/services/notification_service.rb
... ... @@ -178,29 +178,29 @@ class NotificationService
178 178  
179 179 # Get project users with WATCH notification level
180 180 def project_watchers(project)
181   - # Gather all user ids that have WATCH notification setting for project
182   - project_notification_uids = project_notification_list(project, Notification::N_WATCH)
  181 + project_members = users_project_notification(project)
183 182  
184   - # Gather all user ids that have WATCH notification setting for group
185   - group_notification_uids = group_notification_list(project, Notification::N_WATCH)
  183 + users_with_project_level_global = users_project_notification(project, Notification::N_GLOBAL)
  184 + users_with_group_level_global = users_group_notification(project, Notification::N_GLOBAL)
  185 + users = users_with_global_level_watch([users_with_project_level_global, users_with_group_level_global].flatten.uniq)
186 186  
187   - # Gather all user ids that have GLOBAL setting
188   - global_notification_uids = global_notification_list(project)
  187 + users_with_project_setting = select_users_project_setting(project, users_with_project_level_global, users)
  188 + users_with_group_setting = select_users_group_setting(project, project_members, users_with_group_level_global, users)
189 189  
190   - project_and_group_uids = [project_notification_uids, group_notification_uids].flatten.uniq
191   - group_and_project_watchers = User.where(id: project_and_group_uids)
192   -
193   - # Find all users that have WATCH as their GLOBAL setting
194   - global_watchers = User.where(id: global_notification_uids, notification_level: Notification::N_WATCH)
195   -
196   - [group_and_project_watchers, global_watchers].flatten.uniq
  190 + User.where(id: users_with_project_setting.concat(users_with_group_setting).uniq).to_a
197 191 end
198 192  
199   - def project_notification_list(project, notification_level)
200   - project.users_projects.where(notification_level: notification_level).pluck(:user_id)
  193 + def users_project_notification(project, notification_level=nil)
  194 + project_members = project.users_projects
  195 +
  196 + if notification_level
  197 + project_members.where(notification_level: notification_level).pluck(:user_id)
  198 + else
  199 + project_members.pluck(:user_id)
  200 + end
201 201 end
202 202  
203   - def group_notification_list(project, notification_level)
  203 + def users_group_notification(project, notification_level)
204 204 if project.group
205 205 project.group.users_groups.where(notification_level: notification_level).pluck(:user_id)
206 206 else
... ... @@ -208,11 +208,47 @@ class NotificationService
208 208 end
209 209 end
210 210  
211   - def global_notification_list(project)
212   - [
213   - project_notification_list(project, Notification::N_GLOBAL),
214   - group_notification_list(project, Notification::N_GLOBAL)
215   - ].flatten
  211 + def users_with_global_level_watch(ids)
  212 + User.where(
  213 + id: ids,
  214 + notification_level: Notification::N_WATCH
  215 + ).pluck(:id)
  216 + end
  217 +
  218 + # Build a list of users based on project notifcation settings
  219 + def select_users_project_setting(project, global_setting, users_global_level_watch)
  220 + users = users_project_notification(project, Notification::N_WATCH)
  221 +
  222 + # If project setting is global, add to watch list if global setting is watch
  223 + global_setting.each do |user_id|
  224 + if users_global_level_watch.include?(user_id)
  225 + users << user_id
  226 + end
  227 + end
  228 +
  229 + users
  230 + end
  231 +
  232 + # Build a list of users based on group notifcation settings
  233 + def select_users_group_setting(project, project_members, global_setting, users_global_level_watch)
  234 + uids = users_group_notification(project, Notification::N_WATCH)
  235 +
  236 + # Group setting is watch, add to users list if user is not project member
  237 + users = []
  238 + uids.each do |user_id|
  239 + if project_members.exclude?(user_id)
  240 + users << user_id
  241 + end
  242 + end
  243 +
  244 + # Group setting is global, add to users list if global setting is watch
  245 + global_setting.each do |user_id|
  246 + if project_members.exclude?(user_id) && users_global_level_watch.include?(user_id)
  247 + users << user_id
  248 + end
  249 + end
  250 +
  251 + users
216 252 end
217 253  
218 254 # Remove users with disabled notifications from array
... ...
app/views/projects/issues/_head.html.haml
... ... @@ -20,6 +20,11 @@
20 20 = form_tag project_issues_path(@project), method: :get, id: "issue_search_form", class: 'pull-left issue-search-form' do
21 21 .append-right-10.hidden-xs.hidden-sm
22 22 = search_field_tag :issue_search, nil, { placeholder: 'Filter by title or description', class: 'form-control issue_search search-text-input input-mn-300' }
  23 + = hidden_field_tag :state, params['state']
  24 + = hidden_field_tag :scope, params['scope']
  25 + = hidden_field_tag :assignee_id, params['assignee_id']
  26 + = hidden_field_tag :milestone_id, params['milestone_id']
  27 + = hidden_field_tag :label_id, params['label_id']
23 28 - if can? current_user, :write_issue, @project
24 29 = 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
25 30 %i.icon-plus
... ...
app/views/projects/merge_requests/_form.html.haml
... ... @@ -33,7 +33,7 @@
33 33 .col-sm-10
34 34 .clearfix
35 35 .pull-left
36   - - projects = @project.forked_from_project.nil? ? [@project] : [ @project,@project.forked_from_project]
  36 + - projects = @project.forked_from_project.nil? ? [@project] : [@project, @project.forked_from_project]
37 37 = 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? })
38 38 .pull-left
39 39 &nbsp;
... ...
app/views/projects/merge_requests/branch_from.js.haml
... ... @@ -3,5 +3,5 @@
3 3 var mrTitle = $('#merge_request_title');
4 4  
5 5 if(mrTitle.val().length == 0) {
6   - mrTitle.val("#{params[:ref].humanize}");
  6 + mrTitle.val("#{params[:ref].titleize.humanize}");
7 7 }
... ...
app/views/projects/protected_branches/index.html.haml
... ... @@ -9,6 +9,7 @@
9 9 %ul
10 10 %li keep stable branches secured
11 11 %li forced code review before merge to protected branches
  12 + %li prevents branch from force push
12 13 %p Read more about project permissions #{link_to "here", help_permissions_path, class: "underlined-link"}
13 14  
14 15 - if can? current_user, :admin_project, @project
... ...
config/application.rb
... ... @@ -21,9 +21,6 @@ module Gitlab
21 21 # Activate observers that should always be running.
22 22 config.active_record.observers = :milestone_observer,
23 23 :project_activity_cache_observer,
24   - :issue_observer,
25   - :key_observer,
26   - :merge_request_observer,
27 24 :note_observer,
28 25 :project_observer,
29 26 :system_hook_observer,
... ... @@ -64,6 +61,7 @@ module Gitlab
64 61 config.assets.enabled = true
65 62 config.assets.paths << Emoji.images_path
66 63 config.assets.precompile << "emoji/*.png"
  64 + config.assets.precompile << "print.css"
67 65  
68 66 # Version of your assets, change this if you want to expire all your assets
69 67 config.assets.version = '1.0'
... ...
doc/README.md
1   -## The GitLab Documentation covers the following subjects
  1 +**User documentation**
2 2  
3   -+ [API](api/README.md)
4   -+ [Development](development/README.md)
5   -+ [Install](install/README.md)
6   -+ [Integration](integration/external-issue-tracker.md)
7   -+ [Legal](legal/README.md)
8   -+ [Markdown](markdown/markdown.md)
9   -+ [Permissions](permissions/permissions.md)
10   -+ [Public access](public_access/public_access.md)
11   -+ [Raketasks](raketasks/README.md)
12   -+ [Release](release/README.md)
13   -+ [Security](security/README.md)
14   -+ [SSH](ssh/README.md)
15   -+ [System hooks](system_hooks/system_hooks.md)
16   -+ [Update](update/README.md)
17   -+ [Web hooks](web_hooks/web_hooks.md)
18   -+ [Workflow](workflow/workflow.md)
  3 ++ [API](api/README.md) Explore how you can access GitLab via a simple and powerful API.
  4 ++ [Markdown](markdown/markdown.md) Learn what you can do with GitLab's advanced formatting system.
  5 ++ [Permissions](permissions/permissions.md) Learn what each role in a project (guest/reporter/developer/master/owner) can do.
  6 ++ [Public access](public_access/public_access.md) Learn how you can allow public and internal access to a project.
  7 ++ [SSH](ssh/README.md) Setup your ssh keys and deploy keys for secure access to your projects.
  8 ++ [Web hooks](web_hooks/web_hooks.md) Let GitLab notify you when new code has been pushed to your project.
  9 ++ [Workflow](workflow/workflow.md) Learn how to use Git and GitLab together.
  10 +
  11 +**Administrator documentation**
  12 +
  13 ++ [Install](install/README.md) Requirements, directory structures and manual installation.
  14 ++ [Integration](integration/external-issue-tracker.md) How to integrate JIRA and Redmine.
  15 ++ [Raketasks](raketasks/README.md) Explore what GitLab has in store for you to make administration easier.
  16 ++ [System hooks](system_hooks/system_hooks.md) Let GitLab notify you when certain management tasks need to be carried out.
  17 ++ [Security](security/README.md) Learn what you can do to further secure your GitLab instance.
  18 ++ [Update](update/README.md) Update guides to upgrade your installation.
  19 +
  20 +**Contributor documentation**
  21 +
  22 ++ [Development](development/README.md) Explains the architecture and the guidelines for shell commands.
  23 ++ [Legal](legal/README.md) Contributor license agreements.
  24 ++ [Release](release/README.md) How to make the monthly and security releases.
... ...
doc/install/installation.md
... ... @@ -93,7 +93,7 @@ Then select &#39;Internet Site&#39; and press enter to confirm the hostname.
93 93  
94 94 # 2. Ruby
95 95  
96   -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.
  96 +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.
97 97  
98 98 Remove the old Ruby 1.8 if present
99 99  
... ... @@ -202,6 +202,7 @@ You can change `6-6-stable` to `master` if you want the *bleeding edge* version,
202 202  
203 203 # Create directory for satellites
204 204 sudo -u git -H mkdir /home/git/gitlab-satellites
  205 + sudo chmod u+rwx,g+rx,o-rwx /home/git/gitlab-satellites
205 206  
206 207 # Create directories for sockets/pids and make sure GitLab can write to them
207 208 sudo -u git -H mkdir tmp/pids/
... ...
doc/integration/external-issue-tracker.md
... ... @@ -2,7 +2,7 @@ GitLab has a great issue tracker but you can also use an external issue tracker
2 2  
3 3 - the 'Issues' link on the GitLab project pages takes you to the appropriate JIRA issue index;
4 4 - clicking 'New issue' on the project dashboard creates a new JIRA issue;
5   -- textual references to PROJECT-1234 in comments, commit messages get turned into HTML links to the corresponding JIRA issue.
  5 +- To reference JIRA issue PROJECT-1234 in comments, use syntax #PROJECT-1234. Commit messages get turned into HTML links to the corresponding JIRA issue.
6 6  
7 7 ![jira screenshot](jira-intergration-points.png)
8 8  
... ...
doc/integration/ldap.md 0 → 100644
... ... @@ -0,0 +1,14 @@
  1 +# GitLab LDAP integration
  2 +
  3 +GitLab can be configured to allow your users to sign with their LDAP credentials to integrate with e.g. Active Directory.
  4 +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.
  5 +GitLab user attributes such as nickname and email will be copied from the LDAP user entry.
  6 +
  7 +## Enabling LDAP sign-in for existing GitLab users
  8 +
  9 +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.
  10 +If the LDAP email attribute is not found in GitLab's database, a new user is created.
  11 +
  12 +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.
  13 +GitLab recognizes the following LDAP attributes as email addresses: `mail`, `email` and `userPrincipalName`.
  14 +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`.
... ...
doc/release/monthly.md
... ... @@ -61,9 +61,9 @@ After making the release branch new commits are cherry-picked from master. When
61 61 * 1-7th: official merge window (see contributing guide)
62 62 * 8-14th: work on bugfixes, sponsored features and GitLab EE
63 63 * 15th: code freeze (stop merging into master except essential bugfixes)
64   -* 18th: release candidate 1 (VERSION x.x.0.rc1, tag and tweet about x.x.0.rc1, release on GitLab Cloud)
  64 +* 18th: release candidate 1 (VERSION x.x.0.rc1, annotated tag and tweet about x.x.0.rc1, release on GitLab Cloud)
65 65 * 20st: optional release candidate 2 (x.x.0.rc2, only if rc1 had problems)
66   -* 22nd: release (VERSION x.x.0, create x-x-stable branch, tag, blog and tweet)
  66 +* 22nd: release (VERSION x.x.0, create x-x-stable branch, annotated tag tag, blog and tweet)
67 67 * 23nd: optional patch releases (x.x.1, x.x.2, etc., only if there are serious problems)
68 68 * 24-end of month: release GitLab EE and GitLab CI
69 69  
... ...
doc/release/security.md
... ... @@ -18,7 +18,7 @@ Please report suspected security vulnerabilities in private to support@gitlab.co
18 18 1. Create feature branches for the blog post on GitLab.com and link them from the code branch
19 19 1. Merge the code feature branch into master
20 20 1. Cherry-pick the code into the latest stable branch
21   -1. Create a git tag vX.X.X for CE and another patch release for EE
  21 +1. Create an annotated tag vX.X.X for CE and another patch release for EE
22 22 1. Push the code and the tags to all the CE and EE repositories
23 23 1. Apply the patch to GitLab Cloud and the private GitLab development server
24 24 1. Merge and publish the blog posts
... ...
doc/update/6.0-to-6.7.md
... ... @@ -80,6 +80,9 @@ sudo -u git -H bundle exec rake migrate_iids RAILS_ENV=production
80 80  
81 81 # Clean up assets and cache
82 82 sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
  83 +
  84 +# Close access to gitlab-satellites for others
  85 +sudo chmod u+rwx,g+rx,o-rwx /home/git/gitlab-satellites
83 86 ```
84 87  
85 88 ### 6. Update config files
... ...
doc/update/6.6-to-6.7.md
... ... @@ -63,6 +63,9 @@ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
63 63  
64 64 # Update the logrotate configuration (keep logs for 90 days instead of 52 weeks)
65 65 sudo cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab
  66 +
  67 +# Close access to gitlab-satellites for others
  68 +sudo chmod u+rwx,g+rx,o-rwx /home/git/gitlab-satellites
66 69 ```
67 70  
68 71  
... ...
doc/update/upgrader.md
... ... @@ -40,3 +40,10 @@ To make sure you didn&#39;t miss anything run a more thorough check with:
40 40 sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
41 41  
42 42 If all items are green, then congratulations upgrade is complete!
  43 +
  44 +
  45 +### One line upgrade command
  46 +
  47 +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:
  48 +
  49 + 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
... ...
doc/workflow/README.md 0 → 100644
... ... @@ -0,0 +1,2 @@
  1 ++ [Workflow](workflow/workflow.md)
  2 ++ [Project Features](workflow/project_features.md)
... ...
doc/workflow/project_features.md 0 → 100644
... ... @@ -0,0 +1,35 @@
  1 +When in a Project -> Settings, you will find Features on the bottom of the page that you can toggle.
  2 +Below you will find a more elaborate explanation of each of these.
  3 +
  4 +
  5 +## Issues
  6 +
  7 +Issues is a really powerful, but lightweight issue tracking system.
  8 +You can make tickets, assign them to people, file them under milestones, order them with labels and have discussion in them.
  9 +They integrate deeply into GitLab and are easily referenced from anywhere by using # and the issuenumber.
  10 +At GitLab.com, we use this for all our project management needs.
  11 +
  12 +## Merge Requests
  13 +
  14 +Using a merge request, you can review and discuss code before it is merged in the branch of your code.
  15 +As with issues, it can be assigned; people, issues, etc. can be refereced; milestones attached.
  16 +We see it as an integral part of working together on code and couldn't work without it.
  17 +
  18 +
  19 +## Wiki
  20 +
  21 +This is a separate system for documentation, built right into GitLab.
  22 +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.
  23 +
  24 +
  25 +## Wall
  26 +
  27 +For simple, project specific conversations, the wall can be used.
  28 +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.
  29 +
  30 +
  31 +## Snippets
  32 +
  33 +Snippets are little bits of code or text.
  34 +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.
  35 +For example, a specific config file that is used by > the team that is only valid for the people that work on the code.
... ...
features/steps/dashboard/merge_requests.rb
... ... @@ -53,15 +53,15 @@ class DashboardMergeRequests &lt; Spinach::FeatureSteps
53 53 end
54 54  
55 55 def assigned_merge_request
56   - @assigned_merge_request ||= create :merge_request, assignee: current_user, target_project: project
  56 + @assigned_merge_request ||= create :merge_request, assignee: current_user, target_project: project, source_project: project
57 57 end
58 58  
59 59 def authored_merge_request
60   - @authored_merge_request ||= create :merge_request, author: current_user, target_project: project
  60 + @authored_merge_request ||= create :merge_request, source_branch: 'simple_merge_request', author: current_user, target_project: project, source_project: project
61 61 end
62 62  
63 63 def other_merge_request
64   - @other_merge_request ||= create :merge_request, target_project: project
  64 + @other_merge_request ||= create :merge_request, source_branch: '2_3_notes_fix', target_project: project, source_project: project
65 65 end
66 66  
67 67 def project
... ...
features/support/env.rb
... ... @@ -52,6 +52,4 @@ Spinach.hooks.before_run do
52 52 RSpec::Mocks::setup self
53 53  
54 54 include FactoryGirl::Syntax::Methods
55   - MergeRequestObserver.any_instance.stub(current_user: create(:user))
56 55 end
57   -
... ...
lib/api/internal.rb
... ... @@ -10,6 +10,7 @@ module API
10 10 # project - project path with namespace
11 11 # action - git action (git-upload-pack or git-receive-pack)
12 12 # ref - branch name
  13 + # forced_push - forced_push
13 14 #
14 15 get "/allowed" do
15 16 # Check for *.wiki repositories.
... ... @@ -35,7 +36,8 @@ module API
35 36 project,
36 37 params[:ref],
37 38 params[:oldrev],
38   - params[:newrev]
  39 + params[:newrev],
  40 + params[:forced_push]
39 41 )
40 42 end
41 43  
... ...
lib/api/issues.rb
... ... @@ -48,17 +48,15 @@ module API
48 48 # Example Request:
49 49 # POST /projects/:id/issues
50 50 post ":id/issues" do
51   - set_current_user_for_thread do
52   - required_attributes! [:title]
53   - attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id]
54   - attrs[:label_list] = params[:labels] if params[:labels].present?
55   - @issue = user_project.issues.new attrs
56   - @issue.author = current_user
57   - if @issue.save
58   - present @issue, with: Entities::Issue
59   - else
60   - not_found!
61   - end
  51 + required_attributes! [:title]
  52 + attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id]
  53 + attrs[:label_list] = params[:labels] if params[:labels].present?
  54 + issue = ::Issues::CreateService.new(user_project, current_user, attrs).execute
  55 +
  56 + if issue.valid?
  57 + present issue, with: Entities::Issue
  58 + else
  59 + not_found!
62 60 end
63 61 end
64 62  
... ... @@ -76,18 +74,18 @@ module API
76 74 # Example Request:
77 75 # PUT /projects/:id/issues/:issue_id
78 76 put ":id/issues/:issue_id" do
79   - set_current_user_for_thread do
80   - @issue = user_project.issues.find(params[:issue_id])
81   - authorize! :modify_issue, @issue
  77 + issue = user_project.issues.find(params[:issue_id])
  78 + authorize! :modify_issue, issue
  79 +
  80 + attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id, :state_event]
  81 + attrs[:label_list] = params[:labels] if params[:labels].present?
82 82  
83   - attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id, :state_event]
84   - attrs[:label_list] = params[:labels] if params[:labels].present?
  83 + issue = ::Issues::UpdateService.new(user_project, current_user, attrs).execute(issue)
85 84  
86   - if @issue.update_attributes attrs
87   - present @issue, with: Entities::Issue
88   - else
89   - not_found!
90   - end
  85 + if issue.valid?
  86 + present issue, with: Entities::Issue
  87 + else
  88 + not_found!
91 89 end
92 90 end
93 91  
... ...
lib/api/merge_requests.rb
... ... @@ -13,14 +13,6 @@ module API
13 13 end
14 14 not_found!
15 15 end
16   -
17   - def not_fork?(target_project_id, user_project)
18   - target_project_id.nil? || target_project_id == user_project.id.to_s
19   - end
20   -
21   - def target_matches_fork(target_project_id,user_project)
22   - user_project.forked? && user_project.forked_from_project.id.to_s == target_project_id
23   - end
24 16 end
25 17  
26 18 # List merge requests
... ... @@ -70,29 +62,15 @@ module API
70 62 # POST /projects/:id/merge_requests
71 63 #
72 64 post ":id/merge_requests" do
73   - set_current_user_for_thread do
74   - authorize! :write_merge_request, user_project
75   - required_attributes! [:source_branch, :target_branch, :title]
76   - attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :target_project_id, :description]
77   - merge_request = user_project.merge_requests.new(attrs)
78   - merge_request.author = current_user
79   - merge_request.source_project = user_project
80   - target_project_id = attrs[:target_project_id]
81   - if not_fork?(target_project_id, user_project)
82   - merge_request.target_project = user_project
83   - else
84   - if target_matches_fork(target_project_id,user_project)
85   - merge_request.target_project = Project.find_by(id: attrs[:target_project_id])
86   - else
87   - render_api_error!('(Bad Request) Specified target project that is not the source project, or the source fork of the project.', 400)
88   - end
89   - end
90   -
91   - if merge_request.save
92   - present merge_request, with: Entities::MergeRequest
93   - else
94   - handle_merge_request_errors! merge_request.errors
95   - end
  65 + authorize! :write_merge_request, user_project
  66 + required_attributes! [:source_branch, :target_branch, :title]
  67 + attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :target_project_id, :description]
  68 + merge_request = ::MergeRequests::CreateService.new(user_project, current_user, attrs).execute
  69 +
  70 + if merge_request.valid?
  71 + present merge_request, with: Entities::MergeRequest
  72 + else
  73 + handle_merge_request_errors! merge_request.errors
96 74 end
97 75 end
98 76  
... ... @@ -111,17 +89,15 @@ module API
111 89 # PUT /projects/:id/merge_request/:merge_request_id
112 90 #
113 91 put ":id/merge_request/:merge_request_id" do
114   - set_current_user_for_thread do
115   - attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :state_event, :description]
116   - merge_request = user_project.merge_requests.find(params[:merge_request_id])
117   -
118   - authorize! :modify_merge_request, merge_request
  92 + attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :state_event, :description]
  93 + merge_request = user_project.merge_requests.find(params[:merge_request_id])
  94 + authorize! :modify_merge_request, merge_request
  95 + merge_request = ::MergeRequests::UpdateService.new(user_project, current_user, attrs).execute(merge_request)
119 96  
120   - if merge_request.update_attributes attrs
121   - present merge_request, with: Entities::MergeRequest
122   - else
123   - handle_merge_request_errors! merge_request.errors
124   - end
  97 + if merge_request.valid?
  98 + present merge_request, with: Entities::MergeRequest
  99 + else
  100 + handle_merge_request_errors! merge_request.errors
125 101 end
126 102 end
127 103  
... ...
lib/gitlab/git_access.rb
... ... @@ -5,7 +5,7 @@ module Gitlab
5 5  
6 6 attr_reader :params, :project, :git_cmd, :user
7 7  
8   - def allowed?(actor, cmd, project, ref = nil, oldrev = nil, newrev = nil)
  8 + def allowed?(actor, cmd, project, ref = nil, oldrev = nil, newrev = nil, forced_push = false)
9 9 case cmd
10 10 when *DOWNLOAD_COMMANDS
11 11 if actor.is_a? User
... ... @@ -19,12 +19,12 @@ module Gitlab
19 19 end
20 20 when *PUSH_COMMANDS
21 21 if actor.is_a? User
22   - push_allowed?(actor, project, ref, oldrev, newrev)
  22 + push_allowed?(actor, project, ref, oldrev, newrev, forced_push)
23 23 elsif actor.is_a? DeployKey
24 24 # Deploy key not allowed to push
25 25 return false
26 26 elsif actor.is_a? Key
27   - push_allowed?(actor.user, project, ref, oldrev, newrev)
  27 + push_allowed?(actor.user, project, ref, oldrev, newrev, forced_push)
28 28 else
29 29 raise 'Wrong actor'
30 30 end
... ... @@ -41,13 +41,17 @@ module Gitlab
41 41 end
42 42 end
43 43  
44   - def push_allowed?(user, project, ref, oldrev, newrev)
  44 + def push_allowed?(user, project, ref, oldrev, newrev, forced_push)
45 45 if user && user_allowed?(user)
46 46 action = if project.protected_branch?(ref)
47   - :push_code_to_protected_branches
48   - else
49   - :push_code
50   - end
  47 + if forced_push.to_s == 'true'
  48 + :force_push_code_to_protected_branches
  49 + else
  50 + :push_code_to_protected_branches
  51 + end
  52 + else
  53 + :push_code
  54 + end
51 55 user.can?(action, project)
52 56 else
53 57 false
... ...
lib/tasks/gitlab/check.rake
... ... @@ -342,6 +342,7 @@ namespace :gitlab do
342 342 check_repo_base_is_not_symlink
343 343 check_repo_base_user_and_group
344 344 check_repo_base_permissions
  345 + check_satellites_permissions
345 346 check_update_hook_is_up_to_date
346 347 check_repos_update_hooks_is_link
347 348 check_gitlab_shell_self_test
... ... @@ -443,6 +444,29 @@ namespace :gitlab do
443 444 end
444 445 end
445 446  
  447 + def check_satellites_permissions
  448 + print "Satellites access is drwxr-x---? ... "
  449 +
  450 + satellites_path = Gitlab.config.satellites.path
  451 + unless File.exists?(satellites_path)
  452 + puts "can't check because of previous errors".magenta
  453 + return
  454 + end
  455 +
  456 + if File.stat(satellites_path).mode.to_s(8).ends_with?("0750")
  457 + puts "yes".green
  458 + else
  459 + puts "no".red
  460 + try_fixing_it(
  461 + "sudo chmod u+rwx,g+rx,o-rwx #{satellites_path}",
  462 + )
  463 + for_more_information(
  464 + see_installation_guide_section "GitLab"
  465 + )
  466 + fix_and_rerun
  467 + end
  468 + end
  469 +
446 470 def check_repo_base_user_and_group
447 471 gitlab_shell_ssh_user = Gitlab.config.gitlab_shell.ssh_user
448 472 gitlab_shell_owner_group = Gitlab.config.gitlab_shell.owner_group
... ...
lib/tasks/gitlab/info.rake
... ... @@ -24,6 +24,7 @@ namespace :gitlab do
24 24 puts "Gem Version:\t#{gem_version || "unknown".red}"
25 25 puts "Bundler Version:#{bunder_version || "unknown".red}"
26 26 puts "Rake Version:\t#{rake_version || "unknown".red}"
  27 + puts "Sidekiq Version:#{Sidekiq::VERSION}"
27 28  
28 29  
29 30 # check database adapter
... ...
script/background_jobs
... ... @@ -37,7 +37,7 @@ function start_no_deamonize
37 37  
38 38 function start_sidekiq
39 39 {
40   - 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
  40 + 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
41 41 }
42 42  
43 43 function load_ok
... ...
spec/finders/merge_requests_finder_spec.rb
... ... @@ -5,10 +5,10 @@ describe MergeRequestsFinder do
5 5 let(:user2) { create :user }
6 6  
7 7 let(:project1) { create(:project) }
8   - let(:project2) { create(:project) }
  8 + let(:project2) { create(:project, forked_from_project: project1) }
9 9  
10   - let!(:merge_request1) { create(:merge_request, :simple, author: user, source_project: project1, target_project: project2) }
11   - let!(:merge_request2) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project1) }
  10 + let!(:merge_request1) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project1) }
  11 + let!(:merge_request2) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project1, state: 'closed') }
12 12 let!(:merge_request3) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project2) }
13 13  
14 14 before do
... ... @@ -21,7 +21,7 @@ describe MergeRequestsFinder do
21 21 it 'should filter by scope' do
22 22 params = { scope: 'authored', state: 'opened' }
23 23 merge_requests = MergeRequestsFinder.new.execute(user, params)
24   - merge_requests.size.should == 3
  24 + merge_requests.size.should == 2
25 25 end
26 26  
27 27 it 'should filter by project' do
... ...
spec/lib/gitlab/satellite/merge_action_spec.rb
... ... @@ -13,7 +13,7 @@ describe &#39;Gitlab::Satellite::MergeAction&#39; do
13 13 end
14 14  
15 15 let(:project) { create(:project, namespace: create(:group)) }
16   - let(:fork_project) { create(:project, namespace: create(:group)) }
  16 + let(:fork_project) { create(:project, namespace: create(:group), forked_from_project: project) }
17 17 let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
18 18 let(:merge_request_fork) { create(:merge_request, source_project: fork_project, target_project: project) }
19 19  
... ...
spec/models/key_spec.rb
... ... @@ -68,4 +68,18 @@ describe Key do
68 68 build(:invalid_key).should_not be_valid
69 69 end
70 70 end
  71 +
  72 + context 'callbacks' do
  73 + it 'should add new key to authorized_file' do
  74 + @key = build(:personal_key, id: 7)
  75 + GitlabShellWorker.should_receive(:perform_async).with(:add_key, @key.shell_id, @key.key)
  76 + @key.save
  77 + end
  78 +
  79 + it 'should remove key from authorized_file' do
  80 + @key = create(:personal_key)
  81 + GitlabShellWorker.should_receive(:perform_async).with(:remove_key, @key.shell_id, @key.key)
  82 + @key.destroy
  83 + end
  84 + end
71 85 end
... ...
spec/models/note_spec.rb
... ... @@ -209,7 +209,7 @@ describe Note do
209 209 let(:project) { create(:project) }
210 210 let(:author) { create(:user) }
211 211 let(:issue) { create(:issue, project: project) }
212   - let(:mergereq) { create(:merge_request, target_project: project) }
  212 + let(:mergereq) { create(:merge_request, :simple, target_project: project, source_project: project) }
213 213 let(:commit) { project.repository.commit }
214 214  
215 215 # Test all of {issue, merge request, commit} in both the referenced and referencing
... ...
spec/observers/email_observer_spec.rb
... ... @@ -1,17 +0,0 @@
1   -require 'spec_helper'
2   -
3   -describe EmailObserver do
4   - let(:email) { create(:email) }
5   -
6   - before { subject.stub(notification: double('NotificationService').as_null_object) }
7   -
8   - subject { EmailObserver.instance }
9   -
10   - describe '#after_create' do
11   - it 'trigger notification to send emails' do
12   - subject.should_receive(:notification)
13   -
14   - subject.after_create(email)
15   - end
16   - end
17   -end
spec/observers/issue_observer_spec.rb
... ... @@ -1,99 +0,0 @@
1   -require 'spec_helper'
2   -
3   -describe IssueObserver do
4   - let(:some_user) { create :user }
5   - let(:assignee) { create :user }
6   - let(:author) { create :user }
7   - let(:mock_issue) { create(:issue, assignee: assignee, author: author) }
8   -
9   -
10   - before { subject.stub(:current_user).and_return(some_user) }
11   - before { subject.stub(:current_commit).and_return(nil) }
12   - before { subject.stub(notification: double('NotificationService').as_null_object) }
13   - before { mock_issue.project.stub_chain(:repository, :commit).and_return(nil) }
14   -
15   - subject { IssueObserver.instance }
16   -
17   - describe '#after_create' do
18   - it 'trigger notification to send emails' do
19   - subject.should_receive(:notification)
20   -
21   - subject.after_create(mock_issue)
22   - end
23   -
24   - it 'should create cross-reference notes' do
25   - other_issue = create(:issue)
26   - mock_issue.stub(references: [other_issue])
27   -
28   - Note.should_receive(:create_cross_reference_note).with(other_issue, mock_issue,
29   - some_user, mock_issue.project)
30   - subject.after_create(mock_issue)
31   - end
32   - end
33   -
34   - context '#after_close' do
35   - context 'a status "closed"' do
36   - before { mock_issue.stub(state: 'closed') }
37   -
38   - it 'note is created if the issue is being closed' do
39   - Note.should_receive(:create_status_change_note).with(mock_issue, mock_issue.project, some_user, 'closed', nil)
40   -
41   - subject.after_close(mock_issue, nil)
42   - end
43   -
44   - it 'trigger notification to send emails' do
45   - subject.notification.should_receive(:close_issue).with(mock_issue, some_user)
46   - subject.after_close(mock_issue, nil)
47   - end
48   -
49   - it 'appends a mention to the closing commit if one is present' do
50   - commit = double('commit', gfm_reference: 'commit 123456')
51   - subject.stub(current_commit: commit)
52   -
53   - Note.should_receive(:create_status_change_note).with(mock_issue, mock_issue.project, some_user, 'closed', commit)
54   -
55   - subject.after_close(mock_issue, nil)
56   - end
57   - end
58   -
59   - context 'a status "reopened"' do
60   - before { mock_issue.stub(state: 'reopened') }
61   -
62   - it 'note is created if the issue is being reopened' do
63   - Note.should_receive(:create_status_change_note).with(mock_issue, mock_issue.project, some_user, 'reopened', nil)
64   -
65   - subject.after_reopen(mock_issue, nil)
66   - end
67   - end
68   - end
69   -
70   - context '#after_update' do
71   - before(:each) do
72   - mock_issue.stub(:is_being_reassigned?).and_return(false)
73   - end
74   -
75   - context 'notification' do
76   - it 'triggered if the issue is being reassigned' do
77   - mock_issue.should_receive(:is_being_reassigned?).and_return(true)
78   - subject.should_receive(:notification)
79   -
80   - subject.after_update(mock_issue)
81   - end
82   -
83   - it 'is not triggered if the issue is not being reassigned' do
84   - mock_issue.should_receive(:is_being_reassigned?).and_return(false)
85   - subject.should_not_receive(:notification)
86   -
87   - subject.after_update(mock_issue)
88   - end
89   - end
90   -
91   - context 'cross-references' do
92   - it 'notices added references' do
93   - mock_issue.should_receive(:notice_added_references)
94   -
95   - subject.after_update(mock_issue)
96   - end
97   - end
98   - end
99   -end
spec/observers/key_observer_spec.rb
... ... @@ -1,23 +0,0 @@
1   -require 'spec_helper'
2   -
3   -describe KeyObserver do
4   - before do
5   - @key = create(:personal_key)
6   -
7   - @observer = KeyObserver.instance
8   - end
9   -
10   - context :after_create do
11   - it do
12   - GitlabShellWorker.should_receive(:perform_async).with(:add_key, @key.shell_id, @key.key)
13   - @observer.after_create(@key)
14   - end
15   - end
16   -
17   - context :after_destroy do
18   - it do
19   - GitlabShellWorker.should_receive(:perform_async).with(:remove_key, @key.shell_id, @key.key)
20   - @observer.after_destroy(@key)
21   - end
22   - end
23   -end
spec/observers/merge_request_observer_spec.rb
... ... @@ -1,131 +0,0 @@
1   -require 'spec_helper'
2   -
3   -describe MergeRequestObserver do
4   - let(:some_user) { create :user }
5   - let(:assignee) { create :user }
6   - let(:author) { create :user }
7   - let(:project) { create :project }
8   - let(:mr_mock) { double(:merge_request, id: 42, assignee: assignee, author: author).as_null_object }
9   - let(:assigned_mr) { create(:merge_request, assignee: assignee, author: author, source_project: project) }
10   - let(:unassigned_mr) { create(:merge_request, author: author, source_project: project) }
11   - let(:closed_assigned_mr) { create(:closed_merge_request, assignee: assignee, author: author, source_project: project) }
12   - let(:closed_unassigned_mr) { create(:closed_merge_request, author: author, source_project: project) }
13   -
14   - before { subject.stub(:current_user).and_return(some_user) }
15   - before { subject.stub(notification: double('NotificationService').as_null_object) }
16   - before { mr_mock.stub(:author_id) }
17   - before { mr_mock.stub(:source_project) }
18   - before { mr_mock.stub(:source_project) }
19   - before { mr_mock.stub(:project) }
20   - before { mr_mock.stub(:create_cross_references!).and_return(true) }
21   - before { Repository.any_instance.stub(commit: nil) }
22   -
23   - before(:each) { enable_observers }
24   - after(:each) { disable_observers }
25   -
26   - subject { MergeRequestObserver.instance }
27   -
28   - describe '#after_create' do
29   - it 'trigger notification service' do
30   - subject.should_receive(:notification)
31   - subject.after_create(mr_mock)
32   - end
33   -
34   - it 'creates cross-reference notes' do
35   - project = create :project
36   - mr_mock.stub(title: "this mr references !#{assigned_mr.id}", project: project)
37   - mr_mock.should_receive(:create_cross_references!).with(project, some_user)
38   -
39   - subject.after_create(mr_mock)
40   - end
41   - end
42   -
43   - context '#after_update' do
44   - before(:each) do
45   - mr_mock.stub(:is_being_reassigned?).and_return(false)
46   - mr_mock.stub(:notice_added_references)
47   - end
48   -
49   - it 'is called when a merge request is changed' do
50   - changed = create(:merge_request, source_project: project)
51   - subject.should_receive(:after_update)
52   -
53   - MergeRequest.observers.enable :merge_request_observer do
54   - changed.title = 'I changed'
55   - changed.save
56   - end
57   - end
58   -
59   - it 'checks for new references' do
60   - mr_mock.should_receive(:notice_added_references)
61   -
62   - subject.after_update(mr_mock)
63   - end
64   -
65   - context 'a notification' do
66   - it 'is sent if the merge request is being reassigned' do
67   - mr_mock.should_receive(:is_being_reassigned?).and_return(true)
68   - subject.should_receive(:notification)
69   -
70   - subject.after_update(mr_mock)
71   - end
72   -
73   - it 'is not sent if the merge request is not being reassigned' do
74   - mr_mock.should_receive(:is_being_reassigned?).and_return(false)
75   - subject.should_not_receive(:notification)
76   -
77   - subject.after_update(mr_mock)
78   - end
79   - end
80   - end
81   -
82   - context '#after_close' do
83   - context 'a status "closed"' do
84   - it 'note is created if the merge request is being closed' do
85   - Note.should_receive(:create_status_change_note).with(assigned_mr, assigned_mr.source_project, some_user, 'closed', nil)
86   -
87   - assigned_mr.close
88   - end
89   -
90   - it 'notification is delivered only to author if the merge request is being closed' do
91   - Note.should_receive(:create_status_change_note).with(unassigned_mr, unassigned_mr.source_project, some_user, 'closed', nil)
92   -
93   - unassigned_mr.close
94   - end
95   - end
96   - end
97   -
98   - context '#after_reopen' do
99   - context 'a status "reopened"' do
100   - it 'note is created if the merge request is being reopened' do
101   - Note.should_receive(:create_status_change_note).with(closed_assigned_mr, closed_assigned_mr.source_project, some_user, 'reopened', nil)
102   -
103   - closed_assigned_mr.reopen
104   - end
105   -
106   - it 'notification is delivered only to author if the merge request is being reopened' do
107   - Note.should_receive(:create_status_change_note).with(closed_unassigned_mr, closed_unassigned_mr.source_project, some_user, 'reopened', nil)
108   -
109   - closed_unassigned_mr.reopen
110   - end
111   - end
112   - end
113   -
114   - describe "Merge Request created" do
115   - def self.it_should_be_valid_event
116   - it { @event.should_not be_nil }
117   - it { @event.should_not be_nil }
118   - it { @event.project.should == project }
119   - it { @event.project.should == project }
120   - end
121   -
122   - before do
123   - @merge_request = create(:merge_request, source_project: project, target_project: project)
124   - @event = Event.last
125   - end
126   -
127   - it_should_be_valid_event
128   - it { @event.action.should == Event::CREATED }
129   - it { @event.target.should == @merge_request }
130   - end
131   -end
spec/observers/users_project_observer_spec.rb
... ... @@ -21,7 +21,7 @@ describe UsersProjectObserver do
21 21  
22 22 it "should send email to user" do
23 23 subject.should_receive(:notification)
24   - @users_project.update_attribute(:project_access, UsersProject::MASTER)
  24 + @users_project.update_attribute(:project_access, UsersProject::OWNER)
25 25 end
26 26  
27 27 it "should not called after UsersProject destroyed" do
... ...
spec/requests/api/merge_requests_spec.rb
... ... @@ -6,7 +6,7 @@ describe API::API do
6 6 after(:each) { ActiveRecord::Base.observers.disable(:user_observer) }
7 7 let(:user) { create(:user) }
8 8 let!(:project) {create(:project, creator_id: user.id, namespace: user.namespace) }
9   - let!(:merge_request) { create(:merge_request, author: user, assignee: user, source_project: project, target_project: project, title: "Test") }
  9 + let!(:merge_request) { create(:merge_request, :simple, author: user, assignee: user, source_project: project, target_project: project, title: "Test") }
10 10 let!(:note) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "a comment on a MR") }
11 11 before {
12 12 project.team << [user, :reporters]
... ... @@ -79,16 +79,12 @@ describe API::API do
79 79 end
80 80  
81 81 context 'forked projects' do
82   - let!(:user2) {create(:user)}
83   - let!(:forked_project_link) { build(:forked_project_link) }
84   - let!(:fork_project) { create(:project, forked_project_link: forked_project_link, namespace: user2.namespace, creator_id: user2.id) }
85   - let!(:unrelated_project) { create(:project, namespace: create(:user).namespace, creator_id: user2.id) }
  82 + let!(:user2) { create(:user) }
  83 + let!(:fork_project) { create(:project, forked_from_project: project, namespace: user2.namespace, creator_id: user2.id) }
  84 + let!(:unrelated_project) { create(:project, namespace: create(:user).namespace, creator_id: user2.id) }
86 85  
87 86 before :each do |each|
88 87 fork_project.team << [user2, :reporters]
89   - forked_project_link.forked_from_project = project
90   - forked_project_link.forked_to_project = fork_project
91   - forked_project_link.save!
92 88 end
93 89  
94 90 it "should return merge_request" do
... ... @@ -127,16 +123,16 @@ describe API::API do
127 123 response.status.should == 400
128 124 end
129 125  
130   - it "should return 400 when target_branch is specified and not a forked project" do
  126 + it "should return 404 when target_branch is specified and not a forked project" do
131 127 post api("/projects/#{project.id}/merge_requests", user),
132 128 title: 'Test merge_request', target_branch: 'master', source_branch: 'stable', author: user, target_project_id: fork_project.id
133   - response.status.should == 400
  129 + response.status.should == 404
134 130 end
135 131  
136   - it "should return 400 when target_branch is specified and for a different fork" do
  132 + it "should return 404 when target_branch is specified and for a different fork" do
137 133 post api("/projects/#{fork_project.id}/merge_requests", user2),
138 134 title: 'Test merge_request', target_branch: 'master', source_branch: 'stable', author: user2, target_project_id: unrelated_project.id
139   - response.status.should == 400
  135 + response.status.should == 404
140 136 end
141 137  
142 138 it "should return 201 when target_branch is specified and for the same project" do
... ...
spec/services/git_push_service_spec.rb
... ... @@ -170,16 +170,10 @@ describe GitPushService do
170 170 Issue.find(issue.id).should be_closed
171 171 end
172 172  
173   - it "passes the closing commit as a thread-local" do
174   - service.execute(project, user, @oldrev, @newrev, @ref)
175   -
176   - Thread.current[:current_commit].should == closing_commit
177   - end
178   -
179 173 it "doesn't create cross-reference notes for a closing reference" do
180 174 expect {
181 175 service.execute(project, user, @oldrev, @newrev, @ref)
182   - }.not_to change { Note.where(project_id: project.id, system: true).count }
  176 + }.not_to change { Note.where(project_id: project.id, system: true, commit_id: closing_commit.id).count }
183 177 end
184 178  
185 179 it "doesn't close issues when pushed to non-default branches" do
... ...
spec/services/issues/close_service_spec.rb 0 → 100644
... ... @@ -0,0 +1,35 @@
  1 +require 'spec_helper'
  2 +
  3 +describe Issues::CloseService do
  4 + let(:project) { create(:empty_project) }
  5 + let(:user) { create(:user) }
  6 + let(:user2) { create(:user) }
  7 + let(:issue) { create(:issue, assignee: user2) }
  8 +
  9 + before do
  10 + project.team << [user, :master]
  11 + project.team << [user2, :developer]
  12 + end
  13 +
  14 + describe :execute do
  15 + context "valid params" do
  16 + before do
  17 + @issue = Issues::CloseService.new(project, user, {}).execute(issue)
  18 + end
  19 +
  20 + it { @issue.should be_valid }
  21 + it { @issue.should be_closed }
  22 +
  23 + it 'should send email to user2 about assign of new issue' do
  24 + email = ActionMailer::Base.deliveries.last
  25 + email.to.first.should == user2.email
  26 + email.subject.should include(issue.title)
  27 + end
  28 +
  29 + it 'should create system note about issue reassign' do
  30 + note = @issue.notes.last
  31 + note.note.should include "Status changed to closed"
  32 + end
  33 + end
  34 + end
  35 +end
... ...
spec/services/issues/create_service_spec.rb 0 → 100644
... ... @@ -0,0 +1,23 @@
  1 +require 'spec_helper'
  2 +
  3 +describe Issues::CreateService do
  4 + let(:project) { create(:empty_project) }
  5 + let(:user) { create(:user) }
  6 +
  7 + describe :execute do
  8 + context "valid params" do
  9 + before do
  10 + project.team << [user, :master]
  11 + opts = {
  12 + title: 'Awesome issue',
  13 + description: 'please fix'
  14 + }
  15 +
  16 + @issue = Issues::CreateService.new(project, user, opts).execute
  17 + end
  18 +
  19 + it { @issue.should be_valid }
  20 + it { @issue.title.should == 'Awesome issue' }
  21 + end
  22 + end
  23 +end
... ...
spec/services/issues/update_service_spec.rb 0 → 100644
... ... @@ -0,0 +1,44 @@
  1 +require 'spec_helper'
  2 +
  3 +describe Issues::UpdateService do
  4 + let(:project) { create(:empty_project) }
  5 + let(:user) { create(:user) }
  6 + let(:user2) { create(:user) }
  7 + let(:issue) { create(:issue) }
  8 +
  9 + before do
  10 + project.team << [user, :master]
  11 + project.team << [user2, :developer]
  12 + end
  13 +
  14 + describe :execute do
  15 + context "valid params" do
  16 + before do
  17 + opts = {
  18 + title: 'New title',
  19 + description: 'Also please fix',
  20 + assignee_id: user2.id,
  21 + state_event: 'close'
  22 + }
  23 +
  24 + @issue = Issues::UpdateService.new(project, user, opts).execute(issue)
  25 + end
  26 +
  27 + it { @issue.should be_valid }
  28 + it { @issue.title.should == 'New title' }
  29 + it { @issue.assignee.should == user2 }
  30 + it { @issue.should be_closed }
  31 +
  32 + it 'should send email to user2 about assign of new issue' do
  33 + email = ActionMailer::Base.deliveries.last
  34 + email.to.first.should == user2.email
  35 + email.subject.should include(issue.title)
  36 + end
  37 +
  38 + it 'should create system note about issue reassign' do
  39 + note = @issue.notes.last
  40 + note.note.should include "Reassigned to \@#{user2.username}"
  41 + end
  42 + end
  43 + end
  44 +end
... ...
spec/services/merge_requests/close_service_spec.rb 0 → 100644
... ... @@ -0,0 +1,35 @@
  1 +require 'spec_helper'
  2 +
  3 +describe MergeRequests::CloseService do
  4 + let(:user) { create(:user) }
  5 + let(:user2) { create(:user) }
  6 + let(:merge_request) { create(:merge_request, assignee: user2) }
  7 + let(:project) { merge_request.project }
  8 +
  9 + before do
  10 + project.team << [user, :master]
  11 + project.team << [user2, :developer]
  12 + end
  13 +
  14 + describe :execute do
  15 + context "valid params" do
  16 + before do
  17 + @merge_request = MergeRequests::CloseService.new(project, user, {}).execute(merge_request)
  18 + end
  19 +
  20 + it { @merge_request.should be_valid }
  21 + it { @merge_request.should be_closed }
  22 +
  23 + it 'should send email to user2 about assign of new merge_request' do
  24 + email = ActionMailer::Base.deliveries.last
  25 + email.to.first.should == user2.email
  26 + email.subject.should include(merge_request.title)
  27 + end
  28 +
  29 + it 'should create system note about merge_request reassign' do
  30 + note = @merge_request.notes.last
  31 + note.note.should include "Status changed to closed"
  32 + end
  33 + end
  34 + end
  35 +end
... ...
spec/services/merge_requests/create_service_spec.rb 0 → 100644
... ... @@ -0,0 +1,25 @@
  1 +require 'spec_helper'
  2 +
  3 +describe MergeRequests::CreateService do
  4 + let(:project) { create(:project) }
  5 + let(:user) { create(:user) }
  6 +
  7 + describe :execute do
  8 + context "valid params" do
  9 + before do
  10 + project.team << [user, :master]
  11 + opts = {
  12 + title: 'Awesome merge_request',
  13 + description: 'please fix',
  14 + source_branch: 'stable',
  15 + target_branch: 'master'
  16 + }
  17 +
  18 + @merge_request = MergeRequests::CreateService.new(project, user, opts).execute
  19 + end
  20 +
  21 + it { @merge_request.should be_valid }
  22 + it { @merge_request.title.should == 'Awesome merge_request' }
  23 + end
  24 + end
  25 +end
... ...
spec/services/merge_requests/update_service_spec.rb 0 → 100644
... ... @@ -0,0 +1,44 @@
  1 +require 'spec_helper'
  2 +
  3 +describe MergeRequests::UpdateService do
  4 + let(:user) { create(:user) }
  5 + let(:user2) { create(:user) }
  6 + let(:merge_request) { create(:merge_request, :simple) }
  7 + let(:project) { merge_request.project }
  8 +
  9 + before do
  10 + project.team << [user, :master]
  11 + project.team << [user2, :developer]
  12 + end
  13 +
  14 + describe :execute do
  15 + context "valid params" do
  16 + before do
  17 + opts = {
  18 + title: 'New title',
  19 + description: 'Also please fix',
  20 + assignee_id: user2.id,
  21 + state_event: 'close'
  22 + }
  23 +
  24 + @merge_request = MergeRequests::UpdateService.new(project, user, opts).execute(merge_request)
  25 + end
  26 +
  27 + it { @merge_request.should be_valid }
  28 + it { @merge_request.title.should == 'New title' }
  29 + it { @merge_request.assignee.should == user2 }
  30 + it { @merge_request.should be_closed }
  31 +
  32 + it 'should send email to user2 about assign of new merge_request' do
  33 + email = ActionMailer::Base.deliveries.last
  34 + email.to.first.should == user2.email
  35 + email.subject.should include(merge_request.title)
  36 + end
  37 +
  38 + it 'should create system note about merge_request reassign' do
  39 + note = @merge_request.notes.last
  40 + note.note.should include "Reassigned to \@#{user2.username}"
  41 + end
  42 + end
  43 + end
  44 +end
... ...
spec/services/notification_service_spec.rb
... ... @@ -5,7 +5,7 @@ describe NotificationService do
5 5  
6 6 describe 'Keys' do
7 7 describe :new_key do
8   - let(:key) { create(:personal_key) }
  8 + let!(:key) { create(:personal_key) }
9 9  
10 10 it { notification.new_key(key).should be_true }
11 11  
... ... @@ -18,7 +18,7 @@ describe NotificationService do
18 18  
19 19 describe 'Email' do
20 20 describe :new_email do
21   - let(:email) { create(:email) }
  21 + let!(:email) { create(:email) }
22 22  
23 23 it { notification.new_email(email).should be_true }
24 24  
... ... @@ -57,15 +57,42 @@ describe NotificationService do
57 57 Notify.should_not_receive(:note_issue_email)
58 58 notification.new_note(mentioned_note)
59 59 end
  60 + end
60 61  
61   - def should_email(user_id)
62   - Notify.should_receive(:note_issue_email).with(user_id, note.id)
  62 + describe 'new note on issue in project that belongs to a group' do
  63 + let(:group) { create(:group) }
  64 +
  65 + before do
  66 + note.project.namespace_id = group.id
  67 + note.project.group.add_user(@u_watcher, UsersGroup::MASTER)
  68 + note.project.save
  69 + user_project = note.project.users_projects.find_by_user_id(@u_watcher.id)
  70 + user_project.notification_level = Notification::N_PARTICIPATING
  71 + user_project.save
  72 + user_group = note.project.group.users_groups.find_by_user_id(@u_watcher.id)
  73 + user_group.notification_level = Notification::N_GLOBAL
  74 + user_group.save
63 75 end
64 76  
65   - def should_not_email(user_id)
66   - Notify.should_not_receive(:note_issue_email).with(user_id, note.id)
  77 + it do
  78 + should_email(note.noteable.author_id)
  79 + should_email(note.noteable.assignee_id)
  80 + should_email(@u_mentioned.id)
  81 + should_not_email(@u_watcher.id)
  82 + should_not_email(note.author_id)
  83 + should_not_email(@u_participating.id)
  84 + should_not_email(@u_disabled.id)
  85 + notification.new_note(note)
67 86 end
68 87 end
  88 +
  89 + def should_email(user_id)
  90 + Notify.should_receive(:note_issue_email).with(user_id, note.id)
  91 + end
  92 +
  93 + def should_not_email(user_id)
  94 + Notify.should_not_receive(:note_issue_email).with(user_id, note.id)
  95 + end
69 96 end
70 97  
71 98 context 'commit note' do
... ...