Commit e64343d6 authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge branch 'master' of dev.gitlab.org:gitlab/gitlabhq into ce-changes

Signed-off-by: default avatarDmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>

Conflicts:
	VERSION
	app/views/layouts/devise.html.haml
	features/project/project.feature
	features/steps/project/project.rb
parents 750f63ba 024e0577
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
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 v 7.0.0
- The CPU no longer overheats when you hold down the spacebar - The CPU no longer overheats when you hold down the spacebar
- Improve edit file UI - Improve edit file UI
......
# Contribute to GitLab # Contribute to GitLab
This guide details how contribute to GitLab. 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 want to know how the GitLab team handles contributions have a look at [the GitLab contributing process](PROCESS.md). 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 ## Contributor license agreement
...@@ -75,9 +75,9 @@ If you can, please submit a merge request with the fix or improvements including ...@@ -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. 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:** **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 ...@@ -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. Keeps the GitLab code base clean and well structured
1. Contains functionality we think other users will benefit from too 1. Contains functionality we think other users will benefit from too
1. Doesn't add configuration options since they complicate future changes 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 1. It conforms to the following style guides
## Style guides ## Style guides
...@@ -113,4 +114,4 @@ For examples of feedback on merge requests please look at already [closed merge ...@@ -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. [Shell commands](doc/development/shell_commands.md) created by GitLab contributors to enhance security
1. [Markdown](http://www.cirosantilli.com/markdown-styleguide) 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 ...@@ -11,7 +11,6 @@ end
gem "rails", "~> 4.1.0" gem "rails", "~> 4.1.0"
gem "protected_attributes" gem "protected_attributes"
gem 'rails-observers'
# Make links from text # Make links from text
gem 'rails_autolink', '~> 1.1' gem 'rails_autolink', '~> 1.1'
......
...@@ -365,8 +365,6 @@ GEM ...@@ -365,8 +365,6 @@ GEM
bundler (>= 1.3.0, < 2.0) bundler (>= 1.3.0, < 2.0)
railties (= 4.1.1) railties (= 4.1.1)
sprockets-rails (~> 2.0) sprockets-rails (~> 2.0)
rails-observers (0.1.2)
activemodel (~> 4.0)
rails_autolink (1.1.6) rails_autolink (1.1.6)
rails (> 3.1) rails (> 3.1)
rails_best_practices (1.14.4) rails_best_practices (1.14.4)
...@@ -645,7 +643,6 @@ DEPENDENCIES ...@@ -645,7 +643,6 @@ DEPENDENCIES
rack-cors rack-cors
rack-mini-profiler rack-mini-profiler
rails (~> 4.1.0) rails (~> 4.1.0)
rails-observers
rails_autolink (~> 1.1) rails_autolink (~> 1.1)
rails_best_practices rails_best_practices
raphael-rails (~> 2.1.2) raphael-rails (~> 2.1.2)
......
7.0.0-ee 7.1.0.pre-ee
...@@ -143,6 +143,14 @@ $ -> ...@@ -143,6 +143,14 @@ $ ->
$(@).next('table').show() $(@).next('table').show()
$(@).remove() $(@).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 # Disable an element and add the 'disabled' Bootstrap class
$.fn.extend disable: -> $.fn.extend disable: ->
......
...@@ -21,7 +21,6 @@ $(document).ready -> ...@@ -21,7 +21,6 @@ $(document).ready ->
$(".div-dropzone").append divSpinner $(".div-dropzone").append divSpinner
$(".div-dropzone-spinner").append iconSpinner $(".div-dropzone-spinner").append iconSpinner
dropzone = $(".div-dropzone").dropzone( dropzone = $(".div-dropzone").dropzone(
url: project_image_path_upload url: project_image_path_upload
dictDefaultMessage: "" dictDefaultMessage: ""
...@@ -77,6 +76,107 @@ $(document).ready -> ...@@ -77,6 +76,107 @@ $(document).ready ->
return 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) -> $(".markdown-selector").click (e) ->
e.preventDefault() e.preventDefault()
$(".div-dropzone").click() $(".div-dropzone").click()
......
...@@ -109,10 +109,10 @@ class MergeRequest ...@@ -109,10 +109,10 @@ class MergeRequest
type: 'GET' type: 'GET'
url: this.$('.merge-request-tabs .diffs-tab a').attr('href') url: this.$('.merge-request-tabs .diffs-tab a').attr('href')
beforeSend: => beforeSend: =>
this.$('.status').addClass 'loading' this.$('.mr-loading-status .loading').show()
complete: => complete: =>
@diffs_loaded = true @diffs_loaded = true
this.$('.status').removeClass 'loading' this.$('.mr-loading-status .loading').hide()
success: (data) => success: (data) =>
this.$(".diffs").html(data.html) this.$(".diffs").html(data.html)
dataType: 'json' dataType: 'json'
......
...@@ -142,6 +142,13 @@ class Notes ...@@ -142,6 +142,13 @@ class Notes
# remove the note (will be added again below) # remove the note (will be added again below)
row.next().find(".note").remove() row.next().find(".note").remove()
# 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 # append new note to all matching discussions
$(".notes[rel='" + note.discussion_id + "']").append note.html $(".notes[rel='" + note.discussion_id + "']").append note.html
......
...@@ -8,8 +8,6 @@ ...@@ -8,8 +8,6 @@
*= require select2 *= require select2
*= require highlightjs.min *= require highlightjs.min
*= require_self *= require_self
*= require nprogress
*= require nprogress-bootstrap
*= require dropzone/basic *= require dropzone/basic
*/ */
...@@ -20,6 +18,12 @@ ...@@ -20,6 +18,12 @@
*/ */
@import 'gl_bootstrap'; @import 'gl_bootstrap';
/**
* NProgress load bar css
*/
@import 'nprogress';
@import 'nprogress-bootstrap';
/** /**
* Font icons * Font icons
* *
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
.append-bottom-15 { margin-bottom:15px } .append-bottom-15 { margin-bottom:15px }
.append-bottom-20 { margin-bottom:20px } .append-bottom-20 { margin-bottom:20px }
.inline { display: inline-block } .inline { display: inline-block }
.center { text-align: center }
.underlined-link { text-decoration: underline; } .underlined-link { text-decoration: underline; }
.hint { font-style: italic; color: #999; } .hint { font-style: italic; color: #999; }
...@@ -55,7 +56,7 @@ pre { ...@@ -55,7 +56,7 @@ pre {
.dropdown-menu > li > a:hover, .dropdown-menu > li > a:hover,
.dropdown-menu > li > a:focus { .dropdown-menu > li > a:focus {
background: $bg_style_color; background: $bg_primary;
color: #FFF color: #FFF
} }
...@@ -256,13 +257,6 @@ li.note { ...@@ -256,13 +257,6 @@ li.note {
} }
} }
h1.http_status_code {
font-size: 56px;
line-height: 100px;
font-weight: normal;
color: #456;
}
.control-group { .control-group {
.controls { .controls {
span { span {
......
...@@ -76,30 +76,10 @@ label { ...@@ -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 { .fieldset-form fieldset {
margin-bottom: 20px; margin-bottom: 20px;
} }
.form-control {
@include box-shadow(none);
}
...@@ -41,8 +41,8 @@ ...@@ -41,8 +41,8 @@
} }
.ui-state-active { .ui-state-active {
border: 1px solid $bg_style_color; border: 1px solid $bg_primary;
background: $bg_style_color; background: $bg_primary;
color: #FFF; color: #FFF;
} }
......
...@@ -42,7 +42,7 @@ ...@@ -42,7 +42,7 @@
.select2-results { .select2-results {
max-height: 350px; max-height: 350px;
.select2-highlighted { .select2-highlighted {
background: $bg_style_color; background: $bg_primary;
} }
} }
} }
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
$font-size-base: 13px !default; $font-size-base: 13px !default;
$nav-pills-active-link-hover-bg: $bg_primary; $nav-pills-active-link-hover-bg: $bg_primary;
$pagination-active-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 // Core variables and mixins
@import "bootstrap/variables"; @import "bootstrap/variables";
......
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
* General Colors * General Colors
*/ */
$style_color: #474D57; $style_color: #474D57;
$bg_style_color: #2299BB;
$hover: #D9EDF7; $hover: #D9EDF7;
/* /*
...@@ -40,3 +39,8 @@ $border_warning: #EB9532; ...@@ -40,3 +39,8 @@ $border_warning: #EB9532;
*/ */
$added: #63c363; $added: #63c363;
$deleted: #f77; $deleted: #f77;
/**
*
*/
$nprogress-color: #3498db;
...@@ -213,3 +213,27 @@ li.commit { ...@@ -213,3 +213,27 @@ li.commit {
padding: 4px 12px; 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;
}
}
.error-page {
max-width: 400px;
margin: 0 auto;
h1, h2, h3 {
text-align: center;
}
h1 {
font-size: 56px;
line-height: 100px;
font-weight: 300;
}
}
...@@ -156,12 +156,13 @@ ...@@ -156,12 +156,13 @@
.filter_icon { .filter_icon {
a { a {
text-align:center; text-align:center;
background: #EEE; background: $bg_primary;
margin-bottom: 10px; margin-bottom: 10px;
float: left; float: left;
padding: 9px 6px; padding: 9px 6px;
font-size: 18px; font-size: 18px;
width: 40px; width: 40px;
color: #FFF;
@include border-radius(3px); @include border-radius(3px);
} }
......
...@@ -6,14 +6,12 @@ header { ...@@ -6,14 +6,12 @@ header {
&.navbar-gitlab { &.navbar-gitlab {
margin-bottom: 0; margin-bottom: 0;
min-height: 40px; min-height: 40px;
border: none;
.navbar-inner { .navbar-inner {
background: #F1F1F1;
border-bottom: 1px solid #DDD;
filter: none; filter: none;
.nav > li > a { .nav > li > a {
color: $style_color;
font-size: 14px; font-size: 14px;
line-height: 32px; line-height: 32px;
padding: 6px 10px; padding: 6px 10px;
...@@ -248,8 +246,6 @@ header { ...@@ -248,8 +246,6 @@ header {
float: left; float: left;
height: 46px; height: 46px;
width: 2px; width: 2px;
background: white;
border-left: 1px solid #DDD;
margin-left: 10px; margin-left: 10px;
margin-right: 10px; margin-right: 10px;
} }
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
margin: 20px 0; margin: 20px 0;
margin-top: 0; margin-top: 0;
padding-top: 4px; padding-top: 4px;
border-bottom: 1px solid #E1E1E1; border-bottom: 1px solid #E9E9E9;
ul { ul {
padding: 0; padding: 0;
......
...@@ -43,38 +43,14 @@ ul.notes { ...@@ -43,38 +43,14 @@ ul.notes {
} }
.discussion { .discussion {
padding: 8px 0; padding: 10px 0;
overflow: hidden; overflow: hidden;
display: block; display: block;
position:relative; position:relative;
border-bottom: 1px solid #EEE;
.discussion-body { .discussion-body {
margin-left: 50px; 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 { ...@@ -137,10 +113,6 @@ ul.notes {
vertical-align: top; vertical-align: top;
} }
} }
.reply-btn {
margin: 5px;
}
} }
/** /**
...@@ -376,3 +348,17 @@ ul.notes { ...@@ -376,3 +348,17 @@ ul.notes {
margin-top: 5px; margin-top: 5px;
margin-bottom: 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;
}
}
...@@ -56,10 +56,10 @@ ...@@ -56,10 +56,10 @@
text-align: center; text-align: center;
.prev { .prev {
@extend .thumbnail; height: 80px;
height: 30px; width: 160px;
width: 175px;
margin-bottom: 10px; margin-bottom: 10px;
@include border-radius(4px);
&.classic { &.classic {
background: #31363e; background: #31363e;
...@@ -92,10 +92,13 @@ ...@@ -92,10 +92,13 @@
text-align: center; text-align: center;
.prev { .prev {
@extend .thumbnail; width: 160px;
height: 151px;
width: 220px;
margin-bottom: 10px; margin-bottom: 10px;
img {
max-width: 100%;
@include border-radius(4px);
}
} }
} }
} }
......
...@@ -4,11 +4,21 @@ ...@@ -4,11 +4,21 @@
* *
*/ */
.ui_basic { .ui_basic {
header {
&.navbar-gitlab {
.navbar-inner {
background: #F1F1F1;
border-bottom: 1px solid #DDD;
.nav > li > a {
color: $style_color;
}
.separator { .separator {
background: #F9F9F9; background: #F9F9F9;
border-left: 1px solid #DDD; border-left: 1px solid #DDD;
} }
}
}
}
.main-nav { .main-nav {
background: #FFF; background: #FFF;
} }
......
...@@ -100,6 +100,16 @@ class Admin::UsersController < Admin::ApplicationController ...@@ -100,6 +100,16 @@ class Admin::UsersController < Admin::ApplicationController
end end
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 protected
def user def user
......
...@@ -48,7 +48,7 @@ class ApplicationController < ActionController::Base ...@@ -48,7 +48,7 @@ class ApplicationController < ActionController::Base
flash[:alert] = "Your account is blocked. Retry when an admin has unblocked it." flash[:alert] = "Your account is blocked. Retry when an admin has unblocked it."
new_user_session_path new_user_session_path
else else
super @return_to || root_path
end end
end end
......
...@@ -33,6 +33,12 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -33,6 +33,12 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
end end
end end
def omniauth_error
@provider = params[:provider]
@error = params[:error]
render 'errors/omniauth_error', layout: "errors", status: 422
end
private private
def handle_omniauth def handle_omniauth
...@@ -47,14 +53,19 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -47,14 +53,19 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
# Create user if does not exist # Create user if does not exist
# and allow_single_sign_on is true # and allow_single_sign_on is true
if Gitlab.config.omniauth['allow_single_sign_on'] if Gitlab.config.omniauth['allow_single_sign_on'] && !@user
@user ||= Gitlab::OAuth::User.create(oauth) @user, errors = Gitlab::OAuth::User.create(oauth)
end end
if @user if @user && !errors
sign_in_and_redirect(@user) sign_in_and_redirect(@user)
else
if errors
error_message = errors.map{ |attribute, message| "#{attribute} #{message}" }.join(", ")
redirect_to omniauth_error_path(oauth['provider'], error: error_message) and return
else else
flash[:notice] = "There's no such user!" flash[:notice] = "There's no such user!"
end
redirect_to new_user_session_path redirect_to new_user_session_path
end end
end end
......
...@@ -13,6 +13,8 @@ class Projects::CommitsController < Projects::ApplicationController ...@@ -13,6 +13,8 @@ class Projects::CommitsController < Projects::ApplicationController
@limit, @offset = (params[:limit] || 40), (params[:offset] || 0) @limit, @offset = (params[:limit] || 40), (params[:offset] || 0)
@commits = @repo.commits(@ref, @path, @limit, @offset) @commits = @repo.commits(@ref, @path, @limit, @offset)
@note_counts = Note.where(commit_id: @commits.map(&:id)).
group(:commit_id).count
respond_to do |format| respond_to do |format|
format.html # index.html.erb format.html # index.html.erb
......
...@@ -20,7 +20,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -20,7 +20,7 @@ class Projects::IssuesController < Projects::ApplicationController
terms = params['issue_search'] terms = params['issue_search']
@issues = issues_filtered @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) @issues = @issues.page(params[:page]).per(20)
assignee_id, milestone_id = params[:assignee_id], params[:milestone_id] assignee_id, milestone_id = params[:assignee_id], params[:milestone_id]
......
...@@ -32,6 +32,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -32,6 +32,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end end
def show def show
@note_counts = Note.where(commit_id: @merge_request.commits.map(&:id)).
group(:commit_id).count
respond_to do |format| respond_to do |format|
format.html format.html
format.diff { render text: @merge_request.to_diff(current_user) } format.diff { render text: @merge_request.to_diff(current_user) }
...@@ -86,6 +88,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -86,6 +88,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@compare_failed = true @compare_failed = true
end end
@note_counts = Note.where(commit_id: @commits.map(&:id)).
group(:commit_id).count
@diffs = compare_action.diffs @diffs = compare_action.diffs
@merge_request.title = @merge_request.source_branch.titleize.humanize @merge_request.title = @merge_request.source_branch.titleize.humanize
@merge_request.description = @merge_request.target_project.merge_requests_template @merge_request.description = @merge_request.target_project.merge_requests_template
......
...@@ -21,7 +21,7 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -21,7 +21,7 @@ class Projects::NotesController < Projects::ApplicationController
end end
def create 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| respond_to do |format|
format.json { render_note_json(@note) } format.json { render_note_json(@note) }
...@@ -85,12 +85,24 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -85,12 +85,24 @@ class Projects::NotesController < Projects::ApplicationController
) )
end 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) def render_note_json(note)
render json: { render json: {
id: note.id, id: note.id,
discussion_id: note.discussion_id, discussion_id: note.discussion_id,
html: note_to_html(note), 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 end
......
...@@ -37,7 +37,7 @@ class Projects::RefsController < Projects::ApplicationController ...@@ -37,7 +37,7 @@ class Projects::RefsController < Projects::ApplicationController
0 0
end end
@limit = 10 @limit = 25
@path = params[:path] @path = params[:path]
......
...@@ -98,8 +98,7 @@ class ProjectsController < ApplicationController ...@@ -98,8 +98,7 @@ class ProjectsController < ApplicationController
def destroy def destroy
return access_denied! unless can?(current_user, :remove_project, project) return access_denied! unless can?(current_user, :remove_project, project)
project.team.truncate ::Projects::DestroyService.new(@project, current_user, {}).execute
project.destroy
respond_to do |format| respond_to do |format|
format.html { redirect_to root_path } format.html { redirect_to root_path }
...@@ -125,18 +124,12 @@ class ProjectsController < ApplicationController ...@@ -125,18 +124,12 @@ class ProjectsController < ApplicationController
def autocomplete_sources def autocomplete_sources
note_type = params['type'] note_type = params['type']
note_id = params['type_id'] note_id = params['type_id']
participating = if note_type && note_id participants = ::Projects::ParticipantsService.new(@project).execute(note_type, note_id)
participants_in(note_type, note_id)
else
[]
end
team_members = sorted(@project.team.members)
participants = team_members + participating
@suggestions = { @suggestions = {
emojis: Emoji.names.map { |e| { name: e, path: view_context.image_url("emoji/#{e}.png") } }, emojis: Emoji.names.map { |e| { name: e, path: view_context.image_url("emoji/#{e}.png") } },
issues: @project.issues.select([:iid, :title, :description]), issues: @project.issues.select([:iid, :title, :description]),
mergerequests: @project.merge_requests.select([:iid, :title, :description]), mergerequests: @project.merge_requests.select([:iid, :title, :description]),
members: participants.uniq members: participants
} }
respond_to do |format| respond_to do |format|
...@@ -192,25 +185,4 @@ class ProjectsController < ApplicationController ...@@ -192,25 +185,4 @@ class ProjectsController < ApplicationController
def user_layout def user_layout
current_user ? "projects" : "public_projects" current_user ? "projects" : "public_projects"
end 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 end
class UsersSessionsController < Devise::SessionsController
def create
@return_to = params[:return_to]
super
end
end
...@@ -45,15 +45,15 @@ module EventsHelper ...@@ -45,15 +45,15 @@ module EventsHelper
def event_feed_title(event) def event_feed_title(event)
if event.issue? 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? 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? elsif event.push?
"#{event.author_name} #{event.push_action_name} #{event.ref_type} #{event.ref_name} at #{event.project_name}" "#{event.author_name} #{event.push_action_name} #{event.ref_type} #{event.ref_name} at #{event.project_name}"
elsif event.membership_changed? elsif event.membership_changed?
"#{event.author_name} #{event.action_name} #{event.project_name}" "#{event.author_name} #{event.action_name} #{event.project_name}"
elsif event.note? 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 else
"" ""
end end
......
...@@ -63,11 +63,15 @@ module GitlabMarkdownHelper ...@@ -63,11 +63,15 @@ module GitlabMarkdownHelper
paths = extract_paths(text) paths = extract_paths(text)
paths.uniq.each do |file_path| paths.uniq.each do |file_path|
# If project does not have repository
# its nothing to rebuild
if @repository.exists? && !@repository.empty?
new_path = rebuild_path(file_path) new_path = rebuild_path(file_path)
# Finds quoted path so we don't replace other mentions of the string # 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 # eg. "doc/api" will be replaced and "/home/doc/api/text" won't
text.gsub!("\"#{file_path}\"", "\"/#{new_path}\"") text.gsub!("\"#{file_path}\"", "\"/#{new_path}\"")
end end
end
text text
end end
...@@ -91,8 +95,13 @@ module GitlabMarkdownHelper ...@@ -91,8 +95,13 @@ module GitlabMarkdownHelper
end end
def link_to_ignore?(link) def link_to_ignore?(link)
if link =~ /\#\w+/
# ignore anchors like <a href="#my-header">
true
else
ignored_protocols.map{ |protocol| link.include?(protocol) }.any? ignored_protocols.map{ |protocol| link.include?(protocol) }.any?
end end
end
def ignored_protocols def ignored_protocols
["http://","https://", "ftp://", "mailto:"] ["http://","https://", "ftp://", "mailto:"]
...@@ -169,7 +178,7 @@ module GitlabMarkdownHelper ...@@ -169,7 +178,7 @@ module GitlabMarkdownHelper
def current_sha def current_sha
if @commit if @commit
@commit.id @commit.id
else elsif @repository && !@repository.empty?
@repository.head_commit.sha @repository.head_commit.sha
end end
end end
......
...@@ -15,12 +15,6 @@ module NotesHelper ...@@ -15,12 +15,6 @@ module NotesHelper
end end
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) def note_timestamp(note)
# Shows the created at time and the updated at time if different # Shows the created at time and the updated at time if different
ts = "#{time_ago_with_tooltip(note.created_at, 'bottom', 'note_created_ago')}" ts = "#{time_ago_with_tooltip(note.created_at, 'bottom', 'note_created_ago')}"
...@@ -61,4 +55,23 @@ module NotesHelper ...@@ -61,4 +55,23 @@ module NotesHelper
link_to "", "javascript:;", class: "add-diff-note js-add-diff-note-button", link_to "", "javascript:;", class: "add-diff-note js-add-diff-note-button",
data: data, title: "Add a comment to this line" data: data, title: "Add a comment to this line"
end 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 end
...@@ -49,13 +49,17 @@ module Mentionable ...@@ -49,13 +49,17 @@ module Mentionable
matches = mentionable_text.scan(/@[a-zA-Z][a-zA-Z0-9_\-\.]*/) matches = mentionable_text.scan(/@[a-zA-Z][a-zA-Z0-9_\-\.]*/)
matches.each do |match| matches.each do |match|
identifier = match.delete "@" identifier = match.delete "@"
if identifier == "all"
users += project.team.members.flatten
else
if has_project if has_project
id = project.team.members.find_by(username: identifier).try(:id) id = project.team.members.find_by(username: identifier).try(:id)
else else
id = User.where(username: identifier).pluck(:id).first id = User.find_by(username: identifier).try(:id)
end end
users << User.find(id) unless id.blank? users << User.find(id) unless id.blank?
end end
end
users.uniq users.uniq
end end
......
...@@ -41,6 +41,9 @@ class Event < ActiveRecord::Base ...@@ -41,6 +41,9 @@ class Event < ActiveRecord::Base
# For Hash only # For Hash only
serialize :data serialize :data
# Callbacks
after_create :reset_project_activity
# Scopes # Scopes
scope :recent, -> { order("created_at DESC") } scope :recent, -> { order("created_at DESC") }
scope :code_push, -> { where(action: PUSHED) } scope :code_push, -> { where(action: PUSHED) }
...@@ -303,4 +306,10 @@ class Event < ActiveRecord::Base ...@@ -303,4 +306,10 @@ class Event < ActiveRecord::Base
target.respond_to? :title target.respond_to? :title
end end
end end
def reset_project_activity
if project
project.update_column(:last_activity_at, self.created_at)
end
end
end end
...@@ -57,6 +57,7 @@ class Note < ActiveRecord::Base ...@@ -57,6 +57,7 @@ class Note < ActiveRecord::Base
serialize :st_diff serialize :st_diff
before_create :set_diff, if: ->(n) { n.line_code.present? } before_create :set_diff, if: ->(n) { n.line_code.present? }
after_update :set_references
class << self class << self
def create_status_change_note(noteable, project, author, status, source) def create_status_change_note(noteable, project, author, status, source)
...@@ -178,10 +179,28 @@ class Note < ActiveRecord::Base ...@@ -178,10 +179,28 @@ class Note < ActiveRecord::Base
@diff ||= Gitlab::Git::Diff.new(st_diff) if st_diff.respond_to?(:map) @diff ||= Gitlab::Git::Diff.new(st_diff) if st_diff.respond_to?(:map)
end end
# Check if such line of code exists in merge request diff
# If exists - its active discussion
# If not - its outdated diff
def active? def active?
# TODO: determine if discussion is outdated return true unless self.diff
# according to recent MR diff or not
true 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 end
def diff_file_index def diff_file_index
...@@ -314,4 +333,8 @@ class Note < ActiveRecord::Base ...@@ -314,4 +333,8 @@ class Note < ActiveRecord::Base
order('id DESC').limit(100). order('id DESC').limit(100).
update_all(updated_at: Time.now) update_all(updated_at: Time.now)
end end
def set_references
notice_added_references(project, author)
end
end end
...@@ -128,6 +128,7 @@ class Repository ...@@ -128,6 +128,7 @@ class Repository
Rails.cache.delete(cache_key(:commit_count)) Rails.cache.delete(cache_key(:commit_count))
Rails.cache.delete(cache_key(:graph_log)) Rails.cache.delete(cache_key(:graph_log))
Rails.cache.delete(cache_key(:readme)) Rails.cache.delete(cache_key(:readme))
Rails.cache.delete(cache_key(:version))
Rails.cache.delete(cache_key(:contribution_guide)) Rails.cache.delete(cache_key(:contribution_guide))
end end
...@@ -156,12 +157,24 @@ class Repository ...@@ -156,12 +157,24 @@ class Repository
Gitlab::Git::Blob.find(self, sha, path) Gitlab::Git::Blob.find(self, sha, path)
end end
def blob_by_oid(oid)
Gitlab::Git::Blob.raw(self, oid)
end
def readme def readme
Rails.cache.fetch(cache_key(:readme)) do Rails.cache.fetch(cache_key(:readme)) do
tree(:head).readme tree(:head).readme
end end
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 def contribution_guide
Rails.cache.fetch(cache_key(:contribution_guide)) do Rails.cache.fetch(cache_key(:contribution_guide)) do
tree(:head).contribution_guide tree(:head).contribution_guide
......
...@@ -132,6 +132,10 @@ class User < ActiveRecord::Base ...@@ -132,6 +132,10 @@ class User < ActiveRecord::Base
before_validation :sanitize_attrs before_validation :sanitize_attrs
before_save :ensure_authentication_token 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 alias_attribute :private_token, :authentication_token
...@@ -369,7 +373,7 @@ class User < ActiveRecord::Base ...@@ -369,7 +373,7 @@ class User < ActiveRecord::Base
end end
def several_namespaces? def several_namespaces?
owned_groups.any? owned_groups.any? || masters_groups.any?
end end
def namespace_id def namespace_id
...@@ -491,4 +495,36 @@ class User < ActiveRecord::Base ...@@ -491,4 +495,36 @@ class User < ActiveRecord::Base
GravatarService.new.execute(email, size) GravatarService.new.execute(email, size)
end end
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 end
...@@ -33,6 +33,9 @@ class UsersGroup < ActiveRecord::Base ...@@ -33,6 +33,9 @@ class UsersGroup < ActiveRecord::Base
scope :with_group, ->(group) { where(group_id: group.id) } scope :with_group, ->(group) { where(group_id: group.id) }
scope :with_user, ->(user) { where(user_id: user.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 :group_access, inclusion: { in: UsersGroup.group_access_roles.values }, presence: true
validates :user_id, presence: true validates :user_id, presence: true
validates :group_id, presence: true validates :group_id, presence: true
...@@ -43,4 +46,18 @@ class UsersGroup < ActiveRecord::Base ...@@ -43,4 +46,18 @@ class UsersGroup < ActiveRecord::Base
def access_field def access_field
group_access group_access
end 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 end
...@@ -37,6 +37,10 @@ class UsersProject < ActiveRecord::Base ...@@ -37,6 +37,10 @@ class UsersProject < ActiveRecord::Base
scope :in_projects, ->(projects) { where(project_id: projects.map { |p| p.id }) } scope :in_projects, ->(projects) { where(project_id: projects.map { |p| p.id }) }
scope :with_user, ->(user) { where(user_id: user.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 class << self
# Add users to project teams with passed access option # Add users to project teams with passed access option
...@@ -114,4 +118,37 @@ class UsersProject < ActiveRecord::Base ...@@ -114,4 +118,37 @@ class UsersProject < ActiveRecord::Base
def owner? def owner?
project.owner == user project.owner == user
end 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 end
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
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
class ProjectActivityCacheObserver < BaseObserver
observe :event
def after_create(event)
event.project.update_column(:last_activity_at, event.created_at) if event.project
end
end
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
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
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
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
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
...@@ -28,4 +28,8 @@ class BaseService ...@@ -28,4 +28,8 @@ class BaseService
def log_info message def log_info message
Gitlab::AppLogger.info message Gitlab::AppLogger.info message
end end
def system_hook_service
SystemHooksService.new
end
end end
...@@ -7,8 +7,11 @@ module Issues ...@@ -7,8 +7,11 @@ module Issues
Note.create_assignee_change_note(issue, issue.project, current_user, issue.assignee) Note.create_assignee_change_note(issue, issue.project, current_user, issue.assignee)
end end
def execute_hooks(issue) def execute_hooks(issue, action = 'open')
issue.project.execute_hooks(issue.to_hook_data, :issue_hooks) 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 end
def create_milestone_note(issue) def create_milestone_note(issue)
......
...@@ -5,7 +5,7 @@ module Issues ...@@ -5,7 +5,7 @@ module Issues
notification_service.close_issue(issue, current_user) notification_service.close_issue(issue, current_user)
event_service.close_issue(issue, current_user) event_service.close_issue(issue, current_user)
create_note(issue, commit) create_note(issue, commit)
execute_hooks(issue) execute_hooks(issue, 'close')
end end
issue issue
......
...@@ -8,7 +8,7 @@ module Issues ...@@ -8,7 +8,7 @@ module Issues
notification_service.new_issue(issue, current_user) notification_service.new_issue(issue, current_user)
event_service.open_issue(issue, current_user) event_service.open_issue(issue, current_user)
issue.create_cross_references!(issue.project, current_user) issue.create_cross_references!(issue.project, current_user)
execute_hooks(issue) execute_hooks(issue, 'open')
end end
issue issue
......
...@@ -4,7 +4,7 @@ module Issues ...@@ -4,7 +4,7 @@ module Issues
if issue.reopen if issue.reopen
event_service.reopen_issue(issue, current_user) event_service.reopen_issue(issue, current_user)
create_note(issue) create_note(issue)
execute_hooks(issue) execute_hooks(issue, 'reopen')
end end
issue issue
......
...@@ -23,7 +23,7 @@ module Issues ...@@ -23,7 +23,7 @@ module Issues
end end
issue.notice_added_references(issue.project, current_user) issue.notice_added_references(issue.project, current_user)
execute_hooks(issue) execute_hooks(issue, 'update')
end end
issue issue
......
module Notes module Notes
class CreateService < BaseService class CreateService < BaseService
def execute def execute
note = project.notes.new(params[:note]) note = project.notes.new(params)
note.author = current_user note.author = current_user
note.system = false 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 note
end end
end end
......
...@@ -51,6 +51,9 @@ module Projects ...@@ -51,6 +51,9 @@ module Projects
@project.creator = current_user @project.creator = current_user
if @project.save 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 unless @project.group
@project.users_projects.create( @project.users_projects.create(
project_access: UsersProject::MASTER, project_access: UsersProject::MASTER,
......
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
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
...@@ -13,7 +13,15 @@ module Projects ...@@ -13,7 +13,15 @@ module Projects
project.change_head(new_branch) project.change_head(new_branch)
end 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 end
end end
...@@ -28,6 +28,7 @@ module Search ...@@ -28,6 +28,7 @@ module Search
projects: [], projects: [],
merge_requests: [], merge_requests: [],
issues: [], issues: [],
notes: [],
total_results: 0, total_results: 0,
} }
end end
......
...@@ -18,8 +18,9 @@ module Search ...@@ -18,8 +18,9 @@ module Search
result[:total_results] = blobs.total_count result[:total_results] = blobs.total_count
else else
result[:merge_requests] = project.merge_requests.search(query).order('updated_at DESC').limit(20) 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[:issues] = project.issues.where("title like :query OR description like :query ", query: "%#{query}%").order('updated_at DESC').limit(20)
result[:total_results] = %w(issues merge_requests).sum { |items| result[items.to_sym].size } 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 end
result result
...@@ -30,6 +31,7 @@ module Search ...@@ -30,6 +31,7 @@ module Search
merge_requests: [], merge_requests: [],
issues: [], issues: [],
blobs: [], blobs: [],
notes: [],
total_results: 0, total_results: 0,
} }
end end
......
...@@ -44,6 +44,8 @@ ...@@ -44,6 +44,8 @@
%li %li
%span.light Secondary email: %span.light Secondary email:
%strong= email.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 %li
%span.light Can create groups: %span.light Can create groups:
......
- if @has_authorized_projects - if @has_authorized_projects
.dashboard.row .dashboard.row
.activities.col-md-8 %section.activities.col-md-8
= render 'activities' = render 'activities'
.side.col-md-4.left.responsive-side %aside.side.col-md-4.left.responsive-side
= render 'sidebar' = render 'sidebar'
.fixed.sidebar-expand-button.hidden-lg.hidden-md .fixed.sidebar-expand-button.hidden-lg.hidden-md
......
...@@ -7,8 +7,8 @@ ...@@ -7,8 +7,8 @@
= f.check_box :remember_me = f.check_box :remember_me
%span Remember me %span Remember me
%div %div
= hidden_field_tag 'return_to', params[:return_to]
= f.submit "Sign in", class: "btn-create btn" = f.submit "Sign in", class: "btn-create btn"
.pull-right .pull-right
= link_to "Forgot your password?", new_password_path(resource_name), class: "btn" = link_to "Forgot your password?", new_password_path(resource_name), class: "btn"
%h1.http_status_code 403 %h1 403
%h3.page-title Access Denied %h3 Access Denied
%hr %hr
%p You are not allowed to access this page. %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"} %p Read more about project permissions #{link_to "here", help_page_path("permissions", "permissions"), class: "vlink"}
%h1.http_status_code 500 %h1 500
%h3.page-title Encoding Error %h3 Encoding Error
%hr %hr
%p Page can't be loaded because of an encoding error. %p Page can't be loaded because of an encoding error.
%h1.http_status_code 404 %h1 404
%h3.page-title Git Resource Not found %h3 Git Resource Not found
%hr %hr
%p %p
Application can't get access to some branch or commit in your repository. It Application can't get access to some branch or commit in your repository. It
......
%h1.http_status_code 404 %h1 404
%h3.page-title The resource you were looking for doesn't exist. %h3 The resource you were looking for doesn't exist.
%hr %hr
%p You may have mistyped the address or the page may have moved. %p You may have mistyped the address or the page may have moved.
%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.
= 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'
...@@ -6,12 +6,12 @@ ...@@ -6,12 +6,12 @@
= f.label :name, class: 'control-label' do = f.label :name, class: 'control-label' do
Group name Group name
.col-sm-10 .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 .form-group.group-description-holder
= f.label :description, "Details", class: 'control-label' = f.label :description, "Details", class: 'control-label'
.col-sm-10 .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 .form-group.group-description-holder
= f.label :avatar, "Group avatar", class: 'control-label' = f.label :avatar, "Group avatar", class: 'control-label'
...@@ -35,6 +35,4 @@ ...@@ -35,6 +35,4 @@
%li Existing projects may be moved into a group %li Existing projects may be moved into a group
.form-actions .form-actions
= f.submit 'Create group', class: "btn btn-create" = f.submit 'Create group', class: "btn btn-create", tabindex: 3
.dashboard .dashboard
.activities.col-md-8.hidden-sm.hidden-xs %section.activities.col-md-8.hidden-sm.hidden-xs
- if current_user - if current_user
= render "events/event_last_push", event: @last_push = render "events/event_last_push", event: @last_push
= link_to dashboard_path, class: 'btn btn-tiny' do = link_to dashboard_path, class: 'btn btn-tiny' do
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
- else - else
.nothing-here-block Project activity will be displayed here .nothing-here-block Project activity will be displayed here
= spinner = spinner
.side.col-md-4 %aside.side.col-md-4
.light-well.append-bottom-20 .light-well.append-bottom-20
= image_tag group_icon(@group.path), class: "avatar s90" = image_tag group_icon(@group.path), class: "avatar s90"
.clearfix.light .clearfix.light
......
%head %head
%meta{charset: "utf-8"} %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
= "#{title} | " if defined?(title) = "#{title} | " if defined?(title)
GitLab GitLab
...@@ -24,8 +33,3 @@ ...@@ -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}") = 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) - if current_controller?(:issues)
= auto_discovery_link_tag(:atom, project_issues_url(@project, :atom, private_token: current_user.private_token), title: "#{@project.name} 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"}
...@@ -13,10 +13,10 @@ ...@@ -13,10 +13,10 @@
%i.icon-reorder %i.icon-reorder
.pull-right.hidden-xs .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 .navbar-collapse.collapse
%ul.nav.navbar-nav %ul.nav.navbar-nav
%li.visible-xs %li.visible-xs
= link_to "Sign in", new_session_path(:user) = link_to "Sign in", new_session_path(:user, return_to: request.fullpath)
.search .search
= form_tag search_path, method: :get, class: 'navbar-form pull-left' do |f| = 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) = hidden_field_tag :group_id, @group.try(:id)
- if @project && @project.persisted? - if @project && @project.persisted?
= hidden_field_tag :project_id, @project.id = hidden_field_tag :project_id, @project.id
......
...@@ -4,7 +4,6 @@ ...@@ -4,7 +4,6 @@
%body{class: "#{app_theme} application"} %body{class: "#{app_theme} application"}
= render "layouts/head_panel", title: "" if current_user = render "layouts/head_panel", title: "" if current_user
= render "layouts/flash" = render "layouts/flash"
.container .container.navless-container
.content .error-page
%center.padded.prepend-top-20
= yield = yield
%ul %ul
= nav_link(controller: :dashboard, html_options: {class: 'home'}) do = nav_link(controller: :dashboard, html_options: {class: 'home'}) do
= link_to admin_root_path, title: "Stats" do = link_to admin_root_path, title: "Stats" do
%i.icon-home Overview
= nav_link(controller: :projects) do = nav_link(controller: :projects) do
= link_to "Projects", admin_projects_path = link_to "Projects", admin_projects_path
= nav_link(controller: :groups) do
= link_to "Groups", admin_groups_path
= nav_link(controller: :users) do = nav_link(controller: :users) do
= link_to "Users", admin_users_path = link_to "Users", admin_users_path
= nav_link(controller: :groups) do
= link_to "Groups", admin_groups_path
= nav_link(controller: :logs) do = nav_link(controller: :logs) do
= link_to "Logs", admin_logs_path = link_to "Logs", admin_logs_path
= nav_link(controller: :broadcast_messages) do = nav_link(controller: :broadcast_messages) do
......
%ul %ul
= nav_link(path: 'dashboard#show', html_options: {class: 'home'}) do = nav_link(path: 'dashboard#show', html_options: {class: 'home'}) do
= link_to root_path, title: "Home" do = link_to root_path, title: "Home" do
%i.icon-home Activity
= nav_link(path: 'dashboard#projects') do = nav_link(path: 'dashboard#projects') do
= link_to projects_dashboard_path do = link_to projects_dashboard_path do
Projects Projects
......
%ul %ul
= nav_link(path: 'groups#show', html_options: {class: 'home'}) do = nav_link(path: 'groups#show', html_options: {class: 'home'}) do
= link_to group_path(@group), title: "Home" do = link_to group_path(@group), title: "Home" do
%i.icon-home Activity
= nav_link(path: 'groups#issues') do = nav_link(path: 'groups#issues') do
= link_to issues_group_path(@group) do = link_to issues_group_path(@group) do
Issues Issues
......
%ul %ul
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
= link_to profile_path, title: "Profile" do = link_to profile_path, title: "Profile" do
%i.icon-home Profile
= nav_link(controller: :accounts) do = nav_link(controller: :accounts) do
= link_to "Account", profile_account_path = link_to "Account", profile_account_path
= nav_link(controller: :emails) do = nav_link(controller: :emails) do
......
%ul %ul
= nav_link(path: 'projects#show', html_options: {class: "home"}) do = nav_link(path: 'projects#show', html_options: {class: "home"}) do
= link_to project_path(@project), title: "Project" do = link_to project_path(@project), title: "Project" do
%i.icon-home Activity
- if project_nav_tab? :files - if project_nav_tab? :files
= nav_link(controller: %w(tree blob blame edit_tree new_tree)) do = nav_link(controller: %w(tree blob blame edit_tree new_tree)) do
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
= render 'form' = render 'form'
:javascript :javascript
$('#key_key').on('keyup', function(){ $('#key_key').on('focusout', function(){
var title = $('#key_title'), var title = $('#key_title'),
val = $('#key_key').val(), val = $('#key_key').val(),
key_mail = val.match(/([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+|\.[a-zA-Z0-9._-]+)/gi); key_mail = val.match(/([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+|\.[a-zA-Z0-9._-]+)/gi);
......
...@@ -68,10 +68,12 @@ ...@@ -68,10 +68,12 @@
%p.light %p.light
- if @user.avatar? - if @user.avatar?
You can change your avatar here You can change your avatar here
- if Gitlab.config.gravatar.enabled
%br %br
or remove the current avatar to revert to #{link_to "gravatar.com", "http://gravatar.com"} or remove the current avatar to revert to #{link_to "gravatar.com", "http://gravatar.com"}
- else - else
You can upload an avatar here You can upload an avatar here
- if Gitlab.config.gravatar.enabled
%br %br
or change it at #{link_to "gravatar.com", "http://gravatar.com"} or change it at #{link_to "gravatar.com", "http://gravatar.com"}
%hr %hr
......
...@@ -21,6 +21,11 @@ ...@@ -21,6 +21,11 @@
- if can?(current_user, :admin_project, @project) - if can?(current_user, :admin_project, @project)
&ndash; &ndash;
%strong= link_to 'Edit', edit_project_path %strong= link_to 'Edit', edit_project_path
- elsif !@project.empty_repo? && @repository.readme
- readme = @repository.readme
&ndash;
= link_to project_blob_path(@project, tree_join(@repository.root_ref, readme.name)) do
= readme.name
- unless empty_repo - unless empty_repo
.col-md-5 .col-md-5
......
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
= render blob_commit, project: @project = render blob_commit, project: @project
%div#tree-content-holder.tree-content-holder %div#tree-content-holder.tree-content-holder
.file-holder %article.file-holder
.file-title.clearfix .file-title.clearfix
%i.icon-file %i.icon-file
%span.file_name %span.file_name
......
.file-content.blob_file.blob-no-preview .file-content.blob_file.blob-no-preview
%center .center
= link_to project_raw_path(@project, @id) do = link_to project_raw_path(@project, @id) do
%h1.light %h1.light
%i.icon-download-alt %i.icon-download-alt
......
...@@ -9,11 +9,15 @@ ...@@ -9,11 +9,15 @@
= link_to "Browse Code »", project_tree_path(project, commit), class: "pull-right" = link_to "Browse Code »", project_tree_path(project, commit), class: "pull-right"
.notes_count .notes_count
- if @note_counts
- note_count = @note_counts.fetch(commit.id, 0)
- else
- notes = project.notes.for_commit_id(commit.id) - notes = project.notes.for_commit_id(commit.id)
- if notes.any? - note_count = notes.count
- if note_count > 0
%span.label.label-gray %span.label.label-gray
%i.icon-comment %i.icon-comment= note_count
= notes.count
- if commit.description? - if commit.description?
.commit-row-description.js-toggle-content .commit-row-description.js-toggle-content
......
...@@ -45,7 +45,7 @@ ...@@ -45,7 +45,7 @@
- file = project.repository.blob_at(@commit.id, diff.new_path) - file = project.repository.blob_at(@commit.id, diff.new_path)
- file = project.repository.blob_at(@commit.parent_id, diff.old_path) unless file - file = project.repository.blob_at(@commit.parent_id, diff.old_path) unless file
- next 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)}"} .diff-header{id: "file-path-#{hexdigest(diff.new_path || diff.old_path)}"}
- if diff.deleted_file - if diff.deleted_file
%span= diff.old_path %span= diff.old_path
...@@ -61,7 +61,7 @@ ...@@ -61,7 +61,7 @@
%span.file-mode= "#{diff.a_mode}#{diff.b_mode}" %span.file-mode= "#{diff.a_mode}#{diff.b_mode}"
.diff-btn-group .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 %i.icon-chevron-down
Diff comments Diff comments
&nbsp; &nbsp;
......
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
- else - else
.light-well .light-well
%center .center
%h4 %h4
There isn't anything to compare. There isn't anything to compare.
%p.slead %p.slead
......
...@@ -190,7 +190,7 @@ ...@@ -190,7 +190,7 @@
.nothing-here-block Only project owner can remove a project .nothing-here-block Only project owner can remove a project
.save-project-loader.hide .save-project-loader.hide
%center .center
%h2 %h2
%i.icon-spinner.icon-spin %i.icon-spinner.icon-spin
Saving project. Saving project.
......
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
.file-content.code .file-content.code
%pre.js-edit-mode-pane#editor= @blob.data %pre.js-edit-mode-pane#editor= @blob.data
.js-edit-mode-pane#preview.hide .js-edit-mode-pane#preview.hide
%center .center
%h2 %h2
%i.icon-spinner.icon-spin %i.icon-spinner.icon-spin
......
.loading-graph .loading-graph
%center .center
%h3.page-title %h3.page-title
%i.icon-spinner.icon-spin %i.icon-spinner.icon-spin
Building repository graph. Building repository graph.
......
- if @project.import_in_progress? - if @project.import_in_progress?
.save-project-loader .save-project-loader
%center .center
%h2 %h2
%i.icon-spinner.icon-spin %i.icon-spinner.icon-spin
Import in progress. Import in progress.
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
- elsif @project.import_failed? - elsif @project.import_failed?
.save-project-loader .save-project-loader
%center .center
%h2 %h2
Import failed. Retry? Import failed. Retry?
%hr %hr
......
...@@ -39,7 +39,7 @@ ...@@ -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. %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 - else
.light-well .light-well
%center .center
%h4 %h4
There isn't anything to merge. There isn't anything to merge.
%p.slead %p.slead
......
...@@ -31,7 +31,8 @@ ...@@ -31,7 +31,8 @@
= render "projects/merge_requests/show/diffs" = render "projects/merge_requests/show/diffs"
.notes.tab-content.voting_notes#notes{ class: (controller.action_name == 'show') ? "" : "hide" } .notes.tab-content.voting_notes#notes{ class: (controller.action_name == 'show') ? "" : "hide" }
= render "projects/notes/notes_with_form" = render "projects/notes/notes_with_form"
.status .mr-loading-status
= spinner
:javascript :javascript
var merge_request; var merge_request;
......
...@@ -72,7 +72,7 @@ ...@@ -72,7 +72,7 @@
Create a group Create a group
.save-project-loader.hide .save-project-loader.hide
%center .center
%h2 %h2
%i.icon-spinner.icon-spin %i.icon-spinner.icon-spin
Creating project &amp; repository. Creating project &amp; repository.
......
- note = notes.first # example note - note = notes.first # example note
-# Check if line want not changed since comment was left -# Check if line want not changed since comment was left
- if !defined?(line) || line == note.diff_line - if !defined?(line) || line == note.diff_line
%tr.notes_holder.js-toggle-content %tr.notes_holder
%td.notes_line{ colspan: 2 } %td.notes_line{ colspan: 2 }
%span.btn.disabled %span.btn.disabled
%i.icon-comment %i.icon-comment
...@@ -9,5 +9,5 @@ ...@@ -9,5 +9,5 @@
%td.notes_content %td.notes_content
%ul.notes{ rel: note.discussion_id } %ul.notes{ rel: note.discussion_id }
= render notes = render notes
.discussion-reply-holder
= render "projects/notes/discussion_reply_button", note: note = link_to_reply_diff(note)
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
- note2 = notes2.first # example note - note2 = notes2.first # example note
-# Check if line want not changed since comment was left -# Check if line want not changed since comment was left
/- if !defined?(line) || line == note.diff_line /- if !defined?(line) || line == note.diff_line
%tr.notes_holder.js-toggle-content %tr.notes_holder
- if note1 - if note1
%td.notes_line %td.notes_line
%span.btn.disabled %span.btn.disabled
...@@ -12,7 +12,8 @@ ...@@ -12,7 +12,8 @@
%ul.notes{ rel: note1.discussion_id } %ul.notes{ rel: note1.discussion_id }
= render notes1 = render notes1
= render "projects/notes/discussion_reply_button", note: note1 .discussion-reply-holder
= link_to_reply_diff(note1)
- else - else
%td= "" %td= ""
%td= "" %td= ""
...@@ -26,7 +27,8 @@ ...@@ -26,7 +27,8 @@
%ul.notes{ rel: note2.discussion_id } %ul.notes{ rel: note2.discussion_id }
= render notes2 = render notes2
= render "projects/notes/discussion_reply_button", note: note2 .discussion-reply-holder
= link_to_reply_diff(note2)
- else - else
%td= "" %td= ""
%td= "" %td= ""
......
- note = discussion_notes.first - note = discussion_notes.first
.discussion.js-toggle-container{ class: note.discussion_id } - if note.for_merge_request?
.discussion-header - if note.outdated?
.discussion-actions = render "projects/notes/discussions/outdated", discussion_notes: discussion_notes
= 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 - else
started = render "projects/notes/discussions/active", discussion_notes: discussion_notes
%strong - else
%i.icon-remove = render "projects/notes/discussions/commit", discussion_notes: discussion_notes
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
= 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
...@@ -17,8 +17,8 @@ ...@@ -17,8 +17,8 @@
= f.text_area :note, size: 255, class: 'note_text js-note-text js-gfm-input markdown-area' = f.text_area :note, size: 255, class: 'note_text js-note-text js-gfm-input markdown-area'
.light.clearfix .light.clearfix
.pull-left Comments are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'} .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 &amp; dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }. .pull-right Attach images (JPG, PNG, GIF) by dragging &amp; dropping or #{link_to "selecting them", '#', class: 'markdown-selector', tabindex: -1 }.
.note-preview-holder.hide .note-preview-holder.hide
.js-note-preview .js-note-preview
......
- 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
- 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)
- 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
= render "home_panel" = render "home_panel"
.row .row
.col-md-9 %section.col-md-9
= render "events/event_last_push", event: @last_push = render "events/event_last_push", event: @last_push
= render 'shared/event_filter' = render 'shared/event_filter'
.content_list .content_list
= spinner = spinner
.col-md-3.project-side.hidden-sm.hidden-xs %aside.col-md-3.project-side.hidden-sm.hidden-xs
.clearfix .clearfix
- if @project.archived? - if @project.archived?
.alert.alert-warning .alert.alert-warning
...@@ -44,6 +44,12 @@ ...@@ -44,6 +44,12 @@
= link_to project_blob_path(@project, tree_join(@repository.root_ref, readme.name)), class: 'btn btn-block' do = link_to project_blob_path(@project, tree_join(@repository.root_ref, readme.name)), class: 'btn btn-block' do
= readme.name = 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 .prepend-top-10
%p %p
%span.light Created on %span.light Created on
......
...@@ -5,4 +5,4 @@ ...@@ -5,4 +5,4 @@
= link_to blob_item.name, project_blob_path(@project, tree_join(@id || @commit.id, blob_item.name)) = link_to blob_item.name, project_blob_path(@project, tree_join(@id || @commit.id, blob_item.name))
%td.tree_time_ago.cgray %td.tree_time_ago.cgray
= render 'spinner' = render 'spinner'
%td.hidden-xs.tree_commit{ colspan: 2 } %td.hidden-xs.tree_commit
.readme-holder#README %article.readme-holder#README
%h4.readme-file-title %h4.readme-file-title
%i.icon-file %i.icon-file
= readme.name = readme.name
......
...@@ -5,4 +5,4 @@ ...@@ -5,4 +5,4 @@
= link_to tree_item.name, project_tree_path(@project, tree_join(@id || @commit.id, tree_item.name)) = link_to tree_item.name, project_tree_path(@project, tree_join(@id || @commit.id, tree_item.name))
%td.tree_time_ago.cgray %td.tree_time_ago.cgray
= render 'spinner' = render 'spinner'
%td.hidden-xs.tree_commit{ colspan: 2 } %td.hidden-xs.tree_commit
...@@ -15,3 +15,4 @@ ...@@ -15,3 +15,4 @@
%ul.bordered-list %ul.bordered-list
= render partial: "search/results/merge_request", collection: @search_results[:merge_requests] = render partial: "search/results/merge_request", collection: @search_results[:merge_requests]
= render partial: "search/results/issue", collection: @search_results[:issues] = render partial: "search/results/issue", collection: @search_results[:issues]
= render partial: "search/results/note", collection: @search_results[:notes]
%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
...@@ -14,6 +14,7 @@ class RepositoryImportWorker ...@@ -14,6 +14,7 @@ class RepositoryImportWorker
project.import_finish project.import_finish
project.save project.save
project.satellite.create unless project.satellite.exists? project.satellite.create unless project.satellite.exists?
project.update_repository_size
else else
project.import_fail project.import_fail
end end
......
...@@ -18,15 +18,6 @@ module Gitlab ...@@ -18,15 +18,6 @@ module Gitlab
# :all can be used as a placeholder for all plugins not explicitly named. # :all can be used as a placeholder for all plugins not explicitly named.
# config.plugins = [ :exception_notification, :ssl_requirement, :all ] # 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. # 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. # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
# config.time_zone = 'Central Time (US & Canada)' # config.time_zone = 'Central Time (US & Canada)'
......
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
...@@ -68,6 +68,7 @@ Gitlab::Application.routes.draw do ...@@ -68,6 +68,7 @@ Gitlab::Application.routes.draw do
put :team_update put :team_update
put :block put :block
put :unblock put :unblock
delete 'remove/:email_id', action: 'remove_email', as: 'remove_email'
end end
end end
...@@ -171,8 +172,11 @@ Gitlab::Application.routes.draw do ...@@ -171,8 +172,11 @@ Gitlab::Application.routes.draw do
resources :projects, constraints: { id: /[^\/]+/ }, only: [:new, :create] 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 # Project Area
# #
......
...@@ -84,7 +84,7 @@ GET /users ...@@ -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`. Also see `def search query` in `app/models/user.rb`.
......
...@@ -95,8 +95,8 @@ Remove the old Ruby 1.8 if present ...@@ -95,8 +95,8 @@ Remove the old Ruby 1.8 if present
Download Ruby and compile it: Download Ruby and compile it:
mkdir /tmp/ruby && cd /tmp/ruby 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 curl --progress ftp://ftp.ruby-lang.org/pub/ruby/2.1/ruby-2.1.2.tar.gz | tar xz
cd ruby-2.0.0-p481 cd ruby-2.1.2
./configure --disable-install-rdoc ./configure --disable-install-rdoc
make make
sudo make install sudo make install
...@@ -243,7 +243,7 @@ GitLab Shell is an ssh access and repository management software developed speci ...@@ -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. # By default, the gitlab-shell config is generated from your main gitlab config.
# #
# Note: When using GitLab with HTTPS please change the following: # 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. # - 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`. # - In case you are using self signed certificate set `self_signed_cert` to `true`.
# See #using-https for all necessary details. # See #using-https for all necessary details.
......
...@@ -2,9 +2,7 @@ ...@@ -2,9 +2,7 @@
## Operating Systems ## 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 Unix distributions
### Supported Linux distributions
- Ubuntu - Ubuntu
- Debian - Debian
...@@ -13,36 +11,29 @@ GitLab is developed for the Linux operating system. For the installations option ...@@ -13,36 +11,29 @@ GitLab is developed for the Linux operating system. For the installations option
- Scientific Linux - Scientific Linux
- Oracle 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 - Arch Linux
- Fedora - Fedora
- Gentoo - 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). 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.
### Unsupported Unix operating systems
There is nothing that prevents GitLab from running on other Unix operating systems.
This means you may get it to work on systems running FreeBSD or OS X. ### Non Unix operating systems such as Windows
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
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. 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. Please consider using a virtual machine to run GitLab.
## Ruby versions ## Ruby versions
GitLab requires Ruby (MRI) 2.0 or 2.1 GitLab requires Ruby (MRI) 2.0 or 2.1
You will have to use the standard MRI implementation of Ruby. 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. We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/)) but GitLab needs several Gems that have native extensions.
## Hardware requirements ## Hardware requirements
...@@ -59,7 +50,11 @@ We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/)) but GitLab ...@@ -59,7 +50,11 @@ We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/)) but GitLab
### Memory ### 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) - 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 - **2GB** is the **recommended** memory size and supports up to 500 users
- 4GB supports up to 2,000 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 ...@@ -80,7 +75,15 @@ If you have enough RAM memory and a recent CPU the speed of GitLab is mainly lim
## Database ## 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 ## Supported webbrowsers
......
...@@ -16,6 +16,7 @@ If a user is a GitLab administrator they receive all permissions. ...@@ -16,6 +16,7 @@ If a user is a GitLab administrator they receive all permissions.
| Pull project code | | ✓ | ✓ | ✓ | ✓ | | Pull project code | | ✓ | ✓ | ✓ | ✓ |
| Download project | | ✓ | ✓ | ✓ | ✓ | | Download project | | ✓ | ✓ | ✓ | ✓ |
| Create code snippets | | ✓ | ✓ | ✓ | ✓ | | Create code snippets | | ✓ | ✓ | ✓ | ✓ |
| Create new milestones | | | ✓ | ✓ | ✓ |
| Create new merge request | | | ✓ | ✓ | ✓ | | Create new merge request | | | ✓ | ✓ | ✓ |
| Create new branches | | | ✓ | ✓ | ✓ | | Create new branches | | | ✓ | ✓ | ✓ |
| Push to non-protected branches | | | ✓ | ✓ | ✓ | | Push to non-protected branches | | | ✓ | ✓ | ✓ |
......
...@@ -7,6 +7,10 @@ Creates a backup archive of the database and all repositories. This archive will ...@@ -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. 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 bundle exec rake gitlab:backup:create RAILS_ENV=production
``` ```
...@@ -42,6 +46,10 @@ Deleting old backups... [SKIPPING] ...@@ -42,6 +46,10 @@ Deleting old backups... [SKIPPING]
## Restore a previously created backup ## 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 bundle exec rake gitlab:backup:restore RAILS_ENV=production
``` ```
...@@ -84,6 +92,8 @@ Deleting tmp directories...[DONE] ...@@ -84,6 +92,8 @@ Deleting tmp directories...[DONE]
## Configure cron to make daily backups ## 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 cd /home/git/gitlab
sudo -u git -H editor config/gitlab.yml # Enable keep_time in the backup section to automatically delete old backups sudo -u git -H editor config/gitlab.yml # Enable keep_time in the backup section to automatically delete old backups
......
...@@ -5,11 +5,19 @@ ...@@ -5,11 +5,19 @@
Remove namespaces(dirs) from `/home/git/repositories` if they don't exist in GitLab database. 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 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. 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 bundle exec rake gitlab:cleanup:repos RAILS_ENV=production
``` ```
...@@ -15,6 +15,10 @@ How to use: ...@@ -15,6 +15,10 @@ How to use:
2. run the command below 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 bundle exec rake gitlab:import:repos RAILS_ENV=production
``` ```
......
...@@ -5,6 +5,10 @@ ...@@ -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. 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 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 ...@@ -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). 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 bundle exec rake gitlab:check RAILS_ENV=production
``` ```
......
...@@ -3,6 +3,10 @@ ...@@ -3,6 +3,10 @@
## Add user as a developer to all projects ## Add user as a developer to all projects
```bash ```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] bundle exec rake gitlab:import:user_to_projects[username@domain.tld]
``` ```
...@@ -13,12 +17,20 @@ Notes: ...@@ -13,12 +17,20 @@ Notes:
- admin users are added as masters - admin users are added as masters
```bash ```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 bundle exec rake gitlab:import:all_users_to_all_projects
``` ```
## Add user as a developer to all groups ## Add user as a developer to all groups
```bash ```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] bundle exec rake gitlab:import:user_to_groups[username@domain.tld]
``` ```
...@@ -29,5 +41,9 @@ Notes: ...@@ -29,5 +41,9 @@ Notes:
- admin users are added as owners so they can add additional users to the group - admin users are added as owners so they can add additional users to the group
```bash ```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 bundle exec rake gitlab:import:all_users_to_all_groups
``` ```
...@@ -2,26 +2,44 @@ ...@@ -2,26 +2,44 @@
## Add a web hook for **ALL** projects: ## 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" 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**: ## 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 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: ## 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" 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**: ## 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 RAILS_ENV=production bundle exec rake gitlab:web_hook:rm URL="http://example.com/hook" NAMESPACE=acme
## List **ALL** web hooks: ## 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 RAILS_ENV=production bundle exec rake gitlab:web_hook:list
## List the web hooks from projects in a given **NAMESPACE**: ## 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=/ RAILS_ENV=production bundle exec rake gitlab:web_hook:list NAMESPACE=/
> Note: `/` is the global namespace. > Note: `/` is the global namespace.
...@@ -120,6 +120,17 @@ Merge the RC1 code into GitLab.com. Once the build is green, deploy in the morni ...@@ -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. 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 ** # **21st - Preparation **
### **1. Prepare the blog post** ### **1. Prepare the blog post**
...@@ -219,17 +230,6 @@ Include a link to the blog post and keep it short. ...@@ -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." 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** # **23rd - Optional Patch Release**
# **24th - Update GitLab.com** # **24th - Update GitLab.com**
......
...@@ -13,7 +13,39 @@ sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production ...@@ -13,7 +13,39 @@ sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
sudo service gitlab stop 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 ```bash
cd /home/git/gitlab cd /home/git/gitlab
...@@ -34,7 +66,7 @@ For GitLab Enterprise Edition: ...@@ -34,7 +66,7 @@ For GitLab Enterprise Edition:
sudo -u git -H git checkout 7-0-stable-ee 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 ```bash
cd /home/git/gitlab-shell cd /home/git/gitlab-shell
...@@ -42,7 +74,7 @@ sudo -u git -H git fetch ...@@ -42,7 +74,7 @@ sudo -u git -H git fetch
sudo -u git -H git checkout v1.9.6 sudo -u git -H git checkout v1.9.6
``` ```
### 4. Install libs, migrations, etc. ### 5. Install libs, migrations, etc.
```bash ```bash
cd /home/git/gitlab cd /home/git/gitlab
...@@ -58,9 +90,13 @@ sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production ...@@ -58,9 +90,13 @@ sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
# Clean up assets and cache # Clean up assets and cache
sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
# 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 #### New configuration options for gitlab.yml
...@@ -70,12 +106,12 @@ There are new configuration options available for gitlab.yml. View them with the ...@@ -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 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 gitlab start
sudo service nginx restart sudo service nginx restart
### 7. Check application status ### 8. Check application status
Check if GitLab and its environment are configured correctly: Check if GitLab and its environment are configured correctly:
......
- [The individual upgrade guides](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update) - [The individual upgrade guides](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update)
- [Upgrader](upgrader.md) - [Upgrader](upgrader.md)
- [Ruby](ruby.md)
- [Patch versions](patch_versions.md) - [Patch versions](patch_versions.md)
- [MySQL to PostgreSQL](mysql_to_postgresql.md) - [MySQL to PostgreSQL](mysql_to_postgresql.md)
# 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
- [Workflow](workflow.md) - [Workflow](workflow.md)
- [Project Features](project_features.md) - [Project Features](project_features.md)
- [Authorization for merge requests](authorization_for_merge_requests.md) - [Authorization for merge requests](authorization_for_merge_requests.md)
- [Groups](groups.md)
# 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
```
...@@ -21,3 +21,11 @@ Feature: Admin Users ...@@ -21,3 +21,11 @@ Feature: Admin Users
And click edit on my user And click edit on my user
When I submit modified user When I submit modified user
Then I see user attributes changed 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
...@@ -31,3 +31,8 @@ Feature: Project Feature ...@@ -31,3 +31,8 @@ Feature: Project Feature
And I fill in merge request template And I fill in merge request template
And I save project And I save project
Then I should see project with merge request template saved Then I should see project with merge request template saved
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
...@@ -24,3 +24,10 @@ Feature: Project Redirects ...@@ -24,3 +24,10 @@ Feature: Project Redirects
Given I sign in as a user Given I sign in as a user
When I visit project "Enterprise" page When I visit project "Enterprise" page
Then page status code should be 404 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
...@@ -4,7 +4,7 @@ class AdminActiveTab < Spinach::FeatureSteps ...@@ -4,7 +4,7 @@ class AdminActiveTab < Spinach::FeatureSteps
include SharedActiveTab include SharedActiveTab
Then 'the active main tab should be Home' do Then 'the active main tab should be Home' do
ensure_active_main_tab('Home') ensure_active_main_tab('Overview')
end end
Then 'the active main tab should be Projects' do Then 'the active main tab should be Projects' do
......
...@@ -44,4 +44,23 @@ class AdminUsers < Spinach::FeatureSteps ...@@ -44,4 +44,23 @@ class AdminUsers < Spinach::FeatureSteps
step 'click edit on my user' do step 'click edit on my user' do
find("#edit_user_#{current_user.id}").click find("#edit_user_#{current_user.id}").click
end 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 end
...@@ -4,7 +4,7 @@ class DashboardActiveTab < Spinach::FeatureSteps ...@@ -4,7 +4,7 @@ class DashboardActiveTab < Spinach::FeatureSteps
include SharedActiveTab include SharedActiveTab
Then 'the active main tab should be Home' do Then 'the active main tab should be Home' do
ensure_active_main_tab('Home') ensure_active_main_tab('Activity')
end end
Then 'the active main tab should be Issues' do Then 'the active main tab should be Issues' do
......
...@@ -4,7 +4,7 @@ class ProfileActiveTab < Spinach::FeatureSteps ...@@ -4,7 +4,7 @@ class ProfileActiveTab < Spinach::FeatureSteps
include SharedActiveTab include SharedActiveTab
Then 'the active main tab should be Home' do Then 'the active main tab should be Home' do
ensure_active_main_tab('Home') ensure_active_main_tab('Profile')
end end
Then 'the active main tab should be Account' do Then 'the active main tab should be Account' do
......
...@@ -7,7 +7,7 @@ class ProjectActiveTab < Spinach::FeatureSteps ...@@ -7,7 +7,7 @@ class ProjectActiveTab < Spinach::FeatureSteps
# Main Tabs # Main Tabs
Then 'the active main tab should be Home' do Then 'the active main tab should be Home' do
ensure_active_main_tab('Home') ensure_active_main_tab('Activity')
end end
Then 'the active main tab should be Settings' do Then 'the active main tab should be Settings' do
......
...@@ -131,21 +131,21 @@ class ProjectMergeRequests < Spinach::FeatureSteps ...@@ -131,21 +131,21 @@ class ProjectMergeRequests < Spinach::FeatureSteps
end end
step 'I should see a discussion has started on line 185' do 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 "#{current_user.name} started a discussion"
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" page.should have_content "Line is wrong"
end end
step 'I should see a discussion has started on commit b1e6a9dbf1:L185' do 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 "#{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" page.should have_content "Line is wrong"
end end
step 'I should see a discussion has started on commit b1e6a9dbf1' do 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 "#{current_user.name} started a discussion on commit"
page.should have_content "One comment to rule them all" 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 end
step 'merge request is mergeable' do step 'merge request is mergeable' do
...@@ -267,10 +267,8 @@ class ProjectMergeRequests < Spinach::FeatureSteps ...@@ -267,10 +267,8 @@ class ProjectMergeRequests < Spinach::FeatureSteps
click_button "Add Comment" click_button "Add Comment"
end end
within ".note-text" do
page.should have_content message page.should have_content message
end end
end
def init_diff_note_first_file def init_diff_note_first_file
find('a[data-line-code="a5cc2925ca8258af241be7e5b0381edf30266302_12_12"]').click find('a[data-line-code="a5cc2925ca8258af241be7e5b0381edf30266302_12_12"]').click
......
...@@ -34,4 +34,16 @@ class ProjectFeature < Spinach::FeatureSteps ...@@ -34,4 +34,16 @@ class ProjectFeature < Spinach::FeatureSteps
step 'I should see project with merge request template saved' do step 'I should see project with merge request template saved' do
find_field('project_merge_requests_template').value.should == 'This merge request should contain the following.' find_field('project_merge_requests_template').value.should == 'This merge request should contain the following.'
end 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 end
...@@ -31,5 +31,27 @@ class Spinach::Features::ProjectRedirects < Spinach::FeatureSteps ...@@ -31,5 +31,27 @@ class Spinach::Features::ProjectRedirects < Spinach::FeatureSteps
project = Project.find_by(name: 'Community') project = Project.find_by(name: 'Community')
visit project_path(project) + 'DoesNotExist' visit project_path(project) + 'DoesNotExist'
end 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 end
...@@ -2,12 +2,8 @@ module SharedActiveTab ...@@ -2,12 +2,8 @@ module SharedActiveTab
include Spinach::DSL include Spinach::DSL
def ensure_active_main_tab(content) 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) page.find('.main-nav li.active').should have_content(content)
end end
end
def ensure_active_sub_tab(content) def ensure_active_sub_tab(content)
page.find('div.content ul.nav-tabs li.active').should have_content(content) page.find('div.content ul.nav-tabs li.active').should have_content(content)
......
...@@ -2,7 +2,7 @@ module SharedDiffNote ...@@ -2,7 +2,7 @@ module SharedDiffNote
include Spinach::DSL include Spinach::DSL
Given 'I cancel the diff comment' do Given 'I cancel the diff comment' do
within(".diff-file") do within(diff_file_selector) do
find(".js-close-discussion-note-form").click find(".js-close-discussion-note-form").click
end end
end end
...@@ -13,14 +13,14 @@ module SharedDiffNote ...@@ -13,14 +13,14 @@ module SharedDiffNote
end end
Given 'I haven\'t written any diff comment text' do Given 'I haven\'t written any diff comment text' do
within(".diff-file") do within(diff_file_selector) do
fill_in "note[note]", with: "" fill_in "note[note]", with: ""
end end
end end
Given 'I leave a diff comment like "Typo, please fix"' do Given 'I leave a diff comment like "Typo, please fix"' do
find('a[data-line-code="586fb7c4e1add2d4d24e27566ed7064680098646_29_14"]').click 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" fill_in "note[note]", with: "Typo, please fix"
find(".js-comment-button").trigger("click") find(".js-comment-button").trigger("click")
sleep 0.05 sleep 0.05
...@@ -29,7 +29,7 @@ module SharedDiffNote ...@@ -29,7 +29,7 @@ module SharedDiffNote
Given 'I preview a diff comment text like "Should fix it :smile:"' do Given 'I preview a diff comment text like "Should fix it :smile:"' do
find('a[data-line-code="586fb7c4e1add2d4d24e27566ed7064680098646_29_14"]').click 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:" fill_in "note[note]", with: "Should fix it :smile:"
find(".js-note-preview-button").trigger("click") find(".js-note-preview-button").trigger("click")
end end
...@@ -38,7 +38,7 @@ module SharedDiffNote ...@@ -38,7 +38,7 @@ module SharedDiffNote
Given 'I preview another diff comment text like "DRY this up"' do Given 'I preview another diff comment text like "DRY this up"' do
find('a[data-line-code="586fb7c4e1add2d4d24e27566ed7064680098646_57_41"]').click 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" fill_in "note[note]", with: "DRY this up"
find(".js-note-preview-button").trigger("click") find(".js-note-preview-button").trigger("click")
end end
...@@ -53,63 +53,61 @@ module SharedDiffNote ...@@ -53,63 +53,61 @@ module SharedDiffNote
end end
Given 'I write a diff comment like ":-1: I don\'t like this"' do 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" fill_in "note[note]", with: ":-1: I don\'t like this"
end end
end end
Given 'I submit the diff comment' do Given 'I submit the diff comment' do
within(".diff-file") do within(diff_file_selector) do
click_button("Add Comment") click_button("Add Comment")
end end
end end
Then 'I should not see the diff comment form' do 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") page.should_not have_css("form.new_note")
end end
end end
Then 'I should not see the diff comment preview button' do 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) page.should have_css(".js-note-preview-button", visible: false)
end end
end end
Then 'I should not see the diff comment text field' do 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) page.should have_css(".js-note-text", visible: false)
end end
end end
Then 'I should only see one diff form' do 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) page.should have_css("form.new_note", count: 1)
end end
end end
Then 'I should see a diff comment form with ":-1: I don\'t like this"' do 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") page.should have_field("note[note]", with: ":-1: I don\'t like this")
end end
end end
Then 'I should see a diff comment saying "Typo, please fix"' do 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") page.should have_content("Typo, please fix")
end end
end end
Then 'I should see a discussion reply button' do Then 'I should see a discussion reply button' do
within(".diff-file") do within(diff_file_selector) do
page.should have_link("Reply") page.should have_link("Reply")
end end
end end
Then 'I should see a temporary diff comment form' do 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") page.should have_css(".js-temp-notes-holder form.new_note")
end end
end end
...@@ -119,40 +117,44 @@ module SharedDiffNote ...@@ -119,40 +117,44 @@ module SharedDiffNote
end end
Then 'I should see an empty diff comment form' do 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: "") page.should have_field("note[note]", with: "")
end end
end end
Then 'I should see the cancel comment button' do 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") page.should have_css(".js-close-discussion-note-form", text: "Cancel")
end end
end end
Then 'I should see the diff comment preview' do 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) page.should have_css(".js-note-preview", visible: false)
end end
end end
Then 'I should see the diff comment edit button' do 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) page.should have_css(".js-note-write-button", visible: true)
end end
end end
Then 'I should see the diff comment preview button' do 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) page.should have_css(".js-note-preview-button", visible: true)
end end
end end
Then 'I should see two separate previews' do 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_css(".js-note-preview", visible: true, count: 2)
page.should have_content("Should fix it") page.should have_content("Should fix it")
page.should have_content("DRY this up") page.should have_content("DRY this up")
end end
end end
def diff_file_selector
".diff-file"
end
end end
...@@ -50,12 +50,15 @@ module API ...@@ -50,12 +50,15 @@ module API
post ":id/#{noteables_str}/:#{noteable_id_str}/notes" do post ":id/#{noteables_str}/:#{noteable_id_str}/notes" do
required_attributes! [:body] required_attributes! [:body]
@noteable = user_project.send(:"#{noteables_str}").find(params[:"#{noteable_id_str}"]) opts = {
@note = @noteable.notes.new(note: params[:body]) note: params[:body],
@note.author = current_user noteable_type: noteables_str.classify,
@note.project = user_project 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 present @note, with: Entities::Note
else else
not_found! not_found!
......
...@@ -169,10 +169,13 @@ module Gitlab ...@@ -169,10 +169,13 @@ module Gitlab
end end
def reference_user(identifier, project = @project) 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]}" 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) link_to("@#{identifier}", user_url(identifier), options)
end end
end end
......
...@@ -44,7 +44,13 @@ module Gitlab ...@@ -44,7 +44,13 @@ module Gitlab
user.username = email_username.gsub("'", "") user.username = email_username.gsub("'", "")
end end
begin
user.save! 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}" log.info "(OAuth) Creating user #{email} from login with extern_uid => #{uid}"
if Gitlab.config.omniauth['block_auto_created_users'] && !ldap? if Gitlab.config.omniauth['block_auto_created_users'] && !ldap?
......
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
namespace :cache do namespace :cache do
desc "GITLAB | Clear redis cache" desc "GITLAB | Clear redis cache"
task :clear => :environment do 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
end end
...@@ -47,13 +47,10 @@ describe "Admin::Users", feature: true do ...@@ -47,13 +47,10 @@ describe "Admin::Users", feature: true do
it "should call send mail" do it "should call send mail" do
Notify.should_receive(:new_user_email) Notify.should_receive(:new_user_email)
User.observers.enable :user_observer do
click_button "Create user" click_button "Create user"
end end
end
it "should send valid email to user with email & password" do it "should send valid email to user with email & password" do
User.observers.enable :user_observer do
click_button "Create user" click_button "Create user"
user = User.last user = User.last
email = ActionMailer::Base.deliveries.last email = ActionMailer::Base.deliveries.last
...@@ -62,7 +59,6 @@ describe "Admin::Users", feature: true do ...@@ -62,7 +59,6 @@ describe "Admin::Users", feature: true do
email.text_part.body.should have_content('password') email.text_part.body.should have_content('password')
end end
end end
end
describe "GET /admin/users/:id" do describe "GET /admin/users/:id" do
before do before do
......
require 'spec_helper' require 'spec_helper'
describe "Profile account page", feature: true do describe "Profile account page", feature: true do
before(:each) { enable_observers }
after(:each) {disable_observers}
let(:user) { create(:user) } let(:user) { create(:user) }
before do before do
......
require 'spec_helper' require 'spec_helper'
describe "Projects", feature: true do describe "Projects", feature: true do
before(:each) { enable_observers }
after(:each) {disable_observers}
before { login_as :user } before { login_as :user }
describe "DELETE /projects/:id" do describe "DELETE /projects/:id" do
......
...@@ -2,7 +2,6 @@ require 'spec_helper' ...@@ -2,7 +2,6 @@ require 'spec_helper'
describe "Search", feature: true do describe "Search", feature: true do
before do before do
ActiveRecord::Base.observers.enable(:user_observer)
login_as :user login_as :user
@project = create(:project, namespace: @user.namespace) @project = create(:project, namespace: @user.namespace)
@project.team << [@user, :reporter] @project.team << [@user, :reporter]
......
...@@ -5,6 +5,7 @@ describe GitlabMarkdownHelper do ...@@ -5,6 +5,7 @@ describe GitlabMarkdownHelper do
include IssuesHelper include IssuesHelper
let!(:project) { create(:project) } let!(:project) { create(:project) }
let(:empty_project) { create(:empty_project) }
let(:user) { create(:user, username: 'gfm') } let(:user) { create(:user, username: 'gfm') }
let(:commit) { project.repository.commit } let(:commit) { project.repository.commit }
...@@ -506,6 +507,19 @@ describe GitlabMarkdownHelper do ...@@ -506,6 +507,19 @@ describe GitlabMarkdownHelper do
end end
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 describe "#render_wiki_content" do
before do before do
@wiki = double('WikiPage') @wiki = double('WikiPage')
......
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
...@@ -65,26 +65,4 @@ describe Event do ...@@ -65,26 +65,4 @@ describe Event do
it { @event.branch_name.should == "master" } it { @event.branch_name.should == "master" }
it { @event.author.should == @user } it { @event.author.should == @user }
end 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 end
...@@ -27,9 +27,6 @@ ...@@ -27,9 +27,6 @@
require 'spec_helper' require 'spec_helper'
describe Project do describe Project do
before { enable_observers }
after { disable_observers }
describe "Associations" do describe "Associations" do
it { should belong_to(:group) } it { should belong_to(:group) }
it { should belong_to(:namespace) } it { should belong_to(:namespace) }
......
...@@ -19,21 +19,20 @@ require "spec_helper" ...@@ -19,21 +19,20 @@ require "spec_helper"
describe SystemHook do describe SystemHook do
describe "execute" do describe "execute" do
before(:each) { ActiveRecord::Base.observers.enable(:all) }
before(:each) do before(:each) do
@system_hook = create(:system_hook) @system_hook = create(:system_hook)
WebMock.stub_request(:post, @system_hook.url) WebMock.stub_request(:post, @system_hook.url)
end end
it "project_create hook" do 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 WebMock.should have_requested(:post, @system_hook.url).with(body: /project_create/).once
end end
it "project_destroy hook" do it "project_destroy hook" do
project = create(:project) user = create(:user)
project.destroy 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 WebMock.should have_requested(:post, @system_hook.url).with(body: /project_destroy/).once
end end
......
...@@ -145,7 +145,6 @@ describe User do ...@@ -145,7 +145,6 @@ describe User do
describe 'projects' do describe 'projects' do
before do before do
ActiveRecord::Base.observers.enable(:user_observer)
@user = create :user @user = create :user
@project = create :project, namespace: @user.namespace @project = create :project, namespace: @user.namespace
@project_2 = create :project, group: create(:group) # Grant MASTER access to the user @project_2 = create :project, group: create(:group) # Grant MASTER access to the user
...@@ -168,7 +167,6 @@ describe User do ...@@ -168,7 +167,6 @@ describe User do
describe 'groups' do describe 'groups' do
before do before do
ActiveRecord::Base.observers.enable(:user_observer)
@user = create :user @user = create :user
@group = create :group @group = create :group
@group.add_owner(@user) @group.add_owner(@user)
...@@ -181,7 +179,6 @@ describe User do ...@@ -181,7 +179,6 @@ describe User do
describe 'group multiple owners' do describe 'group multiple owners' do
before do before do
ActiveRecord::Base.observers.enable(:user_observer)
@user = create :user @user = create :user
@user2 = create :user @user2 = create :user
@group = create :group @group = create :group
...@@ -195,7 +192,6 @@ describe User do ...@@ -195,7 +192,6 @@ describe User do
describe 'namespaced' do describe 'namespaced' do
before do before do
ActiveRecord::Base.observers.enable(:user_observer)
@user = create :user @user = create :user
@project = create :project, namespace: @user.namespace @project = create :project, namespace: @user.namespace
end end
......
...@@ -37,4 +37,32 @@ describe UsersGroup do ...@@ -37,4 +37,32 @@ describe UsersGroup do
it { should respond_to(:user_name) } it { should respond_to(:user_name) }
it { should respond_to(:user_email) } it { should respond_to(:user_email) }
end 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 end
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
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
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
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
...@@ -3,8 +3,6 @@ require 'mime/types' ...@@ -3,8 +3,6 @@ require 'mime/types'
describe API::API, api: true do describe API::API, api: true do
include ApiHelpers include ApiHelpers
before(:each) { enable_observers }
after(:each) {disable_observers}
let(:user) { create(:user) } let(:user) { create(:user) }
let(:user2) { create(:user) } let(:user2) { create(:user) }
......
...@@ -3,9 +3,6 @@ require 'mime/types' ...@@ -3,9 +3,6 @@ require 'mime/types'
describe API::API, api: true do describe API::API, api: true do
include ApiHelpers include ApiHelpers
before(:each) { enable_observers }
after(:each) {disable_observers}
let(:user) { create(:user) } let(:user) { create(:user) }
let(:user2) { create(:user) } let(:user2) { create(:user) }
let!(:project) { create(:project, creator_id: user.id) } let!(:project) { create(:project, creator_id: user.id) }
......
...@@ -2,9 +2,6 @@ require 'spec_helper' ...@@ -2,9 +2,6 @@ require 'spec_helper'
describe API::API, api: true do describe API::API, api: true do
include ApiHelpers include ApiHelpers
before(:each) { ActiveRecord::Base.observers.enable(:user_observer) }
after(:each) { ActiveRecord::Base.observers.disable(:user_observer) }
let(:user) { create(:user) } let(:user) { create(:user) }
let!(:project) { create(:project, namespace: user.namespace ) } let!(:project) { create(:project, namespace: user.namespace ) }
before { project.team << [user, :developer] } before { project.team << [user, :developer] }
......
...@@ -2,9 +2,6 @@ require 'spec_helper' ...@@ -2,9 +2,6 @@ require 'spec_helper'
describe API::API, api: true do describe API::API, api: true do
include ApiHelpers include ApiHelpers
before(:each) { ActiveRecord::Base.observers.enable(:user_observer) }
after(:each) { ActiveRecord::Base.observers.disable(:user_observer) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:key) { create(:key, user: user) } let(:key) { create(:key, user: user) }
let(:project) { create(:project) } let(:project) { create(:project) }
......
...@@ -2,9 +2,6 @@ require 'spec_helper' ...@@ -2,9 +2,6 @@ require 'spec_helper'
describe API::API, api: true do describe API::API, api: true do
include ApiHelpers include ApiHelpers
before(:each) { ActiveRecord::Base.observers.enable(:user_observer) }
after(:each) { ActiveRecord::Base.observers.disable(:user_observer) }
let(:user) { create(:user) } let(:user) { create(:user) }
let!(:project) { create(:project, namespace: user.namespace ) } let!(:project) { create(:project, namespace: user.namespace ) }
let!(:issue) { create(:issue, author: user, assignee: user, project: project) } let!(:issue) { create(:issue, author: user, assignee: user, project: project) }
......
...@@ -2,8 +2,6 @@ require "spec_helper" ...@@ -2,8 +2,6 @@ require "spec_helper"
describe API::API, api: true do describe API::API, api: true do
include ApiHelpers include ApiHelpers
before(:each) { ActiveRecord::Base.observers.enable(:user_observer) }
after(:each) { ActiveRecord::Base.observers.disable(:user_observer) }
let(:user) { create(:user) } let(:user) { create(:user) }
let!(:project) {create(:project, creator_id: user.id, namespace: user.namespace) } let!(:project) {create(:project, creator_id: user.id, namespace: user.namespace) }
let!(:merge_request) { create(:merge_request, :simple, author: user, assignee: user, source_project: project, target_project: project, title: "Test") } let!(:merge_request) { create(:merge_request, :simple, author: user, assignee: user, source_project: project, target_project: project, title: "Test") }
......
...@@ -2,9 +2,6 @@ require 'spec_helper' ...@@ -2,9 +2,6 @@ require 'spec_helper'
describe API::API, api: true do describe API::API, api: true do
include ApiHelpers include ApiHelpers
before(:each) { enable_observers }
after(:each) {disable_observers}
let(:user) { create(:user) } let(:user) { create(:user) }
let!(:project) { create(:project, namespace: user.namespace ) } let!(:project) { create(:project, namespace: user.namespace ) }
let!(:milestone) { create(:milestone, project: project) } let!(:milestone) { create(:milestone, project: project) }
...@@ -92,9 +89,6 @@ describe API::API, api: true do ...@@ -92,9 +89,6 @@ describe API::API, api: true do
end end
describe "PUT /projects/:id/milestones/:milestone_id to test observer on close" do 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 it "should create an activity event when an milestone is closed" do
Event.should_receive(:create) Event.should_receive(:create)
......
...@@ -2,9 +2,6 @@ require 'spec_helper' ...@@ -2,9 +2,6 @@ require 'spec_helper'
describe API::API, api: true do describe API::API, api: true do
include ApiHelpers include ApiHelpers
before(:each) { ActiveRecord::Base.observers.enable(:user_observer) }
after(:each) { ActiveRecord::Base.observers.disable(:user_observer) }
let(:admin) { create(:admin) } let(:admin) { create(:admin) }
let!(:group1) { create(:group) } let!(:group1) { create(:group) }
let!(:group2) { create(:group) } let!(:group2) { create(:group) }
......
...@@ -2,9 +2,6 @@ require 'spec_helper' ...@@ -2,9 +2,6 @@ require 'spec_helper'
describe API::API, api: true do describe API::API, api: true do
include ApiHelpers include ApiHelpers
before(:each) { ActiveRecord::Base.observers.enable(:user_observer) }
after(:each) { ActiveRecord::Base.observers.disable(:user_observer) }
let(:user) { create(:user) } let(:user) { create(:user) }
let!(:project) { create(:project, namespace: user.namespace ) } let!(:project) { create(:project, namespace: user.namespace ) }
let!(:issue) { create(:issue, project: project, author: user) } let!(:issue) { create(:issue, project: project, author: user) }
...@@ -128,14 +125,10 @@ describe API::API, api: true do ...@@ -128,14 +125,10 @@ describe API::API, api: true do
end end
describe "POST /projects/:id/noteable/:noteable_id/notes to test observer on create" do 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 it "should create an activity event when an issue note is created" do
Event.should_receive(:create) Event.should_receive(:create)
post api("/projects/#{project.id}/issues/#{issue.id}/notes", user), body: 'hi!' post api("/projects/#{project.id}/issues/#{issue.id}/notes", user), body: 'hi!'
end end
end end
end end
...@@ -2,9 +2,6 @@ require 'spec_helper' ...@@ -2,9 +2,6 @@ require 'spec_helper'
describe API::API, 'ProjectHooks', api: true do describe API::API, 'ProjectHooks', api: true do
include ApiHelpers include ApiHelpers
before(:each) { enable_observers }
after(:each) { disable_observers }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:user3) { create(:user) } let(:user3) { create(:user) }
let!(:project) { create(:project, creator_id: user.id, namespace: user.namespace) } let!(:project) { create(:project, creator_id: user.id, namespace: user.namespace) }
......
...@@ -2,9 +2,6 @@ require 'spec_helper' ...@@ -2,9 +2,6 @@ require 'spec_helper'
describe API::API, api: true do describe API::API, api: true do
include ApiHelpers include ApiHelpers
before(:each) { enable_observers }
after(:each) { disable_observers }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:user2) { create(:user) } let(:user2) { create(:user) }
let(:user3) { create(:user) } let(:user3) { create(:user) }
......
...@@ -2,9 +2,6 @@ require 'spec_helper' ...@@ -2,9 +2,6 @@ require 'spec_helper'
describe API::API, api: true do describe API::API, api: true do
include ApiHelpers include ApiHelpers
before(:each) { enable_observers }
after(:each) { disable_observers }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:user2) { create(:user) } let(:user2) { create(:user) }
let(:user3) { create(:user) } let(:user3) { create(:user) }
......
...@@ -3,9 +3,6 @@ require 'mime/types' ...@@ -3,9 +3,6 @@ require 'mime/types'
describe API::API, api: true do describe API::API, api: true do
include ApiHelpers include ApiHelpers
before(:each) { enable_observers }
after(:each) {disable_observers}
let(:user) { create(:user) } let(:user) { create(:user) }
let(:user2) { create(:user) } let(:user2) { create(:user) }
let!(:project) { create(:project, creator_id: user.id) } let!(:project) { create(:project, creator_id: user.id) }
......
...@@ -2,9 +2,6 @@ require "spec_helper" ...@@ -2,9 +2,6 @@ require "spec_helper"
describe API::API, api: true do describe API::API, api: true do
include ApiHelpers include ApiHelpers
before(:each) { ActiveRecord::Base.observers.enable(:user_observer) }
after(:each) { ActiveRecord::Base.observers.disable(:user_observer) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) {create(:project, creator_id: user.id, namespace: user.namespace) } let(:project) {create(:project, creator_id: user.id, namespace: user.namespace) }
......
require 'spec_helper' require 'spec_helper'
describe Issues::BulkUpdateService do describe Issues::BulkUpdateService do
before(:each) { ActiveRecord::Base.observers.enable(:user_observer) }
after(:each) { ActiveRecord::Base.observers.disable(:user_observer) }
let(:issue) { let(:issue) {
create(:issue, project: @project) create(:issue, project: @project)
} }
......
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
...@@ -95,6 +95,49 @@ describe NotificationService do ...@@ -95,6 +95,49 @@ describe NotificationService do
end end
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 context 'commit note' do
let(:note) { create(:note_on_commit) } let(:note) { create(:note_on_commit) }
...@@ -312,6 +355,7 @@ describe NotificationService do ...@@ -312,6 +355,7 @@ describe NotificationService do
@u_disabled = create(:user, notification_level: Notification::N_DISABLED) @u_disabled = create(:user, notification_level: Notification::N_DISABLED)
@u_mentioned = create(:user, username: 'mention', notification_level: Notification::N_PARTICIPATING) @u_mentioned = create(:user, username: 'mention', notification_level: Notification::N_PARTICIPATING)
@u_committer = create(:user, username: 'committer') @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_watcher, :master]
project.team << [@u_participating, :master] project.team << [@u_participating, :master]
......
require 'spec_helper' require 'spec_helper'
describe Projects::CreateService do 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 describe :create_by_user do
before do before do
@user = create :user @user = create :user
...@@ -66,11 +63,8 @@ describe Projects::CreateService do ...@@ -66,11 +63,8 @@ describe Projects::CreateService do
@settings.stub(:merge_requests) { true } @settings.stub(:merge_requests) { true }
@settings.stub(:wiki) { true } @settings.stub(:wiki) { true }
@settings.stub(:snippets) { true } @settings.stub(:snippets) { true }
stub_const("Settings", Class.new) Gitlab.config.gitlab.stub(restricted_visibility_levels: [])
@restrictions = double("restrictions") Gitlab.config.gitlab.stub(:default_projects_features).and_return(@settings)
@restrictions.stub(:restricted_visibility_levels) { [] }
Settings.stub_chain(:gitlab).and_return(@restrictions)
Settings.stub_chain(:gitlab, :default_projects_features).and_return(@settings)
end end
context 'should be public when setting is public' do context 'should be public when setting is public' do
...@@ -109,11 +103,9 @@ describe Projects::CreateService do ...@@ -109,11 +103,9 @@ describe Projects::CreateService do
@settings.stub(:wiki) { true } @settings.stub(:wiki) { true }
@settings.stub(:snippets) { true } @settings.stub(:snippets) { true }
@settings.stub(:visibility_level) { Gitlab::VisibilityLevel::PRIVATE } @settings.stub(:visibility_level) { Gitlab::VisibilityLevel::PRIVATE }
stub_const("Settings", Class.new) @restrictions = [ Gitlab::VisibilityLevel::PUBLIC ]
@restrictions = double("restrictions") Gitlab.config.gitlab.stub(restricted_visibility_levels: @restrictions)
@restrictions.stub(:restricted_visibility_levels) { [ Gitlab::VisibilityLevel::PUBLIC ] } Gitlab.config.gitlab.stub(:default_projects_features).and_return(@settings)
Settings.stub_chain(:gitlab).and_return(@restrictions)
Settings.stub_chain(:gitlab, :default_projects_features).and_return(@settings)
end end
context 'should be private when option is public' do context 'should be private when option is public' do
...@@ -158,4 +150,3 @@ describe Projects::CreateService do ...@@ -158,4 +150,3 @@ describe Projects::CreateService do
Projects::CreateService.new(user, opts).execute Projects::CreateService.new(user, opts).execute
end end
end end
require 'spec_helper' require 'spec_helper'
describe Projects::ImageService do describe Projects::ImageService do
before(:each) { enable_observers }
after(:each) { disable_observers }
describe 'Image service' do describe 'Image service' do
before do before do
@user = create :user @user = create :user
......
require 'spec_helper' require 'spec_helper'
describe Projects::TransferService do describe Projects::TransferService do
before(:each) { enable_observers }
after(:each) {disable_observers}
let(:user) { create(:user) } let(:user) { create(:user) }
let(:group) { create(:group) } let(:group) { create(:group) }
let(:group2) { create(:group) } let(:group2) { create(:group) }
......
require 'spec_helper' require 'spec_helper'
describe Projects::UpdateService do 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 describe :update_by_user do
before do before do
@user = create :user @user = create :user
......
require 'spec_helper' require 'spec_helper'
describe 'Search::GlobalService' do describe 'Search::GlobalService' do
let(:user) { create(:user, namespace: found_namespace) } let(:user) { create(:user) }
let(:public_user) { create(:user, namespace: public_namespace) } let(:public_user) { create(:user) }
let(:internal_user) { create(:user, namespace: internal_namespace) } let(:internal_user) { create(:user) }
let(:found_namespace) { create(:namespace, name: 'searchable namespace', path:'another_thing') } let!(:found_project) { create(:empty_project, :private, name: 'searchable_project') }
let(:unfound_namespace) { create(:namespace, name: 'unfound namespace', path: 'yet_something_else') } let!(:unfound_project) { create(:empty_project, :private, name: 'unfound_project') }
let(:internal_namespace) { create(:namespace, name: 'searchable internal namespace', path: 'something_internal') } let!(:internal_project) { create(:empty_project, :internal, name: 'searchable_internal_project') }
let(:public_namespace) { create(:namespace, name: 'searchable public namespace', path: 'something_public') } 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) } before do
let!(:unfound_project) { create(:project, :private, name: 'unfound_project', creator_id: user.id, namespace: unfound_namespace) } found_project.team << [user, :master]
let!(:internal_project) { create(:project, :internal, name: 'searchable_internal_project', creator_id: internal_user.id, namespace: internal_namespace) } end
let!(:public_project) { create(:project, :public, name: 'searchable_public_project', creator_id: public_user.id, namespace: public_namespace) }
describe '#execute' do describe '#execute' do
context 'unauthenticated' do context 'unauthenticated' do
...@@ -38,7 +37,7 @@ describe 'Search::GlobalService' do ...@@ -38,7 +37,7 @@ describe 'Search::GlobalService' do
end end
it 'namespace name should be searchable' do 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 = context.execute
results[:projects].should match_array [found_project] results[:projects].should match_array [found_project]
end end
......
...@@ -43,7 +43,7 @@ RSpec.configure do |config| ...@@ -43,7 +43,7 @@ RSpec.configure do |config|
# instead of true. # instead of true.
config.before(:suite) do config.before(:suite) do
TestEnv.init(observers: false, init_repos: true, repos: false) TestEnv.init(init_repos: true, repos: false)
end end
config.before(:each) do config.before(:each) do
TestEnv.setup_stubs TestEnv.setup_stubs
......
...@@ -3,9 +3,7 @@ module LoginHelpers ...@@ -3,9 +3,7 @@ module LoginHelpers
# #
# role - User role (e.g., :admin, :user) # role - User role (e.g., :admin, :user)
def login_as(role) def login_as(role)
ActiveRecord::Base.observers.enable(:user_observer) do
@user = create(role) @user = create(role)
end
login_with(@user) login_with(@user)
end end
......
...@@ -17,14 +17,6 @@ module TestEnv ...@@ -17,14 +17,6 @@ module TestEnv
def init(opts = {}) def init(opts = {})
RSpec::Mocks::setup(self) 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 for spinach tests
disable_mailer if opts[:mailer] == false disable_mailer if opts[:mailer] == false
setup_stubs setup_stubs
...@@ -33,14 +25,6 @@ module TestEnv ...@@ -33,14 +25,6 @@ module TestEnv
setup_test_repos(opts) if opts[:repos] == true setup_test_repos(opts) if opts[:repos] == true
end end
def enable_observers
ActiveRecord::Base.observers.enable(:all)
end
def disable_observers
ActiveRecord::Base.observers.disable(:all)
end
def disable_mailer def disable_mailer
NotificationService.any_instance.stub(mailer: double.as_null_object) NotificationService.any_instance.stub(mailer: double.as_null_object)
end end
...@@ -89,10 +73,6 @@ module TestEnv ...@@ -89,10 +73,6 @@ module TestEnv
Repository.any_instance.stub( Repository.any_instance.stub(
size: 12.45 size: 12.45
) )
BaseObserver.any_instance.stub(
current_user: double("current_user", id: 1)
)
end end
def clear_repo_dir(namespace, name) def clear_repo_dir(namespace, name)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment