diff options
204 files changed, 1434 insertions, 980 deletions
diff --git a/.teatro.yml b/.teatro.yml new file mode 100644 index 00000000000..30054361981 --- /dev/null +++ b/.teatro.yml @@ -0,0 +1,8 @@ +stage: + before: + - cp config/gitlab.teatro.yml config/gitlab.yml + - mkdir /apps/gitlab-satellites + - mkdir /apps/repositories + + database: + - RAILS_ENV=development force=yes bundle exec rake db:create gitlab:setup
\ No newline at end of file diff --git a/CHANGELOG b/CHANGELOG index 495d20e8b04..a5962fdfdc7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,13 @@ +v 7.1.0 + - Remove observers + - Improve MR discussions + - Filter by description on Issues#index page + - Fix bug with namespace select when create new project page + - Show README link after description for non-master members + - Add @all mention for comments + - Dont show reply button if user is not signed in + - Expose more information for issues with webhook + v 7.0.0 - The CPU no longer overheats when you hold down the spacebar - Improve edit file UI diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3b6212c960d..a08ffa449d8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,8 +1,8 @@ # Contribute to GitLab -This guide details how contribute to GitLab. - -If you want to know how the GitLab team handles contributions have a look at [the GitLab contributing process](PROCESS.md). +Thank you for your interest in contributing to GitLab. +This guide details how contribute to GitLab in a way that is efficient for everyone. +If you have read this guide and want to know how the GitLab core-team operates please see [the GitLab contributing process](PROCESS.md). ## Contributor license agreement @@ -75,9 +75,9 @@ If you can, please submit a merge request with the fix or improvements including The **official merge window** is in the beginning of the month from the 1st to the 7th day of the month. The best time to submit a MR and get feedback fast. Before this time the GitLab B.V. team is still dealing with work that is created by the monthly release such as assisting subscribers with upgrade issues, the release of Enterprise Edition and the upgrade of GitLab Cloud. After the 7th it is already getting closer to the release date of the next version. This means there is less time to fix the issues created by merging large new features. -Please keep the change in a single MR **as small as possible**. If you want to contribute a large feature think very hard what the minimum viable change is. Can you split functionality? Can you only submit the backend/API code? Can you start with a very simple UI? The smaller a MR is the more likely it is it will be merged, after that you can send more MR's to enhance it. +Please keep the change in a single MR **as small as possible**. If you want to contribute a large feature think very hard what the minimum viable change is. Can you split functionality? Can you only submit the backend/API code? Can you start with a very simple UI? Can you do part of the refactor? The increased reviewability of small MR's that leads to higher code quality is more important to us than having a mimimal commit log. The smaller a MR is the more likely it is it will be merged (quickly), after that you can send more MR's to enhance it. -For examples of feedback on merge requests please look at already [closed merge requests](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests?assignee_id=&label_name=&milestone_id=&scope=&sort=&state=closed). Please ensure that your merge request meets the following contribution acceptance criteria. +For examples of feedback on merge requests please look at already [closed merge requests](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests?assignee_id=&label_name=&milestone_id=&scope=&sort=&state=closed). If you would like quick feedback on your merge request feel free to mention one of the Merge Marshalls of [the core-team](https://about.gitlab.com/core-team/). Please ensure that your merge request meets the following contribution acceptance criteria. **Please format your merge request description as follows:** @@ -97,7 +97,8 @@ For examples of feedback on merge requests please look at already [closed merge 1. Keeps the GitLab code base clean and well structured 1. Contains functionality we think other users will benefit from too 1. Doesn't add configuration options since they complicate future changes -1. Contains a single commit (please use `git rebase -i` to squash commits) +1. Initially contains a single commit (please use `git rebase -i` to squash commits) +1. Changes after submitting the merge request should be in separate commits (no squashing) 1. It conforms to the following style guides ## Style guides @@ -113,4 +114,4 @@ For examples of feedback on merge requests please look at already [closed merge 1. [Shell commands](doc/development/shell_commands.md) created by GitLab contributors to enhance security 1. [Markdown](http://www.cirosantilli.com/markdown-styleguide) -This is also the style used by linting tools such as [Rubocop](https://github.com/bbatsov/rubocop), PullReview[https://www.pullreview.com/] and [Hound CI](https://houndci.com). +This is also the style used by linting tools such as [Rubocop](https://github.com/bbatsov/rubocop), [PullReview](https://www.pullreview.com/) and [Hound CI](https://houndci.com). @@ -11,7 +11,6 @@ end gem "rails", "~> 4.1.0" gem "protected_attributes" -gem 'rails-observers' # Make links from text gem 'rails_autolink', '~> 1.1' diff --git a/Gemfile.lock b/Gemfile.lock index 6c27b6e3739..382633c2246 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -365,8 +365,6 @@ GEM bundler (>= 1.3.0, < 2.0) railties (= 4.1.1) sprockets-rails (~> 2.0) - rails-observers (0.1.2) - activemodel (~> 4.0) rails_autolink (1.1.6) rails (> 3.1) rails_best_practices (1.14.4) @@ -644,7 +642,6 @@ DEPENDENCIES rack-cors rack-mini-profiler rails (~> 4.1.0) - rails-observers rails_autolink (~> 1.1) rails_best_practices raphael-rails (~> 2.1.2) @@ -1 +1 @@ -7.0.0 +7.1.0.pre diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index 9bcd8f0a171..5b124554c38 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -143,6 +143,14 @@ $ -> $(@).next('table').show() $(@).remove() + # Show/hide comments on diff + $("body").on "click", ".js-toggle-diff-comments", (e) -> + $(@).find('i'). + toggleClass('icon-chevron-down'). + toggleClass('icon-chevron-up') + $(@).closest(".diff-file").find(".notes_holder").toggle() + e.preventDefault() + (($) -> # Disable an element and add the 'disabled' Bootstrap class $.fn.extend disable: -> diff --git a/app/assets/javascripts/markdown_area.js.coffee b/app/assets/javascripts/markdown_area.js.coffee index 12b69fa4fdc..516a40b25c2 100644 --- a/app/assets/javascripts/markdown_area.js.coffee +++ b/app/assets/javascripts/markdown_area.js.coffee @@ -21,7 +21,6 @@ $(document).ready -> $(".div-dropzone").append divSpinner $(".div-dropzone-spinner").append iconSpinner - dropzone = $(".div-dropzone").dropzone( url: project_image_path_upload dictDefaultMessage: "" @@ -77,6 +76,107 @@ $(document).ready -> return ) + child = $(dropzone[0]).children("textarea") + + formatLink = (str) -> + "![" + str.alt + "](" + str.url + ")" + + handlePaste = (e) -> + e.preventDefault() + my_event = e.originalEvent + + if my_event.clipboardData and my_event.clipboardData.items + processItem(my_event) + + processItem = (e) -> + image = isImage(e) + if image + filename = getFilename(e) or "image.png" + text = "{{" + filename + "}}" + pasteText(text) + uploadFile image.getAsFile(), filename + + else + text = e.clipboardData.getData("text/plain") + pasteText(text) + + isImage = (data) -> + i = 0 + while i < data.clipboardData.items.length + item = data.clipboardData.items[i] + if item.type.indexOf("image") isnt -1 + return item + i++ + return false + + pasteText = (text) -> + caretStart = $(child)[0].selectionStart + caretEnd = $(child)[0].selectionEnd + textEnd = $(child).val().length + + beforeSelection = $(child).val().substring 0, caretStart + afterSelection = $(child).val().substring caretEnd, textEnd + $(child).val beforeSelection + text + afterSelection + $(".markdown-area").trigger "input" + + getFilename = (e) -> + if window.clipboardData and window.clipboardData.getData + value = window.clipboardData.getData("Text") + else if e.clipboardData and e.clipboardData.getData + value = e.clipboardData.getData("text/plain") + + value = value.split("\r") + value.first() + + uploadFile = (item, filename) -> + formData = new FormData() + formData.append "markdown_img", item, filename + $.ajax + url: project_image_path_upload + type: "POST" + data: formData + dataType: "json" + processData: false + contentType: false + headers: + "X-CSRF-Token": $("meta[name=\"csrf-token\"]").attr("content") + + beforeSend: -> + showSpinner() + closeAlertMessage() + + success: (e, textStatus, response) -> + insertToTextArea(filename, formatLink(response.responseJSON.link)) + + error: (response) -> + showError(response.responseJSON.message) + + complete: -> + closeSpinner() + + insertToTextArea = (filename, url) -> + $(child).val (index, val) -> + val.replace("{{" + filename + "}}", url + "\n") + + appendToTextArea = (url) -> + $(child).val (index, val) -> + val + url + "\n" + + showSpinner = (e) -> + $(".div-dropzone-spinner").css "opacity", 0.7 + + closeSpinner = -> + $(".div-dropzone-spinner").css "opacity", 0 + + showError = (message) -> + checkIfMsgExists = $(".error-alert").children().length + if checkIfMsgExists is 0 + $(".error-alert").append divAlert + $(".div-dropzone-alert").append btnAlert + message + + closeAlertMessage = -> + $(".div-dropzone-alert").alert "close" + $(".markdown-selector").click (e) -> e.preventDefault() $(".div-dropzone").click() diff --git a/app/assets/javascripts/merge_request.js.coffee b/app/assets/javascripts/merge_request.js.coffee index b04a81c85eb..59e53b69e3d 100644 --- a/app/assets/javascripts/merge_request.js.coffee +++ b/app/assets/javascripts/merge_request.js.coffee @@ -109,10 +109,10 @@ class MergeRequest type: 'GET' url: this.$('.merge-request-tabs .diffs-tab a').attr('href') beforeSend: => - this.$('.status').addClass 'loading' + this.$('.mr-loading-status .loading').show() complete: => @diffs_loaded = true - this.$('.status').removeClass 'loading' + this.$('.mr-loading-status .loading').hide() success: (data) => this.$(".diffs").html(data.html) dataType: 'json' diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index a41ee67a841..91a4ccaba43 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -142,8 +142,15 @@ class Notes # remove the note (will be added again below) row.next().find(".note").remove() - # append new note to all matching discussions - $(".notes[rel='" + note.discussion_id + "']").append note.html + # Add note to 'Changes' page discussions + $(".notes[rel='" + note.discussion_id + "']").append note.html + + # Init discussion on 'Discussion' page if it is merge request page + if $('body').attr('data-page').indexOf('projects:merge_request') == 0 + $('ul.main-notes-list').append(note.discussion_with_diff_html) + else + # append new note to all matching discussions + $(".notes[rel='" + note.discussion_id + "']").append note.html # cleanup after successfully creating a diff/discussion note @removeDiscussionNoteForm(form) diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 7d058ad7719..b596af38311 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -8,8 +8,6 @@ *= require select2 *= require highlightjs.min *= require_self - *= require nprogress - *= require nprogress-bootstrap *= require dropzone/basic */ @@ -21,6 +19,12 @@ @import 'gl_bootstrap'; /** + * NProgress load bar css + */ +@import 'nprogress'; +@import 'nprogress-bootstrap'; + +/** * Font icons * */ diff --git a/app/assets/stylesheets/generic/common.scss b/app/assets/stylesheets/generic/common.scss index da2c1b0cbd8..b9942b5cb5d 100644 --- a/app/assets/stylesheets/generic/common.scss +++ b/app/assets/stylesheets/generic/common.scss @@ -16,6 +16,7 @@ .append-bottom-15 { margin-bottom:15px } .append-bottom-20 { margin-bottom:20px } .inline { display: inline-block } +.center { text-align: center } .underlined-link { text-decoration: underline; } .hint { font-style: italic; color: #999; } @@ -55,7 +56,7 @@ pre { .dropdown-menu > li > a:hover, .dropdown-menu > li > a:focus { - background: $bg_style_color; + background: $bg_primary; color: #FFF } @@ -256,13 +257,6 @@ li.note { } } -h1.http_status_code { - font-size: 56px; - line-height: 100px; - font-weight: normal; - color: #456; -} - .control-group { .controls { span { diff --git a/app/assets/stylesheets/generic/forms.scss b/app/assets/stylesheets/generic/forms.scss index 5258acca466..2a31cae5dfb 100644 --- a/app/assets/stylesheets/generic/forms.scss +++ b/app/assets/stylesheets/generic/forms.scss @@ -76,30 +76,10 @@ label { } } -.commit-message-container { - background-color: $body-bg; - position: relative; - font-family: $monospace_font; - $left: 12px; - .max-width-marker { - width: 72ch; - color: rgba(0, 0, 0, 0.0); - font-family: inherit; - left: $left; - height: 100%; - border-right: 1px solid mix($input-border, white); - position: absolute; - z-index: 1; - } - > textarea { - background-color: rgba(0, 0, 0, 0.0); - font-family: inherit; - padding-left: $left; - position: relative; - z-index: 2; - } -} - .fieldset-form fieldset { margin-bottom: 20px; } + +.form-control { + @include box-shadow(none); +} diff --git a/app/assets/stylesheets/generic/jquery.scss b/app/assets/stylesheets/generic/jquery.scss index 6b29accb315..bfbbc7d25e3 100644 --- a/app/assets/stylesheets/generic/jquery.scss +++ b/app/assets/stylesheets/generic/jquery.scss @@ -41,8 +41,8 @@ } .ui-state-active { - border: 1px solid $bg_style_color; - background: $bg_style_color; + border: 1px solid $bg_primary; + background: $bg_primary; color: #FFF; } diff --git a/app/assets/stylesheets/generic/selects.scss b/app/assets/stylesheets/generic/selects.scss index 0afd85aedf6..e0f508d2695 100644 --- a/app/assets/stylesheets/generic/selects.scss +++ b/app/assets/stylesheets/generic/selects.scss @@ -42,7 +42,7 @@ .select2-results { max-height: 350px; .select2-highlighted { - background: $bg_style_color; + background: $bg_primary; } } } diff --git a/app/assets/stylesheets/gl_bootstrap.scss b/app/assets/stylesheets/gl_bootstrap.scss index 4271a7b310c..34b0608646c 100644 --- a/app/assets/stylesheets/gl_bootstrap.scss +++ b/app/assets/stylesheets/gl_bootstrap.scss @@ -9,7 +9,7 @@ $font-size-base: 13px !default; $nav-pills-active-link-hover-bg: $bg_primary; $pagination-active-bg: $bg_primary; -$list-group-active-bg: $bg_style_color; +$list-group-active-bg: $bg_primary; // Core variables and mixins @import "bootstrap/variables"; diff --git a/app/assets/stylesheets/main/variables.scss b/app/assets/stylesheets/main/variables.scss index 68f7f6b8581..c49f7db788e 100644 --- a/app/assets/stylesheets/main/variables.scss +++ b/app/assets/stylesheets/main/variables.scss @@ -2,7 +2,6 @@ * General Colors */ $style_color: #474D57; -$bg_style_color: #2299BB; $hover: #D9EDF7; /* @@ -40,3 +39,8 @@ $border_warning: #EB9532; */ $added: #63c363; $deleted: #f77; + +/** + * + */ +$nprogress-color: #3498db; diff --git a/app/assets/stylesheets/sections/commits.scss b/app/assets/stylesheets/sections/commits.scss index ef8c5f089b9..f00d024f389 100644 --- a/app/assets/stylesheets/sections/commits.scss +++ b/app/assets/stylesheets/sections/commits.scss @@ -213,3 +213,27 @@ li.commit { padding: 4px 12px; } } + +.commit-message-container { + background-color: $body-bg; + position: relative; + font-family: $monospace_font; + $left: 12px; + .max-width-marker { + width: 72ch; + color: rgba(0, 0, 0, 0.0); + font-family: inherit; + left: $left; + height: 100%; + border-right: 1px solid mix($input-border, white); + position: absolute; + z-index: 1; + } + > textarea { + background-color: rgba(0, 0, 0, 0.0); + font-family: inherit; + padding-left: $left; + position: relative; + z-index: 2; + } +} diff --git a/app/assets/stylesheets/sections/errors.scss b/app/assets/stylesheets/sections/errors.scss new file mode 100644 index 00000000000..32d2d7b1dbf --- /dev/null +++ b/app/assets/stylesheets/sections/errors.scss @@ -0,0 +1,14 @@ +.error-page { + max-width: 400px; + margin: 0 auto; + + h1, h2, h3 { + text-align: center; + } + + h1 { + font-size: 56px; + line-height: 100px; + font-weight: 300; + } +} diff --git a/app/assets/stylesheets/sections/events.scss b/app/assets/stylesheets/sections/events.scss index 957af62c0ea..656aa5b18a6 100644 --- a/app/assets/stylesheets/sections/events.scss +++ b/app/assets/stylesheets/sections/events.scss @@ -156,12 +156,13 @@ .filter_icon { a { text-align:center; - background: #EEE; + background: $bg_primary; margin-bottom: 10px; float: left; padding: 9px 6px; font-size: 18px; width: 40px; + color: #FFF; @include border-radius(3px); } diff --git a/app/assets/stylesheets/sections/header.scss b/app/assets/stylesheets/sections/header.scss index b811b0bc665..29cb0f4b87c 100644 --- a/app/assets/stylesheets/sections/header.scss +++ b/app/assets/stylesheets/sections/header.scss @@ -6,14 +6,12 @@ header { &.navbar-gitlab { margin-bottom: 0; min-height: 40px; + border: none; .navbar-inner { - background: #F1F1F1; - border-bottom: 1px solid #DDD; filter: none; .nav > li > a { - color: $style_color; font-size: 14px; line-height: 32px; padding: 6px 10px; @@ -248,8 +246,6 @@ header { float: left; height: 46px; width: 2px; - background: white; - border-left: 1px solid #DDD; margin-left: 10px; margin-right: 10px; } diff --git a/app/assets/stylesheets/sections/nav.scss b/app/assets/stylesheets/sections/nav.scss index 59eaaec9498..f5d09c2df10 100644 --- a/app/assets/stylesheets/sections/nav.scss +++ b/app/assets/stylesheets/sections/nav.scss @@ -3,7 +3,7 @@ margin: 20px 0; margin-top: 0; padding-top: 4px; - border-bottom: 1px solid #E1E1E1; + border-bottom: 1px solid #E9E9E9; ul { padding: 0; diff --git a/app/assets/stylesheets/sections/notes.scss b/app/assets/stylesheets/sections/notes.scss index e8d6ec3e29a..18db7abc64e 100644 --- a/app/assets/stylesheets/sections/notes.scss +++ b/app/assets/stylesheets/sections/notes.scss @@ -43,38 +43,14 @@ ul.notes { } .discussion { - padding: 8px 0; + padding: 10px 0; overflow: hidden; display: block; position:relative; + border-bottom: 1px solid #EEE; .discussion-body { margin-left: 50px; - - .diff-file, - .discussion-hidden, - .notes { - background-color: #F9F9F9; - } - .diff-file .notes { - /* reset */ - background: inherit; - border: none; - @include box-shadow(none); - - } - .discussion-hidden .note { - @extend .cgray; - padding: 8px; - text-align: center; - } - .notes .note { - border-color: #ddd; - padding: 8px; - } - .reply-btn { - margin-top: 8px; - } } } @@ -137,10 +113,6 @@ ul.notes { vertical-align: top; } } - - .reply-btn { - margin: 5px; - } } /** @@ -376,3 +348,17 @@ ul.notes { margin-top: 5px; margin-bottom: 5px; } + +.discussion-body, +.diff-file { + .notes .note { + border-color: #ddd; + padding: 10px 15px; + } + + .discussion-reply-holder { + background: #f9f9f9; + padding: 10px 15px; + border-top: 1px solid #DDD; + } +} diff --git a/app/assets/stylesheets/sections/profile.scss b/app/assets/stylesheets/sections/profile.scss index ed73e1630f2..086875582f3 100644 --- a/app/assets/stylesheets/sections/profile.scss +++ b/app/assets/stylesheets/sections/profile.scss @@ -56,10 +56,10 @@ text-align: center; .prev { - @extend .thumbnail; - height: 30px; - width: 175px; + height: 80px; + width: 160px; margin-bottom: 10px; + @include border-radius(4px); &.classic { background: #31363e; @@ -92,10 +92,13 @@ text-align: center; .prev { - @extend .thumbnail; - height: 151px; - width: 220px; + width: 160px; margin-bottom: 10px; + + img { + max-width: 100%; + @include border-radius(4px); + } } } } diff --git a/app/assets/stylesheets/themes/ui_basic.scss b/app/assets/stylesheets/themes/ui_basic.scss index 30f0bbaf4c8..3e3744fdc33 100644 --- a/app/assets/stylesheets/themes/ui_basic.scss +++ b/app/assets/stylesheets/themes/ui_basic.scss @@ -4,11 +4,21 @@ * */ .ui_basic { - .separator { - background: #F9F9F9; - border-left: 1px solid #DDD; + header { + &.navbar-gitlab { + .navbar-inner { + background: #F1F1F1; + border-bottom: 1px solid #DDD; + .nav > li > a { + color: $style_color; + } + .separator { + background: #F9F9F9; + border-left: 1px solid #DDD; + } + } + } } - .main-nav { background: #FFF; } diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 5ecdfbd807e..f0040bf5e87 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -100,6 +100,16 @@ class Admin::UsersController < Admin::ApplicationController end end + def remove_email + email = user.emails.find(params[:email_id]) + email.destroy + + respond_to do |format| + format.html { redirect_to :back, notice: "Successfully removed email." } + format.js { render nothing: true } + end + end + protected def user diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 603e89a5e29..d58890fa33b 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -48,7 +48,7 @@ class ApplicationController < ActionController::Base flash[:alert] = "Your account is blocked. Retry when an admin has unblocked it." new_user_session_path else - super + @return_to || root_path end end diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb index 0c87fe0d9ae..ef2afec52dc 100644 --- a/app/controllers/omniauth_callbacks_controller.rb +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -31,6 +31,12 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController end end + def omniauth_error + @provider = params[:provider] + @error = params[:error] + render 'errors/omniauth_error', layout: "errors", status: 422 + end + private def handle_omniauth @@ -45,14 +51,19 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController # Create user if does not exist # and allow_single_sign_on is true - if Gitlab.config.omniauth['allow_single_sign_on'] - @user ||= Gitlab::OAuth::User.create(oauth) + if Gitlab.config.omniauth['allow_single_sign_on'] && !@user + @user, errors = Gitlab::OAuth::User.create(oauth) end - if @user + if @user && !errors sign_in_and_redirect(@user) else - flash[:notice] = "There's no such user!" + if errors + error_message = errors.map{ |attribute, message| "#{attribute} #{message}" }.join(", ") + redirect_to omniauth_error_path(oauth['provider'], error: error_message) and return + else + flash[:notice] = "There's no such user!" + end redirect_to new_user_session_path end end diff --git a/app/controllers/projects/commits_controller.rb b/app/controllers/projects/commits_controller.rb index 12856191c26..038645aa497 100644 --- a/app/controllers/projects/commits_controller.rb +++ b/app/controllers/projects/commits_controller.rb @@ -13,6 +13,8 @@ class Projects::CommitsController < Projects::ApplicationController @limit, @offset = (params[:limit] || 40), (params[:offset] || 0) @commits = @repo.commits(@ref, @path, @limit, @offset) + @note_counts = Note.where(commit_id: @commits.map(&:id)). + group(:commit_id).count respond_to do |format| format.html # index.html.erb diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index ddea8901f9b..ffe65cb41c5 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -20,7 +20,7 @@ class Projects::IssuesController < Projects::ApplicationController terms = params['issue_search'] @issues = issues_filtered - @issues = @issues.where("title LIKE ?", "%#{terms}%") if terms.present? + @issues = @issues.where("title LIKE ? OR description LIKE ?", "%#{terms}%", "%#{terms}%") if terms.present? @issues = @issues.page(params[:page]).per(20) assignee_id, milestone_id = params[:assignee_id], params[:milestone_id] diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 1abedc1f272..89f4ab01a3f 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -32,6 +32,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def show + @note_counts = Note.where(commit_id: @merge_request.commits.map(&:id)). + group(:commit_id).count respond_to do |format| format.html format.diff { render text: @merge_request.to_diff(current_user) } @@ -86,6 +88,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController @compare_failed = true end + @note_counts = Note.where(commit_id: @commits.map(&:id)). + group(:commit_id).count + @diffs = compare_action.diffs @merge_request.title = @merge_request.source_branch.titleize.humanize @target_project = @merge_request.target_project diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb index b5b0446b43f..66cc1a3dec7 100644 --- a/app/controllers/projects/notes_controller.rb +++ b/app/controllers/projects/notes_controller.rb @@ -21,7 +21,7 @@ class Projects::NotesController < Projects::ApplicationController end def create - @note = Notes::CreateService.new(project, current_user, params).execute + @note = Notes::CreateService.new(project, current_user, params[:note]).execute respond_to do |format| format.json { render_note_json(@note) } @@ -85,12 +85,24 @@ class Projects::NotesController < Projects::ApplicationController ) end + def note_to_discussion_with_diff_html(note) + return unless note.for_diff_line? + + render_to_string( + "projects/notes/_discussion", + layout: false, + formats: [:html], + locals: { discussion_notes: [note] } + ) + end + def render_note_json(note) render json: { id: note.id, discussion_id: note.discussion_id, html: note_to_html(note), - discussion_html: note_to_discussion_html(note) + discussion_html: note_to_discussion_html(note), + discussion_with_diff_html: note_to_discussion_with_diff_html(note) } end diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb index 8834a995081..7997c726fbb 100644 --- a/app/controllers/projects/refs_controller.rb +++ b/app/controllers/projects/refs_controller.rb @@ -37,7 +37,7 @@ class Projects::RefsController < Projects::ApplicationController 0 end - @limit = 10 + @limit = 25 @path = params[:path] diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 9e14af62048..0d15b458b70 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -98,8 +98,7 @@ class ProjectsController < ApplicationController def destroy return access_denied! unless can?(current_user, :remove_project, project) - project.team.truncate - project.destroy + ::Projects::DestroyService.new(@project, current_user, {}).execute respond_to do |format| format.html { redirect_to root_path } @@ -125,18 +124,12 @@ class ProjectsController < ApplicationController def autocomplete_sources note_type = params['type'] note_id = params['type_id'] - participating = if note_type && note_id - participants_in(note_type, note_id) - else - [] - end - team_members = sorted(@project.team.members) - participants = team_members + participating + participants = ::Projects::ParticipantsService.new(@project).execute(note_type, note_id) @suggestions = { emojis: Emoji.names.map { |e| { name: e, path: view_context.image_url("emoji/#{e}.png") } }, issues: @project.issues.select([:iid, :title, :description]), mergerequests: @project.merge_requests.select([:iid, :title, :description]), - members: participants.uniq + members: participants } respond_to do |format| @@ -192,25 +185,4 @@ class ProjectsController < ApplicationController def user_layout current_user ? "projects" : "public_projects" end - - def participants_in(type, id) - users = case type - when "Issue" - issue = @project.issues.find_by_iid(id) - issue ? issue.participants : [] - when "MergeRequest" - merge_request = @project.merge_requests.find_by_iid(id) - merge_request ? merge_request.participants : [] - when "Commit" - author_ids = Note.for_commit_id(id).pluck(:author_id).uniq - User.where(id: author_ids) - else - [] - end - sorted(users) - end - - def sorted(users) - users.uniq.to_a.compact.sort_by(&:username).map { |user| { username: user.username, name: user.name } } - end end diff --git a/app/controllers/users_sessions_controller.rb b/app/controllers/users_sessions_controller.rb new file mode 100644 index 00000000000..656c92376fd --- /dev/null +++ b/app/controllers/users_sessions_controller.rb @@ -0,0 +1,6 @@ +class UsersSessionsController < Devise::SessionsController + def create + @return_to = params[:return_to] + super + end +end diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index d3904e16c11..f0530c74828 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -45,15 +45,15 @@ module EventsHelper def event_feed_title(event) if event.issue? - "#{event.author_name} #{event.action_name} issue ##{event.target_id}: #{event.issue_title} at #{event.project_name}" + "#{event.author_name} #{event.action_name} issue ##{event.target_iid}: #{event.issue_title} at #{event.project_name}" elsif event.merge_request? - "#{event.author_name} #{event.action_name} MR ##{event.target_id}: #{event.merge_request_title} at #{event.project_name}" + "#{event.author_name} #{event.action_name} MR ##{event.target_iid}: #{event.merge_request_title} at #{event.project_name}" elsif event.push? "#{event.author_name} #{event.push_action_name} #{event.ref_type} #{event.ref_name} at #{event.project_name}" elsif event.membership_changed? "#{event.author_name} #{event.action_name} #{event.project_name}" elsif event.note? - "#{event.author_name} commented on #{event.note_target_type} ##{truncate event.note_target_id} at #{event.project_name}" + "#{event.author_name} commented on #{event.note_target_type} ##{truncate event.note_target_iid} at #{event.project_name}" else "" end diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb index db5b4eacd1f..c7f769cee64 100644 --- a/app/helpers/gitlab_markdown_helper.rb +++ b/app/helpers/gitlab_markdown_helper.rb @@ -63,10 +63,14 @@ module GitlabMarkdownHelper paths = extract_paths(text) paths.uniq.each do |file_path| - new_path = rebuild_path(file_path) - # Finds quoted path so we don't replace other mentions of the string - # eg. "doc/api" will be replaced and "/home/doc/api/text" won't - text.gsub!("\"#{file_path}\"", "\"/#{new_path}\"") + # If project does not have repository + # its nothing to rebuild + if @repository.exists? && !@repository.empty? + new_path = rebuild_path(file_path) + # Finds quoted path so we don't replace other mentions of the string + # eg. "doc/api" will be replaced and "/home/doc/api/text" won't + text.gsub!("\"#{file_path}\"", "\"/#{new_path}\"") + end end text @@ -91,7 +95,12 @@ module GitlabMarkdownHelper end def link_to_ignore?(link) - ignored_protocols.map{ |protocol| link.include?(protocol) }.any? + if link =~ /\#\w+/ + # ignore anchors like <a href="#my-header"> + true + else + ignored_protocols.map{ |protocol| link.include?(protocol) }.any? + end end def ignored_protocols @@ -169,7 +178,7 @@ module GitlabMarkdownHelper def current_sha if @commit @commit.id - else + elsif @repository && !@repository.empty? @repository.head_commit.sha end end diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb index 9ed38ba84b2..53ac5febd61 100644 --- a/app/helpers/notes_helper.rb +++ b/app/helpers/notes_helper.rb @@ -15,12 +15,6 @@ module NotesHelper end end - def link_to_merge_request_diff_line_note(note) - if note.for_merge_request_diff_line? and note.diff - link_to "#{note.diff_file_name}:L#{note.diff_new_line}", diffs_project_merge_request_path(note.project, note.noteable, anchor: note.line_code) - end - end - def note_timestamp(note) # Shows the created at time and the updated at time if different ts = "#{time_ago_with_tooltip(note.created_at, 'bottom', 'note_created_ago')}" @@ -61,4 +55,23 @@ module NotesHelper link_to "", "javascript:;", class: "add-diff-note js-add-diff-note-button", data: data, title: "Add a comment to this line" end + + def link_to_reply_diff(note) + return unless current_user + + data = { + noteable_type: note.noteable_type, + noteable_id: note.noteable_id, + commit_id: note.commit_id, + line_code: note.line_code, + discussion_id: note.discussion_id + } + + link_to "javascript:;", class: "btn reply-btn js-discussion-reply-button", + data: data, title: "Add a reply" do + link_text = "" + link_text < content_tag(:i, nil, class: 'icon-comment') + link_text << "Reply" + end + end end diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb index a46a593c6e3..81414959f3b 100644 --- a/app/models/concerns/mentionable.rb +++ b/app/models/concerns/mentionable.rb @@ -49,12 +49,16 @@ module Mentionable matches = mentionable_text.scan(/@[a-zA-Z][a-zA-Z0-9_\-\.]*/) matches.each do |match| identifier = match.delete "@" - if has_project - id = project.team.members.find_by(username: identifier).try(:id) + if identifier == "all" + users += project.team.members.flatten else - id = User.where(username: identifier).pluck(:id).first + if has_project + id = project.team.members.find_by(username: identifier).try(:id) + else + id = User.find_by(username: identifier).try(:id) + end + users << User.find(id) unless id.blank? end - users << User.find(id) unless id.blank? end users.uniq end diff --git a/app/models/event.rb b/app/models/event.rb index b88635ccb59..1a8d55c54b4 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -41,6 +41,9 @@ class Event < ActiveRecord::Base # For Hash only serialize :data + # Callbacks + after_create :reset_project_activity + # Scopes scope :recent, -> { order("created_at DESC") } scope :code_push, -> { where(action: PUSHED) } @@ -303,4 +306,10 @@ class Event < ActiveRecord::Base target.respond_to? :title end end + + def reset_project_activity + if project + project.update_column(:last_activity_at, self.created_at) + end + end end diff --git a/app/models/note.rb b/app/models/note.rb index 659cd752071..94d45aa43db 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -57,6 +57,7 @@ class Note < ActiveRecord::Base serialize :st_diff before_create :set_diff, if: ->(n) { n.line_code.present? } + after_update :set_references class << self def create_status_change_note(noteable, project, author, status, source) @@ -178,10 +179,28 @@ class Note < ActiveRecord::Base @diff ||= Gitlab::Git::Diff.new(st_diff) if st_diff.respond_to?(:map) end + # Check if such line of code exists in merge request diff + # If exists - its active discussion + # If not - its outdated diff def active? - # TODO: determine if discussion is outdated - # according to recent MR diff or not - true + return true unless self.diff + + noteable.diffs.each do |mr_diff| + next unless mr_diff.new_path == self.diff.new_path + + Gitlab::DiffParser.new(mr_diff.diff.lines.to_a, mr_diff.new_path). + each do |full_line, type, line_code, line_new, line_old| + if full_line == diff_line + return true + end + end + end + + false + end + + def outdated? + !active? end def diff_file_index @@ -314,4 +333,8 @@ class Note < ActiveRecord::Base order('id DESC').limit(100). update_all(updated_at: Time.now) end + + def set_references + notice_added_references(project, author) + end end diff --git a/app/models/repository.rb b/app/models/repository.rb index 00a1032b6c4..22c16abe480 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -128,6 +128,7 @@ class Repository Rails.cache.delete(cache_key(:commit_count)) Rails.cache.delete(cache_key(:graph_log)) Rails.cache.delete(cache_key(:readme)) + Rails.cache.delete(cache_key(:version)) Rails.cache.delete(cache_key(:contribution_guide)) end @@ -156,12 +157,24 @@ class Repository Gitlab::Git::Blob.find(self, sha, path) end + def blob_by_oid(oid) + Gitlab::Git::Blob.raw(self, oid) + end + def readme Rails.cache.fetch(cache_key(:readme)) do tree(:head).readme end end + def version + Rails.cache.fetch(cache_key(:version)) do + tree(:head).blobs.find do |file| + file.name.downcase == 'version' + end + end + end + def contribution_guide Rails.cache.fetch(cache_key(:contribution_guide)) do tree(:head).contribution_guide diff --git a/app/models/user.rb b/app/models/user.rb index 2352f8c050b..63d819a0f36 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -132,6 +132,10 @@ class User < ActiveRecord::Base before_validation :sanitize_attrs before_save :ensure_authentication_token + after_save :ensure_namespace_correct + after_create :post_create_hook + after_destroy :post_destroy_hook + alias_attribute :private_token, :authentication_token @@ -368,7 +372,7 @@ class User < ActiveRecord::Base end def several_namespaces? - owned_groups.any? + owned_groups.any? || masters_groups.any? end def namespace_id @@ -490,4 +494,36 @@ class User < ActiveRecord::Base GravatarService.new.execute(email, size) end end + + def ensure_namespace_correct + # Ensure user has namespace + self.create_namespace!(path: self.username, name: self.username) unless self.namespace + + if self.username_changed? + self.namespace.update_attributes(path: self.username, name: self.username) + end + end + + def post_create_hook + log_info("User \"#{self.name}\" (#{self.email}) was created") + notification_service.new_user(self) + system_hook_service.execute_hooks_for(self, :create) + end + + def post_destroy_hook + log_info("User \"#{self.name}\" (#{self.email}) was removed") + system_hook_service.execute_hooks_for(self, :destroy) + end + + def notification_service + NotificationService.new + end + + def log_info message + Gitlab::AppLogger.info message + end + + def system_hook_service + SystemHooksService.new + end end diff --git a/app/models/users_group.rb b/app/models/users_group.rb index 5f8b832d51e..242c8abb3ca 100644 --- a/app/models/users_group.rb +++ b/app/models/users_group.rb @@ -33,6 +33,9 @@ class UsersGroup < ActiveRecord::Base scope :with_group, ->(group) { where(group_id: group.id) } scope :with_user, ->(user) { where(user_id: user.id) } + after_create :notify_create + after_update :notify_update + validates :group_access, inclusion: { in: UsersGroup.group_access_roles.values }, presence: true validates :user_id, presence: true validates :group_id, presence: true @@ -43,4 +46,18 @@ class UsersGroup < ActiveRecord::Base def access_field group_access end + + def notify_create + notification_service.new_group_member(self) + end + + def notify_update + if group_access_changed? + notification_service.update_group_member(self) + end + end + + def notification_service + NotificationService.new + end end diff --git a/app/models/users_project.rb b/app/models/users_project.rb index e8810f56def..6495bed4e61 100644 --- a/app/models/users_project.rb +++ b/app/models/users_project.rb @@ -37,6 +37,10 @@ class UsersProject < ActiveRecord::Base scope :in_projects, ->(projects) { where(project_id: projects.map { |p| p.id }) } scope :with_user, ->(user) { where(user_id: user.id) } + after_create :post_create_hook + after_update :post_update_hook + after_destroy :post_destroy_hook + class << self # Add users to project teams with passed access option @@ -114,4 +118,37 @@ class UsersProject < ActiveRecord::Base def owner? project.owner == user end + + def post_create_hook + Event.create( + project_id: self.project.id, + action: Event::JOINED, + author_id: self.user.id + ) + + notification_service.new_team_member(self) + system_hook_service.execute_hooks_for(self, :create) + end + + def post_update_hook + notification_service.update_team_member(self) if self.project_access_changed? + end + + def post_destroy_hook + Event.create( + project_id: self.project.id, + action: Event::LEFT, + author_id: self.user.id + ) + + system_hook_service.execute_hooks_for(self, :destroy) + end + + def notification_service + NotificationService.new + end + + def system_hook_service + SystemHooksService.new + end end diff --git a/app/observers/base_observer.rb b/app/observers/base_observer.rb deleted file mode 100644 index 260d1f05db3..00000000000 --- a/app/observers/base_observer.rb +++ /dev/null @@ -1,13 +0,0 @@ -class BaseObserver < ActiveRecord::Observer - def notification - NotificationService.new - end - - def event_service - EventCreateService.new - end - - def log_info message - Gitlab::AppLogger.info message - end -end diff --git a/app/observers/note_observer.rb b/app/observers/note_observer.rb deleted file mode 100644 index bb0b0876eca..00000000000 --- a/app/observers/note_observer.rb +++ /dev/null @@ -1,20 +0,0 @@ -class NoteObserver < BaseObserver - def after_create(note) - notification.new_note(note) - - # Skip system notes, like status changes and cross-references. - unless note.system - event_service.leave_note(note, note.author) - - # Create a cross-reference note if this Note contains GFM that names an - # issue, merge request, or commit. - note.references.each do |mentioned| - Note.create_cross_reference_note(mentioned, note.noteable, note.author, note.project) - end - end - end - - def after_update(note) - note.notice_added_references(note.project, note.author) - end -end diff --git a/app/observers/project_activity_cache_observer.rb b/app/observers/project_activity_cache_observer.rb deleted file mode 100644 index 96ced90db80..00000000000 --- a/app/observers/project_activity_cache_observer.rb +++ /dev/null @@ -1,8 +0,0 @@ -class ProjectActivityCacheObserver < BaseObserver - observe :event - - def after_create(event) - event.project.update_column(:last_activity_at, event.created_at) if event.project - end -end - diff --git a/app/observers/project_observer.rb b/app/observers/project_observer.rb deleted file mode 100644 index ad41ddad58f..00000000000 --- a/app/observers/project_observer.rb +++ /dev/null @@ -1,30 +0,0 @@ -class ProjectObserver < BaseObserver - def after_create(project) - log_info("#{project.owner.name} created a new project \"#{project.name_with_namespace}\"") - end - - def after_update(project) - project.send_move_instructions if project.namespace_id_changed? - project.rename_repo if project.path_changed? - end - - def before_destroy(project) - project.repository.expire_cache unless project.empty_repo? - end - - def after_destroy(project) - GitlabShellWorker.perform_async( - :remove_repository, - project.path_with_namespace - ) - - GitlabShellWorker.perform_async( - :remove_repository, - project.path_with_namespace + ".wiki" - ) - - project.satellite.destroy - - log_info("Project \"#{project.name}\" was removed") - end -end diff --git a/app/observers/system_hook_observer.rb b/app/observers/system_hook_observer.rb deleted file mode 100644 index 80de177b9a2..00000000000 --- a/app/observers/system_hook_observer.rb +++ /dev/null @@ -1,17 +0,0 @@ -class SystemHookObserver < BaseObserver - observe :user, :project, :users_project - - def after_create(model) - system_hook_service.execute_hooks_for(model, :create) - end - - def after_destroy(model) - system_hook_service.execute_hooks_for(model, :destroy) - end - - private - - def system_hook_service - SystemHooksService.new - end -end diff --git a/app/observers/user_observer.rb b/app/observers/user_observer.rb deleted file mode 100644 index fba0f1006d9..00000000000 --- a/app/observers/user_observer.rb +++ /dev/null @@ -1,20 +0,0 @@ -class UserObserver < BaseObserver - def after_create(user) - log_info("User \"#{user.name}\" (#{user.email}) was created") - - notification.new_user(user) - end - - def after_destroy user - log_info("User \"#{user.name}\" (#{user.email}) was removed") - end - - def after_save user - # Ensure user has namespace - user.create_namespace!(path: user.username, name: user.username) unless user.namespace - - if user.username_changed? - user.namespace.update_attributes(path: user.username, name: user.username) - end - end -end diff --git a/app/observers/users_group_observer.rb b/app/observers/users_group_observer.rb deleted file mode 100644 index 42a05b5e177..00000000000 --- a/app/observers/users_group_observer.rb +++ /dev/null @@ -1,9 +0,0 @@ -class UsersGroupObserver < BaseObserver - def after_create(membership) - notification.new_group_member(membership) - end - - def after_update(membership) - notification.update_group_member(membership) if membership.group_access_changed? - end -end diff --git a/app/observers/users_project_observer.rb b/app/observers/users_project_observer.rb deleted file mode 100644 index 44c72b30187..00000000000 --- a/app/observers/users_project_observer.rb +++ /dev/null @@ -1,23 +0,0 @@ -class UsersProjectObserver < BaseObserver - def after_create(users_project) - Event.create( - project_id: users_project.project.id, - action: Event::JOINED, - author_id: users_project.user.id - ) - - notification.new_team_member(users_project) - end - - def after_update(users_project) - notification.update_team_member(users_project) if users_project.project_access_changed? - end - - def after_destroy(users_project) - Event.create( - project_id: users_project.project.id, - action: Event::LEFT, - author_id: users_project.user.id - ) - end -end diff --git a/app/services/base_service.rb b/app/services/base_service.rb index 9ad80923152..31b38aca530 100644 --- a/app/services/base_service.rb +++ b/app/services/base_service.rb @@ -28,4 +28,8 @@ class BaseService def log_info message Gitlab::AppLogger.info message end + + def system_hook_service + SystemHooksService.new + end end diff --git a/app/services/issues/base_service.rb b/app/services/issues/base_service.rb index eac774210ae..71b9ffc3489 100644 --- a/app/services/issues/base_service.rb +++ b/app/services/issues/base_service.rb @@ -7,8 +7,11 @@ module Issues 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) + def execute_hooks(issue, action = 'open') + issue_data = issue.to_hook_data + issue_url = Gitlab::UrlBuilder.new(:issue).build(issue.id) + issue_data[:object_attributes].merge!(url: issue_url, action: action) + issue.project.execute_hooks(issue_data, :issue_hooks) end def create_milestone_note(issue) diff --git a/app/services/issues/close_service.rb b/app/services/issues/close_service.rb index 85c0226ccab..ffed13a12e1 100644 --- a/app/services/issues/close_service.rb +++ b/app/services/issues/close_service.rb @@ -5,7 +5,7 @@ module Issues notification_service.close_issue(issue, current_user) event_service.close_issue(issue, current_user) create_note(issue, commit) - execute_hooks(issue) + execute_hooks(issue, 'close') end issue diff --git a/app/services/issues/create_service.rb b/app/services/issues/create_service.rb index d6137833bb9..6d05d417f1b 100644 --- a/app/services/issues/create_service.rb +++ b/app/services/issues/create_service.rb @@ -8,7 +8,7 @@ module Issues 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) + execute_hooks(issue, 'open') end issue diff --git a/app/services/issues/reopen_service.rb b/app/services/issues/reopen_service.rb index a931398aff6..b23d56258a8 100644 --- a/app/services/issues/reopen_service.rb +++ b/app/services/issues/reopen_service.rb @@ -4,7 +4,7 @@ module Issues if issue.reopen event_service.reopen_issue(issue, current_user) create_note(issue) - execute_hooks(issue) + execute_hooks(issue, 'reopen') end issue diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb index b4a742b0037..169e1e95b4b 100644 --- a/app/services/issues/update_service.rb +++ b/app/services/issues/update_service.rb @@ -23,7 +23,7 @@ module Issues end issue.notice_added_references(issue.project, current_user) - execute_hooks(issue) + execute_hooks(issue, 'update') end issue diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb index ff6dfb61139..f64006a4edc 100644 --- a/app/services/notes/create_service.rb +++ b/app/services/notes/create_service.rb @@ -1,10 +1,25 @@ module Notes class CreateService < BaseService def execute - note = project.notes.new(params[:note]) + note = project.notes.new(params) note.author = current_user note.system = false - note.save + + if note.save + notification_service.new_note(note) + + # Skip system notes, like status changes and cross-references. + unless note.system + event_service.leave_note(note, note.author) + + # Create a cross-reference note if this Note contains GFM that names an + # issue, merge request, or commit. + note.references.each do |mentioned| + Note.create_cross_reference_note(mentioned, note.noteable, note.author, note.project) + end + end + end + note end end diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb index 3d2b9bf4875..dfadcfd296a 100644 --- a/app/services/projects/create_service.rb +++ b/app/services/projects/create_service.rb @@ -51,6 +51,9 @@ module Projects @project.creator = current_user if @project.save + log_info("#{@project.owner.name} created a new project \"#{@project.name_with_namespace}\"") + system_hook_service.execute_hooks_for(@project, :create) + unless @project.group @project.users_projects.create( project_access: UsersProject::MASTER, diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb new file mode 100644 index 00000000000..7e1d753b021 --- /dev/null +++ b/app/services/projects/destroy_service.rb @@ -0,0 +1,28 @@ +module Projects + class DestroyService < BaseService + def execute + return false unless can?(current_user, :remove_project, project) + + project.team.truncate + project.repository.expire_cache unless project.empty_repo? + + if project.destroy + GitlabShellWorker.perform_async( + :remove_repository, + project.path_with_namespace + ) + + GitlabShellWorker.perform_async( + :remove_repository, + project.path_with_namespace + ".wiki" + ) + + project.satellite.destroy + + log_info("Project \"#{project.name}\" was removed") + system_hook_service.execute_hooks_for(project, :destroy) + true + end + end + end +end diff --git a/app/services/projects/participants_service.rb b/app/services/projects/participants_service.rb new file mode 100644 index 00000000000..c4d2c0963b7 --- /dev/null +++ b/app/services/projects/participants_service.rb @@ -0,0 +1,43 @@ +module Projects + class ParticipantsService < BaseService + def initialize(project) + @project = project + end + + def execute(note_type, note_id) + participating = if note_type && note_id + participants_in(note_type, note_id) + else + [] + end + team_members = sorted(@project.team.members) + participants = all_members + team_members + participating + participants.uniq + end + + def participants_in(type, id) + users = case type + when "Issue" + issue = @project.issues.find_by_iid(id) + issue ? issue.participants : [] + when "MergeRequest" + merge_request = @project.merge_requests.find_by_iid(id) + merge_request ? merge_request.participants : [] + when "Commit" + author_ids = Note.for_commit_id(id).pluck(:author_id).uniq + User.where(id: author_ids) + else + [] + end + sorted(users) + end + + def sorted(users) + users.uniq.to_a.compact.sort_by(&:username).map { |user| { username: user.username, name: user.name } } + end + + def all_members + [{ username: "all", name: "Project and Group Members" }] + end + end +end diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb index d9d371da5c4..551a3653cad 100644 --- a/app/services/projects/update_service.rb +++ b/app/services/projects/update_service.rb @@ -13,7 +13,15 @@ module Projects project.change_head(new_branch) end - project.update_attributes(params[:project], as: role) + if project.update_attributes(params[:project], as: role) + if project.previous_changes.include?('namespace_id') + project.send_move_instructions + end + + if project.previous_changes.include?('path') + project.rename_repo + end + end end end end diff --git a/app/services/search/global_service.rb b/app/services/search/global_service.rb index 21214511182..d213e1375e0 100644 --- a/app/services/search/global_service.rb +++ b/app/services/search/global_service.rb @@ -28,6 +28,7 @@ module Search projects: [], merge_requests: [], issues: [], + notes: [], total_results: 0, } end diff --git a/app/services/search/project_service.rb b/app/services/search/project_service.rb index 3ebaafc752c..493dee95f90 100644 --- a/app/services/search/project_service.rb +++ b/app/services/search/project_service.rb @@ -18,8 +18,9 @@ module Search result[:total_results] = blobs.total_count else result[:merge_requests] = project.merge_requests.search(query).order('updated_at DESC').limit(20) - result[:issues] = project.issues.search(query).order('updated_at DESC').limit(20) - result[:total_results] = %w(issues merge_requests).sum { |items| result[items.to_sym].size } + result[:issues] = project.issues.where("title like :query OR description like :query ", query: "%#{query}%").order('updated_at DESC').limit(20) + result[:notes] = Note.where(noteable_type: 'issue').where(project_id: project.id).where("note like :query", query: "%#{query}%").order('updated_at DESC').limit(20) + result[:total_results] = %w(issues merge_requests notes).sum { |items| result[items.to_sym].size } end result @@ -30,6 +31,7 @@ module Search merge_requests: [], issues: [], blobs: [], + notes: [], total_results: 0, } end diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml index a255c64fc27..3c30ccd78b3 100644 --- a/app/views/admin/users/show.html.haml +++ b/app/views/admin/users/show.html.haml @@ -44,6 +44,8 @@ %li %span.light Secondary email: %strong= email.email + = link_to remove_email_admin_user_path(@user, email), data: { confirm: "Are you sure you want to remove #{email.email}?" }, method: :delete, class: "btn-tiny btn btn-remove pull-right", title: 'Remove secondary email', id: "remove_email_#{email.id}" do + %i.icon-remove %li %span.light Can create groups: diff --git a/app/views/dashboard/show.html.haml b/app/views/dashboard/show.html.haml index 306b71717ef..6a08b1aa640 100644 --- a/app/views/dashboard/show.html.haml +++ b/app/views/dashboard/show.html.haml @@ -1,8 +1,8 @@ - if @has_authorized_projects .dashboard.row - .activities.col-md-8 + %section.activities.col-md-8 = render 'activities' - .side.col-md-4.left.responsive-side + %aside.side.col-md-4.left.responsive-side = render 'sidebar' .fixed.sidebar-expand-button.hidden-lg.hidden-md diff --git a/app/views/devise/sessions/_new_base.html.haml b/app/views/devise/sessions/_new_base.html.haml index a2f85fa3fe2..989fcb4a63f 100644 --- a/app/views/devise/sessions/_new_base.html.haml +++ b/app/views/devise/sessions/_new_base.html.haml @@ -7,8 +7,8 @@ = f.check_box :remember_me %span Remember me %div + = hidden_field_tag 'return_to', params[:return_to] = f.submit "Sign in", class: "btn-create btn" + .pull-right = link_to "Forgot your password?", new_password_path(resource_name), class: "btn" - - diff --git a/app/views/errors/access_denied.html.haml b/app/views/errors/access_denied.html.haml index e005a7c4409..a1d8664c4ce 100644 --- a/app/views/errors/access_denied.html.haml +++ b/app/views/errors/access_denied.html.haml @@ -1,5 +1,5 @@ -%h1.http_status_code 403 -%h3.page-title Access Denied +%h1 403 +%h3 Access Denied %hr %p You are not allowed to access this page. %p Read more about project permissions #{link_to "here", help_page_path("permissions", "permissions"), class: "vlink"} diff --git a/app/views/errors/encoding.html.haml b/app/views/errors/encoding.html.haml index 7021f06dd7f..64c7451a8da 100644 --- a/app/views/errors/encoding.html.haml +++ b/app/views/errors/encoding.html.haml @@ -1,4 +1,4 @@ -%h1.http_status_code 500 -%h3.page-title Encoding Error +%h1 500 +%h3 Encoding Error %hr %p Page can't be loaded because of an encoding error. diff --git a/app/views/errors/git_not_found.html.haml b/app/views/errors/git_not_found.html.haml index d8ed7773207..189e53bca55 100644 --- a/app/views/errors/git_not_found.html.haml +++ b/app/views/errors/git_not_found.html.haml @@ -1,5 +1,5 @@ -%h1.http_status_code 404 -%h3.page-title Git Resource Not found +%h1 404 +%h3 Git Resource Not found %hr %p Application can't get access to some branch or commit in your repository. It diff --git a/app/views/errors/not_found.html.haml b/app/views/errors/not_found.html.haml index 4b97ddefc72..7bf88f592cf 100644 --- a/app/views/errors/not_found.html.haml +++ b/app/views/errors/not_found.html.haml @@ -1,4 +1,4 @@ -%h1.http_status_code 404 -%h3.page-title The resource you were looking for doesn't exist. +%h1 404 +%h3 The resource you were looking for doesn't exist. %hr %p You may have mistyped the address or the page may have moved. diff --git a/app/views/errors/omniauth_error.html.haml b/app/views/errors/omniauth_error.html.haml new file mode 100644 index 00000000000..f3c8221a9d9 --- /dev/null +++ b/app/views/errors/omniauth_error.html.haml @@ -0,0 +1,12 @@ +%h1 422 +%h3 Sign-in using #{@provider} auth failed +%hr +%p Sign-in failed because #{@error}. +%p There are couple of steps you can take: + +%ul + %li Try logging in using your email + %li Try logging in using your username + %li If you have forgotten your password, try recovering it using #{ link_to "Password recovery", new_password_path(resource_name) } + +%p If none of the options work, try contacting the GitLab administrator. diff --git a/app/views/groups/_filter.html.haml b/app/views/groups/_filter.html.haml deleted file mode 100644 index fe8c0669c0e..00000000000 --- a/app/views/groups/_filter.html.haml +++ /dev/null @@ -1,29 +0,0 @@ -= form_tag group_filter_path(entity), method: 'get' do - %fieldset - %ul.nav.nav-pills.nav-stacked - %li{class: ("active" if !params[:status])} - = link_to group_filter_path(entity, status: nil) do - Open - %li{class: ("active" if params[:status] == 'closed')} - = link_to group_filter_path(entity, status: 'closed') do - Closed - %li{class: ("active" if params[:status] == 'all')} - = link_to group_filter_path(entity, status: 'all') do - All - - %fieldset - %legend Projects: - %ul.nav.nav-pills.nav-stacked - - @projects.each do |project| - - unless entities_per_project(project, entity).zero? - %li{class: ("active" if params[:project_id] == project.id.to_s)} - = link_to group_filter_path(entity, project_id: project.id) do - = project.name_with_namespace - %small.pull-right= entities_per_project(project, entity) - - if @projects.blank? - .nothing-here-block This group has no projects yet - - %fieldset - %hr - = link_to "Reset", group_filter_path(entity), class: 'btn pull-right' - diff --git a/app/views/groups/new.html.haml b/app/views/groups/new.html.haml index ebf5e8571aa..cdc087f949a 100644 --- a/app/views/groups/new.html.haml +++ b/app/views/groups/new.html.haml @@ -6,12 +6,12 @@ = f.label :name, class: 'control-label' do Group name .col-sm-10 - = f.text_field :name, placeholder: "Ex. OpenSource", class: "form-control" + = f.text_field :name, placeholder: "Ex. OpenSource", class: "form-control", tabindex: 1, autofocus: true .form-group.group-description-holder = f.label :description, "Details", class: 'control-label' .col-sm-10 - = f.text_area :description, maxlength: 250, class: "form-control js-gfm-input", rows: 4 + = f.text_area :description, maxlength: 250, class: "form-control js-gfm-input", rows: 4, tabindex: 2 .form-group.group-description-holder = f.label :avatar, "Group avatar", class: 'control-label' @@ -35,6 +35,4 @@ %li Existing projects may be moved into a group .form-actions - = f.submit 'Create group', class: "btn btn-create" - - + = f.submit 'Create group', class: "btn btn-create", tabindex: 3 diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index 06183dd74a9..6db393c882c 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -1,5 +1,5 @@ .dashboard - .activities.col-md-8.hidden-sm.hidden-xs + %section.activities.col-md-8.hidden-sm.hidden-xs - if current_user = render "events/event_last_push", event: @last_push = link_to dashboard_path, class: 'btn btn-tiny' do @@ -16,7 +16,7 @@ - else .nothing-here-block Project activity will be displayed here = spinner - .side.col-md-4 + %aside.side.col-md-4 .light-well.append-bottom-20 = image_tag group_icon(@group.path), class: "avatar s90" .clearfix.light diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index d9a7a2d31cf..0c27f679dee 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -1,5 +1,14 @@ %head %meta{charset: "utf-8"} + + -# Go repository retrieval support + -# Need to be the fist thing in the head + -# Since Go is using an XML parser to process HTML5 + -# https://github.com/gitlabhq/gitlabhq/pull/5958#issuecomment-45397555 + - if controller_name == 'projects' && action_name == 'show' + %meta{name: "go-import", content: "#{@project.web_url_without_protocol} git #{@project.web_url}.git"} + %meta{content: "GitLab Community Edition", name: "description"} + %title = "#{title} | " if defined?(title) GitLab @@ -24,8 +33,3 @@ = auto_discovery_link_tag(:atom, project_commits_url(@project, @ref, format: :atom, private_token: current_user.private_token), title: "Recent commits to #{@project.name}:#{@ref}") - if current_controller?(:issues) = auto_discovery_link_tag(:atom, project_issues_url(@project, :atom, private_token: current_user.private_token), title: "#{@project.name} issues") - - -# Go repository retrieval support. - - if controller_name == 'projects' && action_name == 'show' - %meta{name: "go-import", content: "#{@project.web_url_without_protocol} git #{@project.web_url}.git"} - diff --git a/app/views/layouts/_public_head_panel.html.haml b/app/views/layouts/_public_head_panel.html.haml index 63992a22f32..25984df0444 100644 --- a/app/views/layouts/_public_head_panel.html.haml +++ b/app/views/layouts/_public_head_panel.html.haml @@ -13,10 +13,10 @@ %i.icon-reorder .pull-right.hidden-xs - = link_to "Sign in", new_session_path(:user), class: 'btn btn-sign-in btn-new' + = link_to "Sign in", new_session_path(:user, return_to: request.fullpath), class: 'btn btn-sign-in btn-new' .navbar-collapse.collapse %ul.nav.navbar-nav %li.visible-xs - = link_to "Sign in", new_session_path(:user) + = link_to "Sign in", new_session_path(:user, return_to: request.fullpath) diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml index a0e55b21c32..caf0e39234a 100644 --- a/app/views/layouts/_search.html.haml +++ b/app/views/layouts/_search.html.haml @@ -1,6 +1,6 @@ .search = form_tag search_path, method: :get, class: 'navbar-form pull-left' do |f| - = text_field_tag "search", nil, placeholder: search_placeholder, class: "search-input" + = search_field_tag "search", nil, placeholder: search_placeholder, class: "search-input" = hidden_field_tag :group_id, @group.try(:id) - if @project && @project.persisted? = hidden_field_tag :project_id, @project.id diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml index 5d93ffa50ad..36c682c48a6 100644 --- a/app/views/layouts/devise.html.haml +++ b/app/views/layouts/devise.html.haml @@ -5,7 +5,7 @@ = render "layouts/flash" .container .content - %center + .center %h1 GitLab %p.light GitLab is open source software to collaborate on code. diff --git a/app/views/layouts/errors.html.haml b/app/views/layouts/errors.html.haml index df2350b1535..d0e276d751a 100644 --- a/app/views/layouts/errors.html.haml +++ b/app/views/layouts/errors.html.haml @@ -4,7 +4,6 @@ %body{class: "#{app_theme} application"} = render "layouts/head_panel", title: "" if current_user = render "layouts/flash" - .container - .content - %center.padded.prepend-top-20 - = yield + .container.navless-container + .error-page + = yield diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml index 48c569f8684..c57216f01c8 100644 --- a/app/views/layouts/nav/_admin.html.haml +++ b/app/views/layouts/nav/_admin.html.haml @@ -1,13 +1,13 @@ %ul = nav_link(controller: :dashboard, html_options: {class: 'home'}) do = link_to admin_root_path, title: "Stats" do - %i.icon-home + Overview = nav_link(controller: :projects) do = link_to "Projects", admin_projects_path - = nav_link(controller: :groups) do - = link_to "Groups", admin_groups_path = nav_link(controller: :users) do = link_to "Users", admin_users_path + = nav_link(controller: :groups) do + = link_to "Groups", admin_groups_path = nav_link(controller: :logs) do = link_to "Logs", admin_logs_path = nav_link(controller: :broadcast_messages) do diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml index 12fd49e609f..a300bbc1904 100644 --- a/app/views/layouts/nav/_dashboard.html.haml +++ b/app/views/layouts/nav/_dashboard.html.haml @@ -1,7 +1,7 @@ %ul = nav_link(path: 'dashboard#show', html_options: {class: 'home'}) do = link_to root_path, title: "Home" do - %i.icon-home + Activity = nav_link(path: 'dashboard#projects') do = link_to projects_dashboard_path do Projects diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml index 36b102dc25a..d306e1eeb54 100644 --- a/app/views/layouts/nav/_group.html.haml +++ b/app/views/layouts/nav/_group.html.haml @@ -1,7 +1,7 @@ %ul = nav_link(path: 'groups#show', html_options: {class: 'home'}) do = link_to group_path(@group), title: "Home" do - %i.icon-home + Activity = nav_link(path: 'groups#issues') do = link_to issues_group_path(@group) do Issues diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml index 35d0d417502..1de5ee99cf4 100644 --- a/app/views/layouts/nav/_profile.html.haml +++ b/app/views/layouts/nav/_profile.html.haml @@ -1,7 +1,7 @@ %ul = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do = link_to profile_path, title: "Profile" do - %i.icon-home + Profile = nav_link(controller: :accounts) do = link_to "Account", profile_account_path = nav_link(controller: :emails) do diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index dd48ff6ce38..69491c2529e 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -1,7 +1,7 @@ %ul = nav_link(path: 'projects#show', html_options: {class: "home"}) do = link_to project_path(@project), title: "Project" do - %i.icon-home + Activity - if project_nav_tab? :files = nav_link(controller: %w(tree blob blame edit_tree new_tree)) do diff --git a/app/views/profiles/keys/new.html.haml b/app/views/profiles/keys/new.html.haml index 126afa55540..46b7fc72869 100644 --- a/app/views/profiles/keys/new.html.haml +++ b/app/views/profiles/keys/new.html.haml @@ -5,7 +5,7 @@ = render 'form' :javascript - $('#key_key').on('keyup', function(){ + $('#key_key').on('focusout', function(){ var title = $('#key_title'), val = $('#key_key').val(), key_mail = val.match(/([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+|\.[a-zA-Z0-9._-]+)/gi); @@ -13,4 +13,4 @@ if( key_mail && key_mail.length > 0 && title.val() == '' ){ $('#key_title').val( key_mail ); } - }); + });
\ No newline at end of file diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index 9a4c13af975..e03a43cbf1f 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -68,12 +68,14 @@ %p.light - if @user.avatar? You can change your avatar here - %br - or remove the current avatar to revert to #{link_to "gravatar.com", "http://gravatar.com"} + - if Gitlab.config.gravatar.enabled + %br + or remove the current avatar to revert to #{link_to "gravatar.com", "http://gravatar.com"} - else You can upload an avatar here - %br - or change it at #{link_to "gravatar.com", "http://gravatar.com"} + - if Gitlab.config.gravatar.enabled + %br + or change it at #{link_to "gravatar.com", "http://gravatar.com"} %hr %a.choose-btn.btn.btn-small.js-choose-user-avatar-button %i.icon-paper-clip diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index 81bb0e20a35..2a97cb49039 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -21,6 +21,11 @@ - if can?(current_user, :admin_project, @project) – %strong= link_to 'Edit', edit_project_path + - elsif !@project.empty_repo? && @repository.readme + - readme = @repository.readme + – + = link_to project_blob_path(@project, tree_join(@repository.root_ref, readme.name)) do + = readme.name - unless empty_repo .col-md-5 diff --git a/app/views/projects/blob/_blob.html.haml b/app/views/projects/blob/_blob.html.haml index 87574d4648b..be785dacedb 100644 --- a/app/views/projects/blob/_blob.html.haml +++ b/app/views/projects/blob/_blob.html.haml @@ -20,7 +20,7 @@ = render blob_commit, project: @project %div#tree-content-holder.tree-content-holder - .file-holder + %article.file-holder .file-title.clearfix %i.icon-file %span.file_name diff --git a/app/views/projects/blob/_download.html.haml b/app/views/projects/blob/_download.html.haml index ff317f90209..cdbfee7cc68 100644 --- a/app/views/projects/blob/_download.html.haml +++ b/app/views/projects/blob/_download.html.haml @@ -1,5 +1,5 @@ .file-content.blob_file.blob-no-preview - %center + .center = link_to project_raw_path(@project, @id) do %h1.light %i.icon-download-alt diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index a0606662807..74146b5f196 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -9,11 +9,15 @@ = link_to "Browse Code »", project_tree_path(project, commit), class: "pull-right" .notes_count - - notes = project.notes.for_commit_id(commit.id) - - if notes.any? + - if @note_counts + - note_count = @note_counts.fetch(commit.id, 0) + - else + - notes = project.notes.for_commit_id(commit.id) + - note_count = notes.count + + - if note_count > 0 %span.label.label-gray - %i.icon-comment - = notes.count + %i.icon-comment= note_count - if commit.description? .commit-row-description.js-toggle-content diff --git a/app/views/projects/commits/_diffs.html.haml b/app/views/projects/commits/_diffs.html.haml index a62f50776fa..fcdb40468d9 100644 --- a/app/views/projects/commits/_diffs.html.haml +++ b/app/views/projects/commits/_diffs.html.haml @@ -45,7 +45,7 @@ - file = project.repository.blob_at(@commit.id, diff.new_path) - file = project.repository.blob_at(@commit.parent_id, diff.old_path) unless file - next unless file - .diff-file.js-toggle-container{id: "diff-#{i}"} + .diff-file{id: "diff-#{i}"} .diff-header{id: "file-path-#{hexdigest(diff.new_path || diff.old_path)}"} - if diff.deleted_file %span= diff.old_path @@ -61,7 +61,7 @@ %span.file-mode= "#{diff.a_mode} → #{diff.b_mode}" .diff-btn-group - = link_to "#", class: "js-toggle-button btn btn-small" do + = link_to "#", class: "js-toggle-diff-comments btn btn-small" do %i.icon-chevron-down Diff comments diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml index 88122a73bba..b232d2a6b26 100644 --- a/app/views/projects/compare/show.html.haml +++ b/app/views/projects/compare/show.html.haml @@ -33,7 +33,7 @@ - else .light-well - %center + .center %h4 There isn't anything to compare. %p.slead diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 4195a491a0f..6eaa3e7559a 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -183,7 +183,7 @@ .nothing-here-block Only project owner can remove a project .save-project-loader.hide - %center + .center %h2 %i.icon-spinner.icon-spin Saving project. diff --git a/app/views/projects/edit_tree/show.html.haml b/app/views/projects/edit_tree/show.html.haml index 32a6d4ef36e..16fc1ab1f91 100644 --- a/app/views/projects/edit_tree/show.html.haml +++ b/app/views/projects/edit_tree/show.html.haml @@ -18,7 +18,7 @@ .file-content.code %pre.js-edit-mode-pane#editor= @blob.data .js-edit-mode-pane#preview.hide - %center + .center %h2 %i.icon-spinner.icon-spin diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml index 27348232ba2..8e4548f26d0 100644 --- a/app/views/projects/graphs/show.html.haml +++ b/app/views/projects/graphs/show.html.haml @@ -1,5 +1,5 @@ .loading-graph - %center + .center %h3.page-title %i.icon-spinner.icon-spin Building repository graph. diff --git a/app/views/projects/import.html.haml b/app/views/projects/import.html.haml index d11372be61b..f883b24fc21 100644 --- a/app/views/projects/import.html.haml +++ b/app/views/projects/import.html.haml @@ -1,6 +1,6 @@ - if @project.import_in_progress? .save-project-loader - %center + .center %h2 %i.icon-spinner.icon-spin Import in progress. @@ -11,7 +11,7 @@ - elsif @project.import_failed? .save-project-loader - %center + .center %h2 Import failed. Retry? %hr diff --git a/app/views/projects/merge_requests/_new_compare.html.haml b/app/views/projects/merge_requests/_new_compare.html.haml index f8f14a71a11..18e3f419c72 100644 --- a/app/views/projects/merge_requests/_new_compare.html.haml +++ b/app/views/projects/merge_requests/_new_compare.html.haml @@ -39,7 +39,7 @@ %p We can't compare selected branches. It may be because of huge diff or satellite timeout. Please try again or select different branches. - else .light-well - %center + .center %h4 There isn't anything to merge. %p.slead diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 486454793ba..8fca9f0212b 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -31,7 +31,8 @@ = render "projects/merge_requests/show/diffs" .notes.tab-content.voting_notes#notes{ class: (controller.action_name == 'show') ? "" : "hide" } = render "projects/notes/notes_with_form" - .status + .mr-loading-status + = spinner :javascript var merge_request; diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index 5d5637c1588..7efaf5a087b 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -72,7 +72,7 @@ Create a group .save-project-loader.hide - %center + .center %h2 %i.icon-spinner.icon-spin Creating project & repository. diff --git a/app/views/projects/notes/_commit_discussion.html.haml b/app/views/projects/notes/_commit_discussion.html.haml new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/app/views/projects/notes/_commit_discussion.html.haml diff --git a/app/views/projects/notes/_diff_notes_with_reply.html.haml b/app/views/projects/notes/_diff_notes_with_reply.html.haml index 9acadc6a14e..79a66eff129 100644 --- a/app/views/projects/notes/_diff_notes_with_reply.html.haml +++ b/app/views/projects/notes/_diff_notes_with_reply.html.haml @@ -1,7 +1,7 @@ - note = notes.first # example note -# Check if line want not changed since comment was left - if !defined?(line) || line == note.diff_line - %tr.notes_holder.js-toggle-content + %tr.notes_holder %td.notes_line{ colspan: 2 } %span.btn.disabled %i.icon-comment @@ -9,5 +9,5 @@ %td.notes_content %ul.notes{ rel: note.discussion_id } = render notes - - = render "projects/notes/discussion_reply_button", note: note + .discussion-reply-holder + = link_to_reply_diff(note) diff --git a/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml b/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml index 399ce30d1a9..8adf903a9a1 100644 --- a/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml +++ b/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml @@ -2,7 +2,7 @@ - note2 = notes2.first # example note -# Check if line want not changed since comment was left /- if !defined?(line) || line == note.diff_line -%tr.notes_holder.js-toggle-content +%tr.notes_holder - if note1 %td.notes_line %span.btn.disabled @@ -12,7 +12,8 @@ %ul.notes{ rel: note1.discussion_id } = render notes1 - = render "projects/notes/discussion_reply_button", note: note1 + .discussion-reply-holder + = link_to_reply_diff(note1) - else %td= "" %td= "" @@ -26,7 +27,8 @@ %ul.notes{ rel: note2.discussion_id } = render notes2 - = render "projects/notes/discussion_reply_button", note: note2 + .discussion-reply-holder + = link_to_reply_diff(note2) - else %td= "" %td= "" diff --git a/app/views/projects/notes/_discussion.html.haml b/app/views/projects/notes/_discussion.html.haml index 78793eb860f..8c7964cbf3e 100644 --- a/app/views/projects/notes/_discussion.html.haml +++ b/app/views/projects/notes/_discussion.html.haml @@ -1,46 +1,8 @@ - note = discussion_notes.first -.discussion.js-toggle-container{ class: note.discussion_id } - .discussion-header - .discussion-actions - = link_to "#", class: "js-toggle-button" do - %i.icon-chevron-up - Show/hide discussion - = image_tag avatar_icon(note.author_email), class: "avatar s32" - %div - = link_to_member(@project, note.author, avatar: false) - - if note.for_merge_request? - - if note.diff - started a discussion on this merge request diff - = link_to_merge_request_diff_line_note(note) - - else - started - %strong - %i.icon-remove - outdated - discussion on this merge request diff - - elsif note.for_commit? - started a discussion on commit - #{link_to note.noteable.short_id, project_commit_path(note.project, note.noteable)} - = link_to_commit_diff_line_note(note) if note.for_diff_line? - - else - %cite.cgray started a discussion - %div - - last_note = discussion_notes.last - last updated by - = link_to_member(@project, last_note.author, avatar: false) - %span.discussion-last-update - #{time_ago_with_tooltip(last_note.updated_at, 'bottom', 'discussion_updated_ago')} - .discussion-body.js-toggle-content - - if note.for_diff_line? - - if note.active? - = render "projects/notes/discussion_diff", discussion_notes: discussion_notes, note: note - - else - = link_to 'show outdated discussion', '#', class: 'js-show-outdated-discussion' - %div.hide.outdated-discussion - .notes{ rel: discussion_notes.first.discussion_id } - = render discussion_notes - - - else - .notes{ rel: discussion_notes.first.discussion_id } - = render discussion_notes - = render "projects/notes/discussion_reply_button", note: discussion_notes.first +- if note.for_merge_request? + - if note.outdated? + = render "projects/notes/discussions/outdated", discussion_notes: discussion_notes + - else + = render "projects/notes/discussions/active", discussion_notes: discussion_notes +- else + = render "projects/notes/discussions/commit", discussion_notes: discussion_notes diff --git a/app/views/projects/notes/_discussion_reply_button.html.haml b/app/views/projects/notes/_discussion_reply_button.html.haml deleted file mode 100644 index d1c5ccc29db..00000000000 --- a/app/views/projects/notes/_discussion_reply_button.html.haml +++ /dev/null @@ -1,10 +0,0 @@ -= link_to "javascript:;", - class: "btn reply-btn js-discussion-reply-button", - data: { noteable_type: note.noteable_type, - noteable_id: note.noteable_id, - commit_id: note.commit_id, - line_code: note.line_code, - discussion_id: note.discussion_id }, - title: "Add a reply" do - %i.icon-comment - Reply diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml index b62aada3a9a..5ebafb13f1c 100644 --- a/app/views/projects/notes/_form.html.haml +++ b/app/views/projects/notes/_form.html.haml @@ -17,8 +17,8 @@ = f.text_area :note, size: 255, class: 'note_text js-note-text js-gfm-input markdown-area' .light.clearfix - .pull-left Comments are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'} - .pull-right Attach images (JPG, PNG, GIF) by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }. + .pull-left Comments are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"),{ target: '_blank', tabindex: -1 }} + .pull-right Attach images (JPG, PNG, GIF) by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector', tabindex: -1 }. .note-preview-holder.hide .js-note-preview diff --git a/app/views/projects/notes/discussions/_active.html.haml b/app/views/projects/notes/discussions/_active.html.haml new file mode 100644 index 00000000000..ef296b35dd5 --- /dev/null +++ b/app/views/projects/notes/discussions/_active.html.haml @@ -0,0 +1,21 @@ +- note = discussion_notes.first +.discussion.js-toggle-container{ class: note.discussion_id } + .discussion-header + .discussion-actions + = link_to "#", class: "js-toggle-button" do + %i.icon-chevron-up + Show/hide discussion + = image_tag avatar_icon(note.author_email), class: "avatar s32" + %div + = link_to_member(@project, note.author, avatar: false) + started a discussion + = link_to diffs_project_merge_request_path(note.project, note.noteable, anchor: note.line_code) do + %strong on the diff + .last-update.hide.js-toggle-content + - last_note = discussion_notes.last + last updated by + = link_to_member(@project, last_note.author, avatar: false) + %span.discussion-last-update + #{time_ago_with_tooltip(last_note.updated_at, 'bottom', 'discussion_updated_ago')} + .discussion-body.js-toggle-content + = render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note diff --git a/app/views/projects/notes/discussions/_commit.html.haml b/app/views/projects/notes/discussions/_commit.html.haml new file mode 100644 index 00000000000..78460974a9b --- /dev/null +++ b/app/views/projects/notes/discussions/_commit.html.haml @@ -0,0 +1,28 @@ +- note = discussion_notes.first +.discussion.js-toggle-container{ class: note.discussion_id } + .discussion-header + .discussion-actions + = link_to "#", class: "js-toggle-button" do + %i.icon-chevron-up + Show/hide discussion + = image_tag avatar_icon(note.author_email), class: "avatar s32" + %div + = link_to_member(@project, note.author, avatar: false) + started a discussion on commit + = link_to(note.noteable.short_id, project_commit_path(note.project, note.noteable), class: 'monospace') + .last-update.hide.js-toggle-content + - last_note = discussion_notes.last + last updated by + = link_to_member(@project, last_note.author, avatar: false) + %span.discussion-last-update + #{time_ago_with_tooltip(last_note.updated_at, 'bottom', 'discussion_updated_ago')} + .discussion-body.js-toggle-content + - if note.for_diff_line? + = render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note + - else + .panel.panel-default + .notes{ rel: discussion_notes.first.discussion_id } + = render discussion_notes + .discussion-reply-holder + = link_to_reply_diff(discussion_notes.first) + diff --git a/app/views/projects/notes/_discussion_diff.html.haml b/app/views/projects/notes/discussions/_diff.html.haml index 26c5494f466..26c5494f466 100644 --- a/app/views/projects/notes/_discussion_diff.html.haml +++ b/app/views/projects/notes/discussions/_diff.html.haml diff --git a/app/views/projects/notes/discussions/_outdated.html.haml b/app/views/projects/notes/discussions/_outdated.html.haml new file mode 100644 index 00000000000..67c29be8ac1 --- /dev/null +++ b/app/views/projects/notes/discussions/_outdated.html.haml @@ -0,0 +1,20 @@ +- note = discussion_notes.first +.discussion.js-toggle-container{ class: note.discussion_id } + .discussion-header + .discussion-actions + = link_to "#", class: "js-toggle-button" do + %i.icon-chevron-down + Show/hide discussion + = image_tag avatar_icon(note.author_email), class: "avatar s32" + %div + = link_to_member(@project, note.author, avatar: false) + started a discussion on the + %strong outdated diff + %div + - last_note = discussion_notes.last + last updated by + = link_to_member(@project, last_note.author, avatar: false) + %span.discussion-last-update + #{time_ago_with_tooltip(last_note.updated_at, 'bottom', 'discussion_updated_ago')} + .discussion-body.js-toggle-content.hide + = render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index 7e9f1122aa9..c4e5087b0a1 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -1,12 +1,12 @@ = render "home_panel" .row - .col-md-9 + %section.col-md-9 = render "events/event_last_push", event: @last_push = render 'shared/event_filter' .content_list = spinner - .col-md-3.project-side.hidden-sm.hidden-xs + %aside.col-md-3.project-side.hidden-sm.hidden-xs .clearfix - if @project.archived? .alert.alert-warning @@ -44,6 +44,12 @@ = link_to project_blob_path(@project, tree_join(@repository.root_ref, readme.name)), class: 'btn btn-block' do = readme.name + - if @repository.version + - version = @repository.version + = link_to project_blob_path(@project, tree_join(@repository.root_ref, version.name)), class: 'btn btn-block' do + Version: + = @repository.blob_by_oid(version.id).data + .prepend-top-10 %p %span.light Created on diff --git a/app/views/projects/tree/_blob_item.html.haml b/app/views/projects/tree/_blob_item.html.haml index 0971a426527..393ef0e24bd 100644 --- a/app/views/projects/tree/_blob_item.html.haml +++ b/app/views/projects/tree/_blob_item.html.haml @@ -5,4 +5,4 @@ = link_to blob_item.name, project_blob_path(@project, tree_join(@id || @commit.id, blob_item.name)) %td.tree_time_ago.cgray = render 'spinner' - %td.hidden-xs.tree_commit{ colspan: 2 } + %td.hidden-xs.tree_commit diff --git a/app/views/projects/tree/_readme.html.haml b/app/views/projects/tree/_readme.html.haml index 0c19109445a..c066b7102fd 100644 --- a/app/views/projects/tree/_readme.html.haml +++ b/app/views/projects/tree/_readme.html.haml @@ -1,4 +1,4 @@ -.readme-holder#README +%article.readme-holder#README %h4.readme-file-title %i.icon-file = readme.name diff --git a/app/views/projects/tree/_tree_item.html.haml b/app/views/projects/tree/_tree_item.html.haml index 760e95e84f0..f8cecf9be1f 100644 --- a/app/views/projects/tree/_tree_item.html.haml +++ b/app/views/projects/tree/_tree_item.html.haml @@ -5,4 +5,4 @@ = link_to tree_item.name, project_tree_path(@project, tree_join(@id || @commit.id, tree_item.name)) %td.tree_time_ago.cgray = render 'spinner' - %td.hidden-xs.tree_commit{ colspan: 2 } + %td.hidden-xs.tree_commit diff --git a/app/views/search/_project_results.html.haml b/app/views/search/_project_results.html.haml index f285bda5736..af3b97879c7 100644 --- a/app/views/search/_project_results.html.haml +++ b/app/views/search/_project_results.html.haml @@ -15,3 +15,4 @@ %ul.bordered-list = render partial: "search/results/merge_request", collection: @search_results[:merge_requests] = render partial: "search/results/issue", collection: @search_results[:issues] + = render partial: "search/results/note", collection: @search_results[:notes] diff --git a/app/views/search/results/_note.html.haml b/app/views/search/results/_note.html.haml new file mode 100644 index 00000000000..97e892bdd4d --- /dev/null +++ b/app/views/search/results/_note.html.haml @@ -0,0 +1,9 @@ +%li + note on issue: + = link_to [note.project, note.noteable] do + %span ##{note.noteable.iid} + %strong.term + = truncate note.noteable.title, length: 50 + %span.light (#{note.project.name_with_namespace}) + - if note.noteable.closed? + %span.label Closed diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb index e66df8c4db1..43ef54631a9 100644 --- a/app/workers/repository_import_worker.rb +++ b/app/workers/repository_import_worker.rb @@ -14,8 +14,9 @@ class RepositoryImportWorker project.import_finish project.save project.satellite.create unless project.satellite.exists? + project.update_repository_size else project.import_fail end end -end +end
\ No newline at end of file diff --git a/config/application.rb b/config/application.rb index 540426b6672..0a77f58f6d1 100644 --- a/config/application.rb +++ b/config/application.rb @@ -18,15 +18,6 @@ module Gitlab # :all can be used as a placeholder for all plugins not explicitly named. # config.plugins = [ :exception_notification, :ssl_requirement, :all ] - # Activate observers that should always be running. - config.active_record.observers = :project_activity_cache_observer, - :note_observer, - :project_observer, - :system_hook_observer, - :user_observer, - :users_group_observer, - :users_project_observer - # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. # config.time_zone = 'Central Time (US & Canada)' diff --git a/config/gitlab.teatro.yml b/config/gitlab.teatro.yml new file mode 100644 index 00000000000..f0656400beb --- /dev/null +++ b/config/gitlab.teatro.yml @@ -0,0 +1,86 @@ + +production: &base + gitlab: + host: localhost + port: 80 + https: false + + user: root + + email_from: example@example.com + + support_email: support@example.com + + default_projects_features: + issues: true + merge_requests: true + wiki: true + wall: false + snippets: false + visibility_level: "private" # can be "private" | "internal" | "public" + + issues_tracker: + + gravatar: + enabled: true # Use user avatar image from Gravatar.com (default: true) + + ldap: + enabled: false + host: '_your_ldap_server' + port: 636 + uid: 'sAMAccountName' + method: 'ssl' # "tls" or "ssl" or "plain" + bind_dn: '_the_full_dn_of_the_user_you_will_bind_with' + password: '_the_password_of_the_bind_user' + allow_username_or_email_login: true + + base: '' + + user_filter: '' + + omniauth: + enabled: false + + satellites: + # Relative paths are relative to Rails.root (default: tmp/repo_satellites/) + path: /apps/gitlab-satellites/ + + backup: + path: "tmp/backups" # Relative paths are relative to Rails.root (default: tmp/backups/) + + gitlab_shell: + path: /apps/gitlab-shell/ + + # REPOS_PATH MUST NOT BE A SYMLINK!!! + repos_path: /apps/repositories/ + hooks_path: /apps/gitlab-shell/hooks/ + + upload_pack: true + receive_pack: true + + git: + bin_path: /usr/bin/git + max_size: 5242880 # 5.megabytes + timeout: 10 + + extra: + +development: + <<: *base + +test: + <<: *base + gravatar: + enabled: true + gitlab: + host: localhost + port: 80 + issues_tracker: + redmine: + title: "Redmine" + project_url: "http://redmine/projects/:issues_tracker_id" + issues_url: "http://redmine/:project_id/:issues_tracker_id/:id" + new_issue_url: "http://redmine/projects/:issues_tracker_id/issues/new" + +staging: + <<: *base diff --git a/config/routes.rb b/config/routes.rb index 746e532a0ec..14ff52f387a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -68,6 +68,7 @@ Gitlab::Application.routes.draw do put :team_update put :block put :unblock + delete 'remove/:email_id', action: 'remove_email', as: 'remove_email' end end @@ -157,8 +158,11 @@ Gitlab::Application.routes.draw do resources :projects, constraints: { id: /[^\/]+/ }, only: [:new, :create] - devise_for :users, controllers: { omniauth_callbacks: :omniauth_callbacks, registrations: :registrations , passwords: :passwords} + devise_for :users, controllers: { omniauth_callbacks: :omniauth_callbacks, registrations: :registrations , passwords: :passwords, sessions: :users_sessions } + devise_scope :user do + get "/users/auth/:provider/omniauth_error" => "omniauth_callbacks#omniauth_error", as: :omniauth_error + end # # Project Area # diff --git a/doc/api/users.md b/doc/api/users.md index 4ddbf739774..a7e9518408c 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -84,7 +84,7 @@ GET /users ] ``` -You can search for a users by email or username with: `/users?search=John` +You can search for users by email or username with: `/users?search=John` Also see `def search query` in `app/models/user.rb`. diff --git a/doc/install/installation.md b/doc/install/installation.md index 1b5139e8bb9..37f9377256c 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -95,8 +95,8 @@ Remove the old Ruby 1.8 if present Download Ruby and compile it: mkdir /tmp/ruby && cd /tmp/ruby - curl --progress ftp://ftp.ruby-lang.org/pub/ruby/2.0/ruby-2.0.0-p481.tar.gz | tar xz - cd ruby-2.0.0-p481 + curl --progress ftp://ftp.ruby-lang.org/pub/ruby/2.1/ruby-2.1.2.tar.gz | tar xz + cd ruby-2.1.2 ./configure --disable-install-rdoc make sudo make install @@ -243,7 +243,7 @@ GitLab Shell is an ssh access and repository management software developed speci # By default, the gitlab-shell config is generated from your main gitlab config. # # Note: When using GitLab with HTTPS please change the following: - # - Provide paths to the certificates under `ca_file` and `ca_path options. + # - Provide paths to the certificates under `ca_file` and `ca_path` options. # - The `gitlab_url` option must point to the https endpoint of GitLab. # - In case you are using self signed certificate set `self_signed_cert` to `true`. # See #using-https for all necessary details. diff --git a/doc/install/requirements.md b/doc/install/requirements.md index d7c4c4da2ed..10370d54fb1 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -2,9 +2,7 @@ ## Operating Systems -GitLab is developed for the Linux operating system. For the installations options and instructions please see [the installation section of the readme](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/README.md#installation). - -### Supported Linux distributions +### Supported Unix distributions - Ubuntu - Debian @@ -13,36 +11,29 @@ GitLab is developed for the Linux operating system. For the installations option - Scientific Linux - Oracle Linux -### Unsupported Linux distributions +For the installations options please see [the installation page on the GitLab website](https://about.gitlab.com/installation/). + +### Unsupported Unix distributions +- OS X - Arch Linux - Fedora - Gentoo +- FreeBSD -But on the above unsupported distributions is still possible to install GitLab yourself with the [manual installation guide](https://github.com/gitlabhq/gitlabhq/blob/master/doc/install/installation.md). - -### Unsupported Unix operating systems - -There is nothing that prevents GitLab from running on other Unix operating systems. +On the above unsupported distributions is still possible to install GitLab yourself. +Please see the [manual installation guide](https://github.com/gitlabhq/gitlabhq/blob/master/doc/install/installation.md) and the [unofficial installation guides](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Unofficial-Installation-Guides) on the public wiki for more information. -This means you may get it to work on systems running FreeBSD or OS X. - -If you want to do this, please be aware it could be a lot of work. - -Please consider using a virtual machine to run GitLab. - -### Other operating systems such as Windows +### Non Unix operating systems such as Windows +GitLab is developed for Unix operating systems. GitLab does **not** run on Windows and we have no plans of supporting it in the near future. - Please consider using a virtual machine to run GitLab. ## Ruby versions GitLab requires Ruby (MRI) 2.0 or 2.1 - You will have to use the standard MRI implementation of Ruby. - We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/)) but GitLab needs several Gems that have native extensions. ## Hardware requirements @@ -59,7 +50,11 @@ We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/)) but GitLab ### Memory -- 512MB is the absolute minimum but we do not recommend this amount of memory, you'll need to configure a minimum swap of 256MB, you're memory will only allow you to run one slow unicorn worker, things will case only git ssh access to work because the git http access requires two running workers (one to receive the user request and one for the authorization check), +- 512MB is the absolute minimum but we do not recommend this amount of memory. +You will either need to configure a minimum swap of 256MB and this will only allow you to run one slow unicorn worker. +One unicorn worker will cause only git ssh access to work because the git http access requires two running workers. +It requires one worker to receive the user request and one worker for the authorization check. +Or if you use SSD you can configure 2GB of swap to use two Unicorn workers and have slow operation with http access. - 1GB supports up to 100 users (with individual repositories under 250MB, otherwise git memory usage necessitates configuring swap space) - **2GB** is the **recommended** memory size and supports up to 500 users - 4GB supports up to 2,000 users @@ -80,7 +75,15 @@ If you have enough RAM memory and a recent CPU the speed of GitLab is mainly lim ## Database -If you want to run the database separately, the **recommended** database size is **1 MB per user** +If you want to run the database separately, the **recommended** database size is **1 MB per user**. + +## Redis and Sidekiq + +Redis stores all user sessions and the background task queue. +The storage requirements for Redis are minimal, about 25kB per user. +Sidekiq processes the background jobs with a multithreaded process. +This process starts with the entire Rails stack (200MB+) but it can grow over time due to memory leaks. +On a very active server (10.000 active users) the Sidekiq process can use 1GB+ of memory. ## Supported webbrowsers diff --git a/doc/permissions/permissions.md b/doc/permissions/permissions.md index e01b02212b6..d1da1496520 100644 --- a/doc/permissions/permissions.md +++ b/doc/permissions/permissions.md @@ -16,6 +16,7 @@ If a user is a GitLab administrator they receive all permissions. | Pull project code | | ✓ | ✓ | ✓ | ✓ | | Download project | | ✓ | ✓ | ✓ | ✓ | | Create code snippets | | ✓ | ✓ | ✓ | ✓ | +| Create new milestones | | | ✓ | ✓ | ✓ | | Create new merge request | | | ✓ | ✓ | ✓ | | Create new branches | | | ✓ | ✓ | ✓ | | Push to non-protected branches | | | ✓ | ✓ | ✓ | diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md index 5c1594d13d8..00ce6ed27c2 100644 --- a/doc/raketasks/backup_restore.md +++ b/doc/raketasks/backup_restore.md @@ -7,6 +7,10 @@ Creates a backup archive of the database and all repositories. This archive will The filename will be `[TIMESTAMP]_gitlab_backup.tar`. This timestamp can be used to restore an specific backup. ``` +# omnibus-gitlab +sudo gitlab-rake gitlab:backup:create + +# installation from source or cookbook bundle exec rake gitlab:backup:create RAILS_ENV=production ``` @@ -42,6 +46,10 @@ Deleting old backups... [SKIPPING] ## Restore a previously created backup ``` +# omnibus-gitlab +sudo gitlab-rake gitlab:backup:restore + +# installation from source or cookbook bundle exec rake gitlab:backup:restore RAILS_ENV=production ``` @@ -84,6 +92,8 @@ Deleting tmp directories...[DONE] ## Configure cron to make daily backups +For omnibus-gitlab, see https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#scheduling-a-backup . + ``` cd /home/git/gitlab sudo -u git -H editor config/gitlab.yml # Enable keep_time in the backup section to automatically delete old backups diff --git a/doc/raketasks/cleanup.md b/doc/raketasks/cleanup.md index 4b3b6d71a75..9e48f56c951 100644 --- a/doc/raketasks/cleanup.md +++ b/doc/raketasks/cleanup.md @@ -5,11 +5,19 @@ Remove namespaces(dirs) from `/home/git/repositories` if they don't exist in GitLab database. ``` +# omnibus-gitlab +sudo gitlab-rake gitlab:cleanup:dirs + +# installation from source or cookbook bundle exec rake gitlab:cleanup:dirs RAILS_ENV=production ``` Remove repositories (global only for now) from `/home/git/repositories` if they don't exist in GitLab database. ``` +# omnibus-gitlab +sudo gitlab-rake gitlab:cleanup:repos + +# installation from source or cookbook bundle exec rake gitlab:cleanup:repos RAILS_ENV=production ``` diff --git a/doc/raketasks/import.md b/doc/raketasks/import.md index 628bd373b8d..39b1a52a44d 100644 --- a/doc/raketasks/import.md +++ b/doc/raketasks/import.md @@ -15,6 +15,10 @@ How to use: 2. run the command below ``` +# omnibus-gitlab +sudo gitlab-rake gitlab:import:repos + +# installation from source or cookbook bundle exec rake gitlab:import:repos RAILS_ENV=production ``` diff --git a/doc/raketasks/maintenance.md b/doc/raketasks/maintenance.md index 30276dd7629..3339dfb03ac 100644 --- a/doc/raketasks/maintenance.md +++ b/doc/raketasks/maintenance.md @@ -5,6 +5,10 @@ This command gathers information about your GitLab installation and the System it runs on. These may be useful when asking for help or reporting issues. ``` +# omnibus-gitlab +sudo gitlab-rake gitlab:env:info + +# installation from source or cookbook bundle exec rake gitlab:env:info RAILS_ENV=production ``` @@ -52,6 +56,10 @@ It will check that each component was setup according to the installation guide You may also have a look at our [Trouble Shooting Guide](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Trouble-Shooting-Guide). ``` +# omnibus-gitlab +sudo gitlab-rake gitlab:check + +# installation from source or cookbook bundle exec rake gitlab:check RAILS_ENV=production ``` diff --git a/doc/raketasks/user_management.md b/doc/raketasks/user_management.md index 81378494c6b..eb9eebb4b4a 100644 --- a/doc/raketasks/user_management.md +++ b/doc/raketasks/user_management.md @@ -3,6 +3,10 @@ ## Add user as a developer to all projects ```bash +# omnibus-gitlab +sudo gitlab-rake gitlab:import:user_to_projects[username@domain.tld] + +# installation from source or cookbook bundle exec rake gitlab:import:user_to_projects[username@domain.tld] ``` @@ -13,12 +17,20 @@ Notes: - admin users are added as masters ```bash +# omnibus-gitlab +sudo gitlab-rake gitlab:import:all_users_to_all_projects + +# installation from source or cookbook bundle exec rake gitlab:import:all_users_to_all_projects ``` ## Add user as a developer to all groups ```bash +# omnibus-gitlab +sudo gitlab-rake gitlab:import:user_to_groups[username@domain.tld] + +# installation from source or cookbook bundle exec rake gitlab:import:user_to_groups[username@domain.tld] ``` @@ -29,5 +41,9 @@ Notes: - admin users are added as owners so they can add additional users to the group ```bash +# omnibus-gitlab +sudo gitlab-rake gitlab:import:all_users_to_all_groups + +# installation from source or cookbook bundle exec rake gitlab:import:all_users_to_all_groups ``` diff --git a/doc/raketasks/web_hooks.md b/doc/raketasks/web_hooks.md index 704473af2d9..ac32c01289d 100644 --- a/doc/raketasks/web_hooks.md +++ b/doc/raketasks/web_hooks.md @@ -2,26 +2,44 @@ ## Add a web hook for **ALL** projects: + # omnibus-gitlab + sudo gitlab-rake gitlab:web_hook:add URL="http://example.com/hook" + # source installations or cookbook RAILS_ENV=production bundle exec rake gitlab:web_hook:add URL="http://example.com/hook" ## Add a web hook for projects in a given **NAMESPACE**: + # omnibus-gitlab + sudo gitlab-rake gitlab:web_hook:add URL="http://example.com/hook" NAMESPACE=acme + # source installations or cookbook RAILS_ENV=production bundle exec rake gitlab:web_hook:add URL="http://example.com/hook" NAMESPACE=acme ## Remove a web hook from **ALL** projects using: + # omnibus-gitlab + sudo gitlab-rake gitlab:web_hook:rm URL="http://example.com/hook" + # source installations or cookbook RAILS_ENV=production bundle exec rake gitlab:web_hook:rm URL="http://example.com/hook" ## Remove a web hook from projects in a given **NAMESPACE**: + # omnibus-gitlab + sudo gitlab-rake gitlab:web_hook:rm URL="http://example.com/hook" NAMESPACE=acme + # source installations or cookbook RAILS_ENV=production bundle exec rake gitlab:web_hook:rm URL="http://example.com/hook" NAMESPACE=acme ## List **ALL** web hooks: + # omnibus-gitlab + sudo gitlab-rake gitlab:web_hook:list + # source installations or cookbook RAILS_ENV=production bundle exec rake gitlab:web_hook:list ## List the web hooks from projects in a given **NAMESPACE**: + # omnibus-gitlab + sudo gitlab-rake gitlab:web_hook:list NAMESPACE=/ + # source installations or cookbook RAILS_ENV=production bundle exec rake gitlab:web_hook:list NAMESPACE=/ > Note: `/` is the global namespace. diff --git a/doc/release/monthly.md b/doc/release/monthly.md index 45040084dfc..22824016f26 100644 --- a/doc/release/monthly.md +++ b/doc/release/monthly.md @@ -120,6 +120,17 @@ Merge the RC1 code into GitLab.com. Once the build is green, deploy in the morni It is important to do this as soon as possible, so we can catch any errors before we release the full version. +### **9. Create a regressions issue** + +On [the GitLab CE issue tracker on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/issues/) create an issue titled "GitLab X.X regressions" add the following text: + +This is a meta issue to discuss possible regressions in this monthly release and any patch versions. +Please do not raise issues directly in this issue but link to issues that might warrant a patch release. +The decision to create a patch release or not is with the release manager who is assigned to this issue. +The release manager will comment here about the plans for patch releases. + +Assign the issue to the release manager and /cc all the core-team members active on the issue tracker. If there are any known bugs in the release add them immediately. + # **21st - Preparation ** ### **1. Prepare the blog post** @@ -219,17 +230,6 @@ Include a link to the blog post and keep it short. Proposed email for CE: "We have released a new version of GitLab Community Edition and its packages. See our blog post(<link>) for more information." -### **10. Create a regressions issue** - -On [the GitLab CE issue tracker on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/issues/) create an issue titled "GitLab X.X regressions" add the following text: - -This is a meta issue to discuss possible regressions in this monthly release and any patch versions. -Please do not raise issues directly in this issue but link to issues that might warrant a patch release. -The decision to create a patch release or not is with the release manager who is assigned to this issue. -The release manager will comment here about the plans for patch releases. - -Assign the issue to the release manager and /cc all the core-team members active on the issue tracker. If there are any known bugs in the release add them immediately. - # **23rd - Optional Patch Release** # **24th - Update GitLab.com** diff --git a/doc/update/6.9-to-7.0.md b/doc/update/6.9-to-7.0.md index d6e9c9f3155..70a30a58687 100644 --- a/doc/update/6.9-to-7.0.md +++ b/doc/update/6.9-to-7.0.md @@ -13,7 +13,39 @@ sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production sudo service gitlab stop ``` -### 2. Get latest code +### 2. Update Ruby + +If you are still using Ruby 1.9.3 or below, you will need to update Ruby. +You can check which version you are running with `ruby -v`. + +If you are you running Ruby 2.0.x, you do not need to upgrade ruby, but can consider doing so for performance reasons. + +If you are running Ruby 2.1.1 consider upgrading to 2.1.2, because of the high memory usage of Ruby 2.1.1. + +Install, update dependencies: + +```bash +sudo apt-get install build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libreadline-dev libncurses5-dev libffi-dev curl +``` + +Download and compile Ruby: + +```bash +mkdir /tmp/ruby && cd /tmp/ruby +curl --progress ftp://ftp.ruby-lang.org/pub/ruby/2.1/ruby-2.1.2.tar.gz | tar xz +cd ruby-2.1.2 +./configure --disable-install-rdoc +make +sudo make install +``` + +Install Bundler: + +```bash +sudo gem install bundler --no-ri --no-rdoc +``` + +### 3. Get latest code ```bash cd /home/git/gitlab @@ -34,7 +66,7 @@ For GitLab Enterprise Edition: sudo -u git -H git checkout 7-0-stable-ee ``` -### 3. Update gitlab-shell (and its config) +### 4. Update gitlab-shell (and its config) ```bash cd /home/git/gitlab-shell @@ -42,7 +74,7 @@ sudo -u git -H git fetch sudo -u git -H git checkout v1.9.6 ``` -### 4. Install libs, migrations, etc. +### 5. Install libs, migrations, etc. ```bash cd /home/git/gitlab @@ -58,9 +90,13 @@ sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production # Clean up assets and cache sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production + +# Update init.d script +sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab +sudo chmod +x /etc/init.d/gitlab ``` -### 5. Update config files +### 6. Update config files #### New configuration options for gitlab.yml @@ -70,12 +106,12 @@ There are new configuration options available for gitlab.yml. View them with the git diff 6-9-stable:config/gitlab.yml.example 7-0-stable:config/gitlab.yml.example ``` -### 6. Start application +### 7. Start application sudo service gitlab start sudo service nginx restart -### 7. Check application status +### 8. Check application status Check if GitLab and its environment are configured correctly: diff --git a/doc/update/README.md b/doc/update/README.md index 8c0cf5f7b26..9a6f09b370a 100644 --- a/doc/update/README.md +++ b/doc/update/README.md @@ -1,5 +1,4 @@ - [The individual upgrade guides](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update) - [Upgrader](upgrader.md) -- [Ruby](ruby.md) - [Patch versions](patch_versions.md) - [MySQL to PostgreSQL](mysql_to_postgresql.md) diff --git a/doc/update/ruby.md b/doc/update/ruby.md deleted file mode 100644 index d1d9d3e77f5..00000000000 --- a/doc/update/ruby.md +++ /dev/null @@ -1,59 +0,0 @@ -# Updating Ruby from source - -This guide explains how to update Ruby in case you installed it from source according to the [instructions](../install/installation.md#2-ruby). - -## 1. Look for Ruby versions - -This guide will only update `/usr/local/bin/ruby`. You can see which Ruby binaries are installed on your system by running: - -```bash -ls -l $(which -a ruby) -``` - -## 2. Stop GitLab - -```bash -sudo service gitlab stop -``` - -## 3. Install or update dependencies - -Here we are assuming you are using Debian/Ubuntu. - -```bash -sudo apt-get install build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libreadline-dev libncurses5-dev libffi-dev curl -``` - -## 4. Download, compile and install Ruby - -Find the latest stable version of Ruby 1.9 or 2.0 at <https://www.ruby-lang.org/en/downloads/>. We recommend at least 2.0.0-p353, which is patched against [CVE-2013-4164](https://www.ruby-lang.org/en/news/2013/11/22/heap-overflow-in-floating-point-parsing-cve-2013-4164/). - -```bash -cd /tmp -curl --progress http://cache.ruby-lang.org/pub/ruby/2.0/ruby-2.0.0-p353.tar.gz | tar xz -cd ruby-2.0.0-p353 -./configure --disable-install-rdoc -make -sudo make install # overwrite the existing Ruby in /usr/local/bin -sudo gem install bundler -``` - -### 5. Reinstall GitLab gem bundle - -Just to be sure we will reinstall the gems used by GitLab. Note that the `bundle install` command [depends on your choice of database](../install/installation.md#install-gems). - -```bash -cd /home/git/gitlab -sudo -u git -H rm -rf vendor/bundle # remove existing Gem bundle -sudo -u git -H bundle install --deployment --without development test mysql aws # Assuming PostgreSQL -``` - -## 6. Start GitLab - -We are now ready to restart GitLab. - -```bash -sudo service gitlab start -``` - -## Done diff --git a/doc/workflow/README.md b/doc/workflow/README.md index c715f6e5943..00d4bed1cc7 100644 --- a/doc/workflow/README.md +++ b/doc/workflow/README.md @@ -1,3 +1,4 @@ - [Workflow](workflow.md) - [Project Features](project_features.md) - [Authorization for merge requests](authorization_for_merge_requests.md) +- [Groups](groups.md) diff --git a/doc/workflow/groups.md b/doc/workflow/groups.md new file mode 100644 index 00000000000..611f871cf77 --- /dev/null +++ b/doc/workflow/groups.md @@ -0,0 +1,71 @@ +# GitLab Groups + +GitLab groups allow you to group projects into directories and give users to several projects at once. + +When you create a new project in GitLab, the default namespace for the project is the personal namespace associated with your GitLab user. +In this document we will see how to create groups, put projects in groups and manage who can access the projects in a group. + +## Creating groups + +You can create a group by going to the 'Groups' tab of the GitLab dashboard and clicking the 'New group' button. + +![Click the 'New group' button in the 'Groups' tab](groups/new_group_button.png) + +Next, enter the name (required) and the optional description and group avatar. + +![Fill in the name for your new group](groups/new_group_form.png) + +When your group has been created you are presented with the group dashboard feed, which will be empty. + +![Group dashboard](groups/group_dashboard.png) + +You can use the 'New project' button to add a project to the new group. + +## Transfering an existing project into a group + +You can transfer an existing project into a group you own from the project settings page. +First scroll down to the 'Dangerous settings' and click 'Show them to me'. +Now you can pick any of the groups you manage as the new namespace for the group. + +![Transfer a project to a new namespace](groups/transfer_project.png) + +GitLab administrators can use the admin interface to move any project to any namespace if needed. + +## Adding users to a group + +One of the benefits of putting multiple projects in one group is that you can give a user to access to all projects in the group with one action. + +Suppose we have a group with two projects. + +![Group with two projects](groups/group_with_two_projects.png) + +On the 'Group Members' page we can now add a new user Barry to the group. + +![Add user Barry to the group](groups/add_member_to_group.png) + +Now because Barry is a 'Developer' member of the 'Open Source' group, he automatically gets 'Developer' access to all projects in the 'Open Source' group. + +![Barry has 'Developer' access to GitLab CI](groups/project_members_via_group.png) + +If necessary, you can increase the access level of an individual user for a specific project, by adding them as a Member to the project. + +![Barry effectively has 'Master' access to GitLab CI now](groups/override_access_level.png) + +## Managing group memberships via LDAP + +In GitLab Enterprise Edition it is possible to manage GitLab group memberships using LDAP groups. +See [the GitLab Enterprise Edition documentation](http://doc.gitlab.com/ee/integration/ldap.html) for more information. + +## Allowing only admins to create groups + +By default, any GitLab user can create new groups. +This ability can be disabled for individual users from the admin panel. +It is also possible to configure GitLab so that new users default to not being able to create groups: + +``` +# For omnibus-gitlab, put the following in /etc/gitlab/gitlab.rb +gitlab_rails['gitlab_default_can_create_group'] = false + +# For installations from source, uncomment the 'default_can_create_group' +# line in /home/git/gitlab/config/gitlab.yml +``` diff --git a/doc/workflow/groups/add_member_to_group.png b/doc/workflow/groups/add_member_to_group.png Binary files differnew file mode 100644 index 00000000000..fa340ce572f --- /dev/null +++ b/doc/workflow/groups/add_member_to_group.png diff --git a/doc/workflow/groups/group_dashboard.png b/doc/workflow/groups/group_dashboard.png Binary files differnew file mode 100644 index 00000000000..7fc9048d74d --- /dev/null +++ b/doc/workflow/groups/group_dashboard.png diff --git a/doc/workflow/groups/group_with_two_projects.png b/doc/workflow/groups/group_with_two_projects.png Binary files differnew file mode 100644 index 00000000000..87242781e4f --- /dev/null +++ b/doc/workflow/groups/group_with_two_projects.png diff --git a/doc/workflow/groups/new_group_button.png b/doc/workflow/groups/new_group_button.png Binary files differnew file mode 100644 index 00000000000..51e82798658 --- /dev/null +++ b/doc/workflow/groups/new_group_button.png diff --git a/doc/workflow/groups/new_group_form.png b/doc/workflow/groups/new_group_form.png Binary files differnew file mode 100644 index 00000000000..bf992c40bc2 --- /dev/null +++ b/doc/workflow/groups/new_group_form.png diff --git a/doc/workflow/groups/override_access_level.png b/doc/workflow/groups/override_access_level.png Binary files differnew file mode 100644 index 00000000000..f4225a63679 --- /dev/null +++ b/doc/workflow/groups/override_access_level.png diff --git a/doc/workflow/groups/project_members_via_group.png b/doc/workflow/groups/project_members_via_group.png Binary files differnew file mode 100644 index 00000000000..b13cb1cfd95 --- /dev/null +++ b/doc/workflow/groups/project_members_via_group.png diff --git a/doc/workflow/groups/transfer_project.png b/doc/workflow/groups/transfer_project.png Binary files differnew file mode 100644 index 00000000000..044fe10d073 --- /dev/null +++ b/doc/workflow/groups/transfer_project.png diff --git a/features/admin/users.feature b/features/admin/users.feature index ce31aafd290..d8c1288e5f0 100644 --- a/features/admin/users.feature +++ b/features/admin/users.feature @@ -21,3 +21,11 @@ Feature: Admin Users And click edit on my user When I submit modified user Then I see user attributes changed + +@javascript + Scenario: Remove users secondary email + Given I visit admin users page + And I view the user with secondary email + And I see the secondary email + When I click remove secondary email + Then I should not see secondary email anymore diff --git a/features/project/project.feature b/features/project/project.feature index d8bb1d55e2d..d561c6e440e 100644 --- a/features/project/project.feature +++ b/features/project/project.feature @@ -24,3 +24,8 @@ Feature: Project Feature When I visit edit project "Shop" page And change project path settings Then I should see project with new path settings + + Scenario: I should see project readme and version + When I visit project "Shop" page + Then I should see project "Shop" README link + And I should see project "Shop" version diff --git a/features/project/redirects.feature b/features/project/redirects.feature index ce197912f64..776ab83a876 100644 --- a/features/project/redirects.feature +++ b/features/project/redirects.feature @@ -24,3 +24,10 @@ Feature: Project Redirects Given I sign in as a user When I visit project "Enterprise" page Then page status code should be 404 + + Scenario: I visit a public project without signing in + When I visit project "Community" page + And I should see project "Community" home page + And I click on "Sign In" + And Authenticate + Then I should be redirected to "Community" page diff --git a/features/steps/admin/active_tab.rb b/features/steps/admin/active_tab.rb index ccafe09c18f..8f09e51ccef 100644 --- a/features/steps/admin/active_tab.rb +++ b/features/steps/admin/active_tab.rb @@ -4,7 +4,7 @@ class AdminActiveTab < Spinach::FeatureSteps include SharedActiveTab Then 'the active main tab should be Home' do - ensure_active_main_tab('Home') + ensure_active_main_tab('Overview') end Then 'the active main tab should be Projects' do diff --git a/features/steps/admin/users.rb b/features/steps/admin/users.rb index 659008dd875..253c4609e82 100644 --- a/features/steps/admin/users.rb +++ b/features/steps/admin/users.rb @@ -44,4 +44,23 @@ class AdminUsers < Spinach::FeatureSteps step 'click edit on my user' do find("#edit_user_#{current_user.id}").click end + + step 'I view the user with secondary email' do + @user_with_secondary_email = User.last + @user_with_secondary_email.emails.new(email: "secondary@example.com") + @user_with_secondary_email.save + visit "/admin/users/#{@user_with_secondary_email.username}" + end + + step 'I see the secondary email' do + page.should have_content "Secondary email: #{@user_with_secondary_email.emails.last.email}" + end + + step 'I click remove secondary email' do + find("#remove_email_#{@user_with_secondary_email.emails.last.id}").click + end + + step 'I should not see secondary email anymore' do + page.should_not have_content "Secondary email:" + end end diff --git a/features/steps/dashboard/active_tab.rb b/features/steps/dashboard/active_tab.rb index 8f5f0eed816..68d32ed971a 100644 --- a/features/steps/dashboard/active_tab.rb +++ b/features/steps/dashboard/active_tab.rb @@ -4,7 +4,7 @@ class DashboardActiveTab < Spinach::FeatureSteps include SharedActiveTab Then 'the active main tab should be Home' do - ensure_active_main_tab('Home') + ensure_active_main_tab('Activity') end Then 'the active main tab should be Issues' do diff --git a/features/steps/profile/active_tab.rb b/features/steps/profile/active_tab.rb index ee9f5f201cf..1924a6fa785 100644 --- a/features/steps/profile/active_tab.rb +++ b/features/steps/profile/active_tab.rb @@ -4,7 +4,7 @@ class ProfileActiveTab < Spinach::FeatureSteps include SharedActiveTab Then 'the active main tab should be Home' do - ensure_active_main_tab('Home') + ensure_active_main_tab('Profile') end Then 'the active main tab should be Account' do diff --git a/features/steps/project/active_tab.rb b/features/steps/project/active_tab.rb index dcc252f4765..dfafbc6fc0e 100644 --- a/features/steps/project/active_tab.rb +++ b/features/steps/project/active_tab.rb @@ -7,7 +7,7 @@ class ProjectActiveTab < Spinach::FeatureSteps # Main Tabs Then 'the active main tab should be Home' do - ensure_active_main_tab('Home') + ensure_active_main_tab('Activity') end Then 'the active main tab should be Settings' do diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index e0aec699a56..b0a6e530e8a 100644 --- a/features/steps/project/merge_requests.rb +++ b/features/steps/project/merge_requests.rb @@ -131,21 +131,21 @@ class ProjectMergeRequests < Spinach::FeatureSteps end step 'I should see a discussion has started on line 185' do - page.should have_content "#{current_user.name} started a discussion on this merge request diff" - page.should have_content "app/assets/stylesheets/tree.scss:L185" + page.should have_content "#{current_user.name} started a discussion" + page.should have_content "app/assets/stylesheets/tree.scss" page.should have_content "Line is wrong" end step 'I should see a discussion has started on commit b1e6a9dbf1:L185' do page.should have_content "#{current_user.name} started a discussion on commit" - page.should have_content "app/assets/stylesheets/tree.scss:L185" + page.should have_content "app/assets/stylesheets/tree.scss" page.should have_content "Line is wrong" end step 'I should see a discussion has started on commit b1e6a9dbf1' do page.should have_content "#{current_user.name} started a discussion on commit" page.should have_content "One comment to rule them all" - page.should have_content "app/assets/stylesheets/tree.scss:L185" + page.should have_content "app/assets/stylesheets/tree.scss" end step 'merge request is mergeable' do @@ -257,9 +257,7 @@ class ProjectMergeRequests < Spinach::FeatureSteps click_button "Add Comment" end - within ".note-text" do - page.should have_content message - end + page.should have_content message end def init_diff_note_first_file diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb index 92728d474b2..7c0b2509416 100644 --- a/features/steps/project/project.rb +++ b/features/steps/project/project.rb @@ -24,4 +24,16 @@ class ProjectFeature < Spinach::FeatureSteps step 'I should see project with new path settings' do project.path.should == "new-path" end + + step 'I should see project "Shop" README link' do + within '.project-side' do + page.should have_content "README.md" + end + end + + step 'I should see project "Shop" version' do + within '.project-side' do + page.should have_content "Version: 2.2.0" + end + end end diff --git a/features/steps/project/redirects.rb b/features/steps/project/redirects.rb index cfa4ce82be3..5a4342dba30 100644 --- a/features/steps/project/redirects.rb +++ b/features/steps/project/redirects.rb @@ -31,5 +31,27 @@ class Spinach::Features::ProjectRedirects < Spinach::FeatureSteps project = Project.find_by(name: 'Community') visit project_path(project) + 'DoesNotExist' end + + step 'I click on "Sign In"' do + within '.pull-right' do + click_link "Sign in" + end + end + + step 'Authenticate' do + admin = create(:admin) + project = Project.find_by(name: 'Community') + find(:xpath, "//input[@id='return_to']").set "/#{project.path_with_namespace}" + fill_in "user_login", with: admin.email + fill_in "user_password", with: admin.password + click_button "Sign in" + Thread.current[:current_user] = admin + end + + step 'I should be redirected to "Community" page' do + project = Project.find_by(name: 'Community') + page.current_path.should == "/#{project.path_with_namespace}" + page.status_code.should == 200 + end end diff --git a/features/steps/shared/active_tab.rb b/features/steps/shared/active_tab.rb index d504fda3327..e3cd5fcfe85 100644 --- a/features/steps/shared/active_tab.rb +++ b/features/steps/shared/active_tab.rb @@ -2,11 +2,7 @@ module SharedActiveTab include Spinach::DSL def ensure_active_main_tab(content) - if content == "Home" - page.find('.main-nav li.active').should have_css('i.icon-home') - else - page.find('.main-nav li.active').should have_content(content) - end + page.find('.main-nav li.active').should have_content(content) end def ensure_active_sub_tab(content) diff --git a/features/steps/shared/diff_note.rb b/features/steps/shared/diff_note.rb index f917d7bde08..201642a086e 100644 --- a/features/steps/shared/diff_note.rb +++ b/features/steps/shared/diff_note.rb @@ -2,7 +2,7 @@ module SharedDiffNote include Spinach::DSL Given 'I cancel the diff comment' do - within(".diff-file") do + within(diff_file_selector) do find(".js-close-discussion-note-form").click end end @@ -13,14 +13,14 @@ module SharedDiffNote end Given 'I haven\'t written any diff comment text' do - within(".diff-file") do + within(diff_file_selector) do fill_in "note[note]", with: "" end end Given 'I leave a diff comment like "Typo, please fix"' do find('a[data-line-code="586fb7c4e1add2d4d24e27566ed7064680098646_29_14"]').click - within(".diff-file form[rel$='586fb7c4e1add2d4d24e27566ed7064680098646_29_14']") do + within("#{diff_file_selector} form[rel$='586fb7c4e1add2d4d24e27566ed7064680098646_29_14']") do fill_in "note[note]", with: "Typo, please fix" find(".js-comment-button").trigger("click") sleep 0.05 @@ -29,7 +29,7 @@ module SharedDiffNote Given 'I preview a diff comment text like "Should fix it :smile:"' do find('a[data-line-code="586fb7c4e1add2d4d24e27566ed7064680098646_29_14"]').click - within(".diff-file form[rel$='586fb7c4e1add2d4d24e27566ed7064680098646_29_14']") do + within("#{diff_file_selector} form[rel$='586fb7c4e1add2d4d24e27566ed7064680098646_29_14']") do fill_in "note[note]", with: "Should fix it :smile:" find(".js-note-preview-button").trigger("click") end @@ -38,7 +38,7 @@ module SharedDiffNote Given 'I preview another diff comment text like "DRY this up"' do find('a[data-line-code="586fb7c4e1add2d4d24e27566ed7064680098646_57_41"]').click - within(".diff-file form[rel$='586fb7c4e1add2d4d24e27566ed7064680098646_57_41']") do + within("#{diff_file_selector} form[rel$='586fb7c4e1add2d4d24e27566ed7064680098646_57_41']") do fill_in "note[note]", with: "DRY this up" find(".js-note-preview-button").trigger("click") end @@ -53,63 +53,61 @@ module SharedDiffNote end Given 'I write a diff comment like ":-1: I don\'t like this"' do - within(".diff-file") do + within(diff_file_selector) do fill_in "note[note]", with: ":-1: I don\'t like this" end end Given 'I submit the diff comment' do - within(".diff-file") do + within(diff_file_selector) do click_button("Add Comment") end end - - Then 'I should not see the diff comment form' do - within(".diff-file") do + within(diff_file_selector) do page.should_not have_css("form.new_note") end end Then 'I should not see the diff comment preview button' do - within(".diff-file") do + within(diff_file_selector) do page.should have_css(".js-note-preview-button", visible: false) end end Then 'I should not see the diff comment text field' do - within(".diff-file") do + within(diff_file_selector) do page.should have_css(".js-note-text", visible: false) end end Then 'I should only see one diff form' do - within(".diff-file") do + within(diff_file_selector) do page.should have_css("form.new_note", count: 1) end end Then 'I should see a diff comment form with ":-1: I don\'t like this"' do - within(".diff-file") do + within(diff_file_selector) do page.should have_field("note[note]", with: ":-1: I don\'t like this") end end Then 'I should see a diff comment saying "Typo, please fix"' do - within(".diff-file .note") do + within("#{diff_file_selector} .note") do page.should have_content("Typo, please fix") end end Then 'I should see a discussion reply button' do - within(".diff-file") do + within(diff_file_selector) do page.should have_link("Reply") end end Then 'I should see a temporary diff comment form' do - within(".diff-file") do + within(diff_file_selector) do page.should have_css(".js-temp-notes-holder form.new_note") end end @@ -119,40 +117,44 @@ module SharedDiffNote end Then 'I should see an empty diff comment form' do - within(".diff-file") do + within(diff_file_selector) do page.should have_field("note[note]", with: "") end end Then 'I should see the cancel comment button' do - within(".diff-file form") do + within("#{diff_file_selector} form") do page.should have_css(".js-close-discussion-note-form", text: "Cancel") end end Then 'I should see the diff comment preview' do - within(".diff-file form") do + within("#{diff_file_selector} form") do page.should have_css(".js-note-preview", visible: false) end end Then 'I should see the diff comment edit button' do - within(".diff-file") do + within(diff_file_selector) do page.should have_css(".js-note-write-button", visible: true) end end Then 'I should see the diff comment preview button' do - within(".diff-file") do + within(diff_file_selector) do page.should have_css(".js-note-preview-button", visible: true) end end Then 'I should see two separate previews' do - within(".diff-file") do + within(diff_file_selector) do page.should have_css(".js-note-preview", visible: true, count: 2) page.should have_content("Should fix it") page.should have_content("DRY this up") end end + + def diff_file_selector + ".diff-file" + end end diff --git a/lib/api/notes.rb b/lib/api/notes.rb index 413faf0cf2d..0ef9a3c4beb 100644 --- a/lib/api/notes.rb +++ b/lib/api/notes.rb @@ -50,12 +50,15 @@ module API post ":id/#{noteables_str}/:#{noteable_id_str}/notes" do required_attributes! [:body] - @noteable = user_project.send(:"#{noteables_str}").find(params[:"#{noteable_id_str}"]) - @note = @noteable.notes.new(note: params[:body]) - @note.author = current_user - @note.project = user_project + opts = { + note: params[:body], + noteable_type: noteables_str.classify, + noteable_id: params[noteable_id_str] + } + + @note = ::Notes::CreateService.new(user_project, current_user, opts).execute - if @note.save + if @note.valid? present @note, with: Entities::Note else not_found! diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb index c04be788f07..b248d8f9436 100644 --- a/lib/gitlab/markdown.rb +++ b/lib/gitlab/markdown.rb @@ -169,10 +169,13 @@ module Gitlab end def reference_user(identifier, project = @project) - if user = User.find_by(username: identifier) - options = html_options.merge( + options = html_options.merge( class: "gfm gfm-team_member #{html_options[:class]}" ) + + if identifier == "all" + link_to("@all", project_url(project), options) + elsif user = User.find_by(username: identifier) link_to("@#{identifier}", user_url(identifier), options) end end diff --git a/lib/gitlab/oauth/user.rb b/lib/gitlab/oauth/user.rb index c5be884a895..38e33c0eee5 100644 --- a/lib/gitlab/oauth/user.rb +++ b/lib/gitlab/oauth/user.rb @@ -44,7 +44,13 @@ module Gitlab user.username = email_username.gsub("'", "") end - user.save! + begin + user.save! + rescue ActiveRecord::RecordInvalid => e + log.info "(OAuth) Email #{e.record.errors[:email]}. Username #{e.record.errors[:username]}" + return nil, e.record.errors + end + log.info "(OAuth) Creating user #{email} from login with extern_uid => #{uid}" if Gitlab.config.omniauth['block_auto_created_users'] && !ldap? diff --git a/lib/gitlab/url_builder.rb b/lib/gitlab/url_builder.rb new file mode 100644 index 00000000000..de7e0404086 --- /dev/null +++ b/lib/gitlab/url_builder.rb @@ -0,0 +1,25 @@ +module Gitlab + class UrlBuilder + include Rails.application.routes.url_helpers + + def initialize(type) + @type = type + end + + def build(id) + case @type + when :issue + issue_url(id) + end + end + + private + + def issue_url(id) + issue = Issue.find(id) + project_issue_url(id: issue.iid, + project_id: issue.project, + host: Settings.gitlab['url']) + end + end +end diff --git a/lib/tasks/cache.rake b/lib/tasks/cache.rake index 8320b9b2576..753a5a11070 100644 --- a/lib/tasks/cache.rake +++ b/lib/tasks/cache.rake @@ -1,6 +1,11 @@ namespace :cache do desc "GITLAB | Clear redis cache" task :clear => :environment do - Rails.cache.clear + # Hack into Rails.cache until https://github.com/redis-store/redis-store/pull/225 + # is accepted (I hope) and we can update the redis-store gem. + redis_store = Rails.cache.instance_variable_get(:@data) + redis_store.keys.each_slice(1000) do |key_slice| + redis_store.del(*key_slice) + end end end diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb index 1aba40966c4..82da19746f8 100644 --- a/spec/features/admin/admin_users_spec.rb +++ b/spec/features/admin/admin_users_spec.rb @@ -47,20 +47,16 @@ describe "Admin::Users", feature: true do it "should call send mail" do Notify.should_receive(:new_user_email) - User.observers.enable :user_observer do - click_button "Create user" - end + click_button "Create user" end it "should send valid email to user with email & password" do - User.observers.enable :user_observer do - click_button "Create user" - user = User.last - email = ActionMailer::Base.deliveries.last - email.subject.should have_content("Account was created") - email.text_part.body.should have_content(user.email) - email.text_part.body.should have_content('password') - end + click_button "Create user" + user = User.last + email = ActionMailer::Base.deliveries.last + email.subject.should have_content("Account was created") + email.text_part.body.should have_content(user.email) + email.text_part.body.should have_content('password') end end diff --git a/spec/features/profile_spec.rb b/spec/features/profile_spec.rb index e8364bbb962..bdf7b59114b 100644 --- a/spec/features/profile_spec.rb +++ b/spec/features/profile_spec.rb @@ -1,8 +1,6 @@ require 'spec_helper' describe "Profile account page", feature: true do - before(:each) { enable_observers } - after(:each) {disable_observers} let(:user) { create(:user) } before do diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb index 1e24782c39a..524c4d5fa21 100644 --- a/spec/features/projects_spec.rb +++ b/spec/features/projects_spec.rb @@ -1,8 +1,6 @@ require 'spec_helper' describe "Projects", feature: true do - before(:each) { enable_observers } - after(:each) {disable_observers} before { login_as :user } describe "DELETE /projects/:id" do diff --git a/spec/features/search_spec.rb b/spec/features/search_spec.rb index f12eb124296..cce9f06cb69 100644 --- a/spec/features/search_spec.rb +++ b/spec/features/search_spec.rb @@ -2,7 +2,6 @@ require 'spec_helper' describe "Search", feature: true do before do - ActiveRecord::Base.observers.enable(:user_observer) login_as :user @project = create(:project, namespace: @user.namespace) @project.team << [@user, :reporter] diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb index fc9d1ac90c0..f176a393415 100644 --- a/spec/helpers/gitlab_markdown_helper_spec.rb +++ b/spec/helpers/gitlab_markdown_helper_spec.rb @@ -5,6 +5,7 @@ describe GitlabMarkdownHelper do include IssuesHelper let!(:project) { create(:project) } + let(:empty_project) { create(:empty_project) } let(:user) { create(:user, username: 'gfm') } let(:commit) { project.repository.commit } @@ -506,6 +507,19 @@ describe GitlabMarkdownHelper do end end + describe "markdwon for empty repository" do + before do + @project = empty_project + @repository = empty_project.repository + end + + it "should not touch relative urls" do + actual = "[GitLab API doc][GitLab readme]\n [GitLab readme]: doc/api/README.md\n" + expected = "<p><a href=\"doc/api/README.md\">GitLab API doc</a></p>\n" + markdown(actual).should match(expected) + end + end + describe "#render_wiki_content" do before do @wiki = double('WikiPage') diff --git a/spec/lib/gitlab/url_builder_spec.rb b/spec/lib/gitlab/url_builder_spec.rb new file mode 100644 index 00000000000..eb47bee8336 --- /dev/null +++ b/spec/lib/gitlab/url_builder_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +describe Gitlab::UrlBuilder do + describe 'When asking for an issue' do + it 'returns the issue url' do + issue = create(:issue) + url = Gitlab::UrlBuilder.new(:issue).build(issue.id) + expect(url).to eq "#{Settings.gitlab['url']}/#{issue.project.to_param}/issues/#{issue.iid}" + end + end +end diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index f415e750dd5..1fdd959da9d 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -65,26 +65,4 @@ describe Event do it { @event.branch_name.should == "master" } it { @event.author.should == @user } end - - describe 'Team events' do - let(:user_project) { double.as_null_object } - let(:observer) { UsersProjectObserver.instance } - - before { - Event.should_receive :create - observer.stub(notification: double.as_null_object) - } - - describe "Joined project team" do - it "should create event" do - observer.after_create user_project - end - end - - describe "Left project team" do - it "should create event" do - observer.after_destroy user_project - end - end - end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index a2519fbd684..93eae5a9ebd 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -27,9 +27,6 @@ require 'spec_helper' describe Project do - before { enable_observers } - after { disable_observers } - describe "Associations" do it { should belong_to(:group) } it { should belong_to(:namespace) } diff --git a/spec/models/system_hook_spec.rb b/spec/models/system_hook_spec.rb index 47d6d861d70..2b98acdeb6c 100644 --- a/spec/models/system_hook_spec.rb +++ b/spec/models/system_hook_spec.rb @@ -19,21 +19,20 @@ require "spec_helper" describe SystemHook do describe "execute" do - before(:each) { ActiveRecord::Base.observers.enable(:all) } - before(:each) do @system_hook = create(:system_hook) WebMock.stub_request(:post, @system_hook.url) end it "project_create hook" do - project = create(:project) + Projects::CreateService.new(create(:user), name: 'empty').execute WebMock.should have_requested(:post, @system_hook.url).with(body: /project_create/).once end it "project_destroy hook" do - project = create(:project) - project.destroy + user = create(:user) + project = create(:empty_project, namespace: user.namespace) + Projects::DestroyService.new(project, user, {}).execute WebMock.should have_requested(:post, @system_hook.url).with(body: /project_destroy/).once end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 4e0ebb584d1..0a665b7defb 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -145,7 +145,6 @@ describe User do describe 'projects' do before do - ActiveRecord::Base.observers.enable(:user_observer) @user = create :user @project = create :project, namespace: @user.namespace @project_2 = create :project, group: create(:group) # Grant MASTER access to the user @@ -168,7 +167,6 @@ describe User do describe 'groups' do before do - ActiveRecord::Base.observers.enable(:user_observer) @user = create :user @group = create :group @group.add_owner(@user) @@ -181,7 +179,6 @@ describe User do describe 'group multiple owners' do before do - ActiveRecord::Base.observers.enable(:user_observer) @user = create :user @user2 = create :user @group = create :group @@ -195,7 +192,6 @@ describe User do describe 'namespaced' do before do - ActiveRecord::Base.observers.enable(:user_observer) @user = create :user @project = create :project, namespace: @user.namespace end @@ -339,7 +335,7 @@ describe User do user.all_ssh_keys.should include(key.key) end end - + describe :avatar_type do let(:user) { create(:user) } diff --git a/spec/models/users_group_spec.rb b/spec/models/users_group_spec.rb index acecae07146..05dd97d92d4 100644 --- a/spec/models/users_group_spec.rb +++ b/spec/models/users_group_spec.rb @@ -37,4 +37,32 @@ describe UsersGroup do it { should respond_to(:user_name) } it { should respond_to(:user_email) } end + + context 'notification' do + describe "#after_create" do + it "should send email to user" do + membership = build(:users_group) + membership.stub(notification_service: double('NotificationService').as_null_object) + membership.should_receive(:notification_service) + membership.save + end + end + + describe "#after_update" do + before do + @membership = create :users_group + @membership.stub(notification_service: double('NotificationService').as_null_object) + end + + it "should send email to user" do + @membership.should_receive(:notification_service) + @membership.update_attribute(:group_access, UsersGroup::MASTER) + end + + it "does not send an email when the access level has not changed" do + @membership.should_not_receive(:notification_service) + @membership.update_attribute(:group_access, UsersGroup::OWNER) + end + end + end end diff --git a/spec/observers/note_observer_spec.rb b/spec/observers/note_observer_spec.rb deleted file mode 100644 index f8693355b23..00000000000 --- a/spec/observers/note_observer_spec.rb +++ /dev/null @@ -1,56 +0,0 @@ -require 'spec_helper' - -describe NoteObserver do - subject { NoteObserver.instance } - before { subject.stub(notification: double('NotificationService').as_null_object) } - - let(:team_without_author) { (1..2).map { |n| double :user, id: n } } - let(:note) { double(:note).as_null_object } - - describe '#after_create' do - - it 'is called after a note is created' do - subject.should_receive :after_create - - Note.observers.enable :note_observer do - create(:note) - end - end - - it 'sends out notifications' do - subject.should_receive(:notification) - - subject.after_create(note) - end - - it 'creates cross-reference notes as appropriate' do - @p = create(:project) - @referenced = create(:issue, project: @p) - @referencer = create(:issue, project: @p) - @author = create(:user) - - Note.should_receive(:create_cross_reference_note).with(@referenced, @referencer, @author, @p) - - Note.observers.enable :note_observer do - create(:note, project: @p, author: @author, noteable: @referencer, - note: "Duplicate of ##{@referenced.iid}") - end - end - - it "doesn't cross-reference system notes" do - Note.should_receive(:create_cross_reference_note).once - - Note.observers.enable :note_observer do - Note.create_cross_reference_note(create(:issue), create(:issue)) - end - end - end - - describe '#after_update' do - it 'checks for new cross-references' do - note.should_receive(:notice_added_references) - - subject.after_update(note) - end - end -end diff --git a/spec/observers/user_observer_spec.rb b/spec/observers/user_observer_spec.rb deleted file mode 100644 index 9aeade535f9..00000000000 --- a/spec/observers/user_observer_spec.rb +++ /dev/null @@ -1,27 +0,0 @@ -require 'spec_helper' - -describe UserObserver do - before(:each) { enable_observers } - after(:each) {disable_observers} - subject { UserObserver.instance } - before { subject.stub(notification: double('NotificationService').as_null_object) } - - it 'calls #after_create when new users are created' do - new_user = build(:user) - subject.should_receive(:after_create).with(new_user) - new_user.save - end - - context 'when a new user is created' do - it 'sends an email' do - subject.should_receive(:notification) - create(:user) - end - - it 'trigger logger' do - user = double(:user, id: 42, password: 'P@ssword!', name: 'John', email: 'u@mail.local', extern_uid?: false) - Gitlab::AppLogger.should_receive(:info) - create(:user) - end - end -end diff --git a/spec/observers/users_group_observer_spec.rb b/spec/observers/users_group_observer_spec.rb deleted file mode 100644 index 2ab99c33b78..00000000000 --- a/spec/observers/users_group_observer_spec.rb +++ /dev/null @@ -1,32 +0,0 @@ -require 'spec_helper' - -describe UsersGroupObserver do - before(:each) { enable_observers } - after(:each) { disable_observers } - - subject { UsersGroupObserver.instance } - before { subject.stub(notification: double('NotificationService').as_null_object) } - - describe "#after_create" do - it "should send email to user" do - subject.should_receive(:notification) - create(:users_group) - end - end - - describe "#after_update" do - before do - @membership = create :users_group - end - - it "should send email to user" do - subject.should_receive(:notification) - @membership.update_attribute(:group_access, UsersGroup::MASTER) - end - - it "does not send an email when the access level has not changed" do - subject.should_not_receive(:notification) - @membership.update_attribute(:group_access, UsersGroup::OWNER) - end - end -end diff --git a/spec/observers/users_project_observer_spec.rb b/spec/observers/users_project_observer_spec.rb deleted file mode 100644 index b024465e8c3..00000000000 --- a/spec/observers/users_project_observer_spec.rb +++ /dev/null @@ -1,63 +0,0 @@ -require 'spec_helper' - -describe UsersProjectObserver do - before(:each) { enable_observers } - after(:each) { disable_observers } - - let(:user) { create(:user) } - let(:project) { create(:project) } - subject { UsersProjectObserver.instance } - before { subject.stub(notification: double('NotificationService').as_null_object) } - - describe "#after_update" do - before do - @users_project = create :users_project - end - - it "should called when UsersProject updated" do - subject.should_receive(:after_update) - @users_project.update_attribute(:project_access, UsersProject::MASTER) - end - - it "should send email to user" do - subject.should_receive(:notification) - @users_project.update_attribute(:project_access, UsersProject::OWNER) - end - - it "should not called after UsersProject destroyed" do - subject.should_not_receive(:after_update) - @users_project.destroy - end - end - - describe "#after_destroy" do - before do - @users_project = create :users_project - end - - it "should called when UsersProject destroyed" do - subject.should_receive(:after_destroy) - @users_project.destroy - end - - it "should create new event" do - Event.should_receive(:create) - @users_project.destroy - end - end - - describe "#after_create" do - it "should send email to user" do - subject.should_receive(:notification) - Event.stub(create: true) - - create(:users_project) - end - - it "should create new event" do - Event.should_receive(:create) - - create(:users_project) - end - end -end diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb index 72589da5d40..b145e620122 100644 --- a/spec/requests/api/branches_spec.rb +++ b/spec/requests/api/branches_spec.rb @@ -3,8 +3,6 @@ require 'mime/types' describe API::API, api: true do include ApiHelpers - before(:each) { enable_observers } - after(:each) {disable_observers} let(:user) { create(:user) } let(:user2) { create(:user) } diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index 165276d1435..b56269d275d 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -3,9 +3,6 @@ require 'mime/types' describe API::API, api: true do include ApiHelpers - before(:each) { enable_observers } - after(:each) {disable_observers} - let(:user) { create(:user) } let(:user2) { create(:user) } let!(:project) { create(:project, creator_id: user.id) } diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb index edfe935d985..e84122f2fbc 100644 --- a/spec/requests/api/files_spec.rb +++ b/spec/requests/api/files_spec.rb @@ -2,9 +2,6 @@ require 'spec_helper' describe API::API, api: true do include ApiHelpers - before(:each) { ActiveRecord::Base.observers.enable(:user_observer) } - after(:each) { ActiveRecord::Base.observers.disable(:user_observer) } - let(:user) { create(:user) } let!(:project) { create(:project, namespace: user.namespace ) } before { project.team << [user, :developer] } diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index 8797c3fbbbf..dbe8043c633 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -2,9 +2,6 @@ require 'spec_helper' describe API::API, api: true do include ApiHelpers - before(:each) { ActiveRecord::Base.observers.enable(:user_observer) } - after(:each) { ActiveRecord::Base.observers.disable(:user_observer) } - let(:user) { create(:user) } let(:key) { create(:key, user: user) } let(:project) { create(:project) } diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index e62bc844352..dff7f20cb32 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -2,9 +2,6 @@ require 'spec_helper' describe API::API, api: true do include ApiHelpers - before(:each) { ActiveRecord::Base.observers.enable(:user_observer) } - after(:each) { ActiveRecord::Base.observers.disable(:user_observer) } - let(:user) { create(:user) } let!(:project) { create(:project, namespace: user.namespace ) } let!(:issue) { create(:issue, author: user, assignee: user, project: project) } diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 2fb3684fdf0..3611d9d6dc3 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -2,8 +2,6 @@ require "spec_helper" describe API::API, api: true do include ApiHelpers - before(:each) { ActiveRecord::Base.observers.enable(:user_observer) } - 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, :simple, author: user, assignee: user, source_project: project, target_project: project, title: "Test") } diff --git a/spec/requests/api/milestones_spec.rb b/spec/requests/api/milestones_spec.rb index 0d2740c5acf..f0619a1c801 100644 --- a/spec/requests/api/milestones_spec.rb +++ b/spec/requests/api/milestones_spec.rb @@ -2,9 +2,6 @@ require 'spec_helper' describe API::API, api: true do include ApiHelpers - before(:each) { enable_observers } - after(:each) {disable_observers} - let(:user) { create(:user) } let!(:project) { create(:project, namespace: user.namespace ) } let!(:milestone) { create(:milestone, project: project) } @@ -92,9 +89,6 @@ describe API::API, api: true do end describe "PUT /projects/:id/milestones/:milestone_id to test observer on close" do - before { enable_observers } - after { disable_observers } - it "should create an activity event when an milestone is closed" do Event.should_receive(:create) diff --git a/spec/requests/api/namespaces_spec.rb b/spec/requests/api/namespaces_spec.rb index 2de7d1e23c3..d9d52468b15 100644 --- a/spec/requests/api/namespaces_spec.rb +++ b/spec/requests/api/namespaces_spec.rb @@ -2,9 +2,6 @@ require 'spec_helper' describe API::API, api: true do include ApiHelpers - before(:each) { ActiveRecord::Base.observers.enable(:user_observer) } - after(:each) { ActiveRecord::Base.observers.disable(:user_observer) } - let(:admin) { create(:admin) } let!(:group1) { create(:group) } let!(:group2) { create(:group) } diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb index 2875db04ee4..7aa53787aed 100644 --- a/spec/requests/api/notes_spec.rb +++ b/spec/requests/api/notes_spec.rb @@ -2,9 +2,6 @@ require 'spec_helper' describe API::API, api: true do include ApiHelpers - before(:each) { ActiveRecord::Base.observers.enable(:user_observer) } - after(:each) { ActiveRecord::Base.observers.disable(:user_observer) } - let(:user) { create(:user) } let!(:project) { create(:project, namespace: user.namespace ) } let!(:issue) { create(:issue, project: project, author: user) } @@ -128,14 +125,10 @@ describe API::API, api: true do end describe "POST /projects/:id/noteable/:noteable_id/notes to test observer on create" do - before { enable_observers } - after { disable_observers } - it "should create an activity event when an issue note is created" do Event.should_receive(:create) post api("/projects/#{project.id}/issues/#{issue.id}/notes", user), body: 'hi!' end end - end diff --git a/spec/requests/api/project_hooks_spec.rb b/spec/requests/api/project_hooks_spec.rb index 6e281f5a43e..cdb5e3d0612 100644 --- a/spec/requests/api/project_hooks_spec.rb +++ b/spec/requests/api/project_hooks_spec.rb @@ -2,9 +2,6 @@ require 'spec_helper' describe API::API, 'ProjectHooks', api: true do include ApiHelpers - before(:each) { enable_observers } - after(:each) { disable_observers } - let(:user) { create(:user) } let(:user3) { create(:user) } let!(:project) { create(:project, creator_id: user.id, namespace: user.namespace) } diff --git a/spec/requests/api/project_members_spec.rb b/spec/requests/api/project_members_spec.rb index 032f850010c..3c480c2ac4b 100644 --- a/spec/requests/api/project_members_spec.rb +++ b/spec/requests/api/project_members_spec.rb @@ -2,9 +2,6 @@ require 'spec_helper' describe API::API, api: true do include ApiHelpers - before(:each) { enable_observers } - after(:each) { disable_observers } - let(:user) { create(:user) } let(:user2) { create(:user) } let(:user3) { create(:user) } diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 415735091c3..41841e855fd 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -2,9 +2,6 @@ require 'spec_helper' describe API::API, api: true do include ApiHelpers - before(:each) { enable_observers } - after(:each) { disable_observers } - let(:user) { create(:user) } let(:user2) { create(:user) } let(:user3) { create(:user) } diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb index 5afb3bddcb7..94850d0f1ae 100644 --- a/spec/requests/api/repositories_spec.rb +++ b/spec/requests/api/repositories_spec.rb @@ -3,9 +3,6 @@ require 'mime/types' describe API::API, api: true do include ApiHelpers - before(:each) { enable_observers } - after(:each) {disable_observers} - let(:user) { create(:user) } let(:user2) { create(:user) } let!(:project) { create(:project, creator_id: user.id) } diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb index 684f8615999..f883c9e028a 100644 --- a/spec/requests/api/services_spec.rb +++ b/spec/requests/api/services_spec.rb @@ -2,9 +2,6 @@ require "spec_helper" describe API::API, api: true do include ApiHelpers - before(:each) { ActiveRecord::Base.observers.enable(:user_observer) } - after(:each) { ActiveRecord::Base.observers.disable(:user_observer) } - let(:user) { create(:user) } let(:project) {create(:project, creator_id: user.id, namespace: user.namespace) } diff --git a/spec/services/issues/bulk_update_context_spec.rb b/spec/services/issues/bulk_update_context_spec.rb index 548109a8450..f4c9148f1a3 100644 --- a/spec/services/issues/bulk_update_context_spec.rb +++ b/spec/services/issues/bulk_update_context_spec.rb @@ -1,12 +1,9 @@ require 'spec_helper' describe Issues::BulkUpdateService do - before(:each) { ActiveRecord::Base.observers.enable(:user_observer) } - after(:each) { ActiveRecord::Base.observers.disable(:user_observer) } - let(:issue) { create(:issue, project: @project) - } + } before do @user = create :user @@ -23,7 +20,7 @@ describe Issues::BulkUpdateService do @issues = 5.times.collect do create(:issue, project: @project) end - @params = { + @params = { update: { status: 'closed', issues_ids: @issues.map(&:id) @@ -48,7 +45,7 @@ describe Issues::BulkUpdateService do @issues = 5.times.collect do create(:closed_issue, project: @project) end - @params = { + @params = { update: { status: 'reopen', issues_ids: @issues.map(&:id) @@ -71,7 +68,7 @@ describe Issues::BulkUpdateService do before do @new_assignee = create :user - @params = { + @params = { update: { issues_ids: [issue.id], assignee_id: @new_assignee.id @@ -93,7 +90,7 @@ describe Issues::BulkUpdateService do before do @milestone = create :milestone - @params = { + @params = { update: { issues_ids: [issue.id], milestone_id: @milestone.id diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb new file mode 100644 index 00000000000..106c14bc015 --- /dev/null +++ b/spec/services/notes/create_service_spec.rb @@ -0,0 +1,27 @@ +require 'spec_helper' + +describe Notes::CreateService do + let(:project) { create(:empty_project) } + let(:issue) { create(:issue, project: project) } + let(:user) { create(:user) } + + describe :execute do + context "valid params" do + before do + project.team << [user, :master] + opts = { + note: 'Awesome comment', + description: 'please fix', + noteable_type: 'Issue', + noteable_id: issue.id + } + + @note = Notes::CreateService.new(project, user, opts).execute + end + + it { @note.should be_valid } + it { @note.note.should == 'Awesome comment' } + end + end +end + diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index 57a4240f7a2..f82a14d482c 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -95,6 +95,49 @@ describe NotificationService do end end + context 'issue note mention' do + let(:issue) { create(:issue, assignee: create(:user)) } + let(:mentioned_issue) { create(:issue, assignee: issue.assignee) } + let(:note) { create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: '@all mentioned') } + + before do + build_team(note.project) + end + + describe :new_note do + it do + # Notify all team members + note.project.team.members.each do |member| + # User with disabled notification should not be notified + next if member.id == @u_disabled.id + should_email(member.id) + end + should_email(note.noteable.author_id) + should_email(note.noteable.assignee_id) + + should_not_email(note.author_id) + should_not_email(@u_disabled.id) + should_not_email(@u_not_mentioned.id) + notification.new_note(note) + end + + it 'filters out "mentioned in" notes' do + mentioned_note = Note.create_cross_reference_note(mentioned_issue, issue, issue.author, issue.project) + + 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) + 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 let(:note) { create(:note_on_commit) } @@ -312,6 +355,7 @@ describe NotificationService do @u_disabled = create(:user, notification_level: Notification::N_DISABLED) @u_mentioned = create(:user, username: 'mention', notification_level: Notification::N_PARTICIPATING) @u_committer = create(:user, username: 'committer') + @u_not_mentioned = create(:user, username: 'regular', notification_level: Notification::N_PARTICIPATING) project.team << [@u_watcher, :master] project.team << [@u_participating, :master] diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb index 0eac6bed74b..74c23418a28 100644 --- a/spec/services/projects/create_service_spec.rb +++ b/spec/services/projects/create_service_spec.rb @@ -1,9 +1,6 @@ require 'spec_helper' describe Projects::CreateService do - before(:each) { ActiveRecord::Base.observers.enable(:user_observer) } - after(:each) { ActiveRecord::Base.observers.disable(:user_observer) } - describe :create_by_user do before do @user = create :user @@ -66,11 +63,8 @@ describe Projects::CreateService do @settings.stub(:merge_requests) { true } @settings.stub(:wiki) { true } @settings.stub(:snippets) { true } - stub_const("Settings", Class.new) - @restrictions = double("restrictions") - @restrictions.stub(:restricted_visibility_levels) { [] } - Settings.stub_chain(:gitlab).and_return(@restrictions) - Settings.stub_chain(:gitlab, :default_projects_features).and_return(@settings) + Gitlab.config.gitlab.stub(restricted_visibility_levels: []) + Gitlab.config.gitlab.stub(:default_projects_features).and_return(@settings) end context 'should be public when setting is public' do @@ -109,11 +103,9 @@ describe Projects::CreateService do @settings.stub(:wiki) { true } @settings.stub(:snippets) { true } @settings.stub(:visibility_level) { Gitlab::VisibilityLevel::PRIVATE } - stub_const("Settings", Class.new) - @restrictions = double("restrictions") - @restrictions.stub(:restricted_visibility_levels) { [ Gitlab::VisibilityLevel::PUBLIC ] } - Settings.stub_chain(:gitlab).and_return(@restrictions) - Settings.stub_chain(:gitlab, :default_projects_features).and_return(@settings) + @restrictions = [ Gitlab::VisibilityLevel::PUBLIC ] + Gitlab.config.gitlab.stub(restricted_visibility_levels: @restrictions) + Gitlab.config.gitlab.stub(:default_projects_features).and_return(@settings) end context 'should be private when option is public' do @@ -158,4 +150,3 @@ describe Projects::CreateService do Projects::CreateService.new(user, opts).execute end end - diff --git a/spec/services/projects/image_service_spec.rb b/spec/services/projects/image_service_spec.rb index 070c21698cf..23c4e227ae3 100644 --- a/spec/services/projects/image_service_spec.rb +++ b/spec/services/projects/image_service_spec.rb @@ -1,9 +1,6 @@ require 'spec_helper' describe Projects::ImageService do - before(:each) { enable_observers } - after(:each) { disable_observers } - describe 'Image service' do before do @user = create :user diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb index 563a8b0c01c..2508dfc4565 100644 --- a/spec/services/projects/transfer_service_spec.rb +++ b/spec/services/projects/transfer_service_spec.rb @@ -1,9 +1,6 @@ require 'spec_helper' describe Projects::TransferService do - before(:each) { enable_observers } - after(:each) {disable_observers} - let(:user) { create(:user) } let(:group) { create(:group) } let(:group2) { create(:group) } diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb index 1854c0d8233..bb0470e3771 100644 --- a/spec/services/projects/update_service_spec.rb +++ b/spec/services/projects/update_service_spec.rb @@ -1,9 +1,6 @@ require 'spec_helper' describe Projects::UpdateService do - before(:each) { ActiveRecord::Base.observers.enable(:user_observer) } - after(:each) { ActiveRecord::Base.observers.disable(:user_observer) } - describe :update_by_user do before do @user = create :user diff --git a/spec/services/search_service_spec.rb b/spec/services/search_service_spec.rb index b467282a5d6..daffe98a8ed 100644 --- a/spec/services/search_service_spec.rb +++ b/spec/services/search_service_spec.rb @@ -1,19 +1,18 @@ require 'spec_helper' describe 'Search::GlobalService' do - let(:user) { create(:user, namespace: found_namespace) } - let(:public_user) { create(:user, namespace: public_namespace) } - let(:internal_user) { create(:user, namespace: internal_namespace) } + let(:user) { create(:user) } + let(:public_user) { create(:user) } + let(:internal_user) { create(:user) } - let(:found_namespace) { create(:namespace, name: 'searchable namespace', path:'another_thing') } - let(:unfound_namespace) { create(:namespace, name: 'unfound namespace', path: 'yet_something_else') } - let(:internal_namespace) { create(:namespace, name: 'searchable internal namespace', path: 'something_internal') } - let(:public_namespace) { create(:namespace, name: 'searchable public namespace', path: 'something_public') } + let!(:found_project) { create(:empty_project, :private, name: 'searchable_project') } + let!(:unfound_project) { create(:empty_project, :private, name: 'unfound_project') } + let!(:internal_project) { create(:empty_project, :internal, name: 'searchable_internal_project') } + let!(:public_project) { create(:empty_project, :public, name: 'searchable_public_project') } - let!(:found_project) { create(:project, :private, name: 'searchable_project', creator_id: user.id, namespace: found_namespace) } - let!(:unfound_project) { create(:project, :private, name: 'unfound_project', creator_id: user.id, namespace: unfound_namespace) } - let!(:internal_project) { create(:project, :internal, name: 'searchable_internal_project', creator_id: internal_user.id, namespace: internal_namespace) } - let!(:public_project) { create(:project, :public, name: 'searchable_public_project', creator_id: public_user.id, namespace: public_namespace) } + before do + found_project.team << [user, :master] + end describe '#execute' do context 'unauthenticated' do @@ -38,7 +37,7 @@ describe 'Search::GlobalService' do end it 'namespace name should be searchable' do - context = Search::GlobalService.new(user, search: "searchable namespace") + context = Search::GlobalService.new(user, search: found_project.namespace.path) results = context.execute results[:projects].should match_array [found_project] end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 65a641bcf12..2181238ae9f 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -43,7 +43,7 @@ RSpec.configure do |config| # instead of true. config.before(:suite) do - TestEnv.init(observers: false, init_repos: true, repos: false) + TestEnv.init(init_repos: true, repos: false) end config.before(:each) do TestEnv.setup_stubs diff --git a/spec/support/login_helpers.rb b/spec/support/login_helpers.rb index cc0ec2f4e3d..7713e9f17d7 100644 --- a/spec/support/login_helpers.rb +++ b/spec/support/login_helpers.rb @@ -3,9 +3,7 @@ module LoginHelpers # # role - User role (e.g., :admin, :user) def login_as(role) - ActiveRecord::Base.observers.enable(:user_observer) do - @user = create(role) - end + @user = create(role) login_with(@user) end diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb index b1bb65a836e..85059dfa9e9 100644 --- a/spec/support/test_env.rb +++ b/spec/support/test_env.rb @@ -17,14 +17,6 @@ module TestEnv def init(opts = {}) RSpec::Mocks::setup(self) - # Disable observers to improve test speed - # - # You can enable it in whole test case where needed by next string: - # - # before(:each) { enable_observers } - # - disable_observers if opts[:observers] == false - # Disable mailer for spinach tests disable_mailer if opts[:mailer] == false setup_stubs @@ -33,14 +25,6 @@ module TestEnv setup_test_repos(opts) if opts[:repos] == true end - def enable_observers - ActiveRecord::Base.observers.enable(:all) - end - - def disable_observers - ActiveRecord::Base.observers.disable(:all) - end - def disable_mailer NotificationService.any_instance.stub(mailer: double.as_null_object) end @@ -89,10 +73,6 @@ module TestEnv Repository.any_instance.stub( size: 12.45 ) - - BaseObserver.any_instance.stub( - current_user: double("current_user", id: 1) - ) end def clear_repo_dir(namespace, name) |