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