Commit 00241a84 authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge branch 'ce-changes' into 'master'

CE changes into EE
parents 750f63ba e64343d6
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,8 +142,15 @@ class Notes ...@@ -142,8 +142,15 @@ 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()
# append new note to all matching discussions # Add note to 'Changes' page discussions
$(".notes[rel='" + note.discussion_id + "']").append note.html $(".notes[rel='" + note.discussion_id + "']").append note.html
# Init discussion on 'Discussion' page if it is merge request page
if $('body').attr('data-page').indexOf('projects:merge_request') == 0
$('ul.main-notes-list').append(note.discussion_with_diff_html)
else
# append new note to all matching discussions
$(".notes[rel='" + note.discussion_id + "']").append note.html
# cleanup after successfully creating a diff/discussion note # cleanup after successfully creating a diff/discussion note
@removeDiscussionNoteForm(form) @removeDiscussionNoteForm(form)
......
...@@ -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 {
.separator { header {
background: #F9F9F9; &.navbar-gitlab {
border-left: 1px solid #DDD; .navbar-inner {
background: #F1F1F1;
border-bottom: 1px solid #DDD;
.nav > li > a {
color: $style_color;
}
.separator {
background: #F9F9F9;
border-left: 1px solid #DDD;
}
}
}
} }
.main-nav { .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 else
flash[:notice] = "There's no such user!" if errors
error_message = errors.map{ |attribute, message| "#{attribute} #{message}" }.join(", ")
redirect_to omniauth_error_path(oauth['provider'], error: error_message) and return
else
flash[:notice] = "There's no such user!"
end
redirect_to new_user_session_path 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,10 +63,14 @@ module GitlabMarkdownHelper ...@@ -63,10 +63,14 @@ module GitlabMarkdownHelper
paths = extract_paths(text) paths = extract_paths(text)
paths.uniq.each do |file_path| paths.uniq.each do |file_path|
new_path = rebuild_path(file_path) # If project does not have repository
# Finds quoted path so we don't replace other mentions of the string # its nothing to rebuild
# eg. "doc/api" will be replaced and "/home/doc/api/text" won't if @repository.exists? && !@repository.empty?
text.gsub!("\"#{file_path}\"", "\"/#{new_path}\"") new_path = rebuild_path(file_path)
# Finds quoted path so we don't replace other mentions of the string
# eg. "doc/api" will be replaced and "/home/doc/api/text" won't
text.gsub!("\"#{file_path}\"", "\"/#{new_path}\"")
end
end end
text text
...@@ -91,7 +95,12 @@ module GitlabMarkdownHelper ...@@ -91,7 +95,12 @@ module GitlabMarkdownHelper
end end
def link_to_ignore?(link) def link_to_ignore?(link)
ignored_protocols.map{ |protocol| link.include?(protocol) }.any? if link =~ /\#\w+/
# ignore anchors like <a href="#my-header">
true
else
ignored_protocols.map{ |protocol| link.include?(protocol) }.any?
end
end end
def ignored_protocols def ignored_protocols
...@@ -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,12 +49,16 @@ module Mentionable ...@@ -49,12 +49,16 @@ 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 has_project if identifier == "all"
id = project.team.members.find_by(username: identifier).try(:id) users += project.team.members.flatten
else else
id = User.where(username: identifier).pluck(:id).first if has_project
id = project.team.members.find_by(username: identifier).try(:id)
else
id = User.find_by(username: identifier).try(:id)
end
users << User.find(id) unless id.blank?
end end
users << User.find(id) unless id.blank?
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);
...@@ -13,4 +13,4 @@ ...@@ -13,4 +13,4 @@
if( key_mail && key_mail.length > 0 && title.val() == '' ){ if( key_mail && key_mail.length > 0 && title.val() == '' ){
$('#key_title').val( key_mail ); $('#key_title').val( key_mail );
} }
}); });
\ No newline at end of file
...@@ -68,12 +68,14 @@ ...@@ -68,12 +68,14 @@
%p.light %p.light
- if @user.avatar? - if @user.avatar?
You can change your avatar here You can change your avatar here
%br - if Gitlab.config.gravatar.enabled
or remove the current avatar to revert to #{link_to "gravatar.com", "http://gravatar.com"} %br
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
%br - if Gitlab.config.gravatar.enabled
or change it at #{link_to "gravatar.com", "http://gravatar.com"} %br
or change it at #{link_to "gravatar.com", "http://gravatar.com"}
%hr %hr
%a.choose-btn.btn.btn-small.js-choose-user-avatar-button %a.choose-btn.btn.btn-small.js-choose-user-avatar-button
%i.icon-paper-clip %i.icon-paper-clip
......
...@@ -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
- notes = project.notes.for_commit_id(commit.id) - if @note_counts
- if notes.any? - note_count = @note_counts.fetch(commit.id, 0)
- else
- notes = project.notes.for_commit_id(commit.id)
- note_count = notes.count
- if note_count > 0
%span.label.label-gray %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;
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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