Commit 147c643f authored by Valery Sizov's avatar Valery Sizov

Merge branch 'master' into ee_master

parents baa733c6 c80f3425
......@@ -8,7 +8,7 @@ before_script:
- touch log/application.log
- touch log/test.log
- bundle install --without postgres production --jobs $(nproc) "${FLAGS[@]}"
- bundle exec rake db:create RAILS_ENV=test
- bundle exec rake db:reset db:create RAILS_ENV=test
spec:feature:
script:
......@@ -24,6 +24,27 @@ spec:api:
- ruby
- mysql
spec:models:
script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:models
tags:
- ruby
- mysql
spec:lib:
script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:lib
tags:
- ruby
- mysql
spec:services:
script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:services
tags:
- ruby
- mysql
spec:benchmark:
script:
- RAILS_ENV=test bundle exec rake spec:benchmark
......@@ -39,9 +60,16 @@ spec:other:
- ruby
- mysql
spinach:project:
spinach:project:half:
script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:half
tags:
- ruby
- mysql
spinach:project:rest:
script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:rest
tags:
- ruby
- mysql
......
......@@ -735,23 +735,39 @@ Metrics/AbcSize:
Description: >-
A calculated magnitude based on number of assignments,
branches, and conditions.
Enabled: false
Enabled: true
Max: 70
Metrics/CyclomaticComplexity:
Description: >-
A complexity metric that is strongly correlated to the number
of test cases needed to validate a method.
Enabled: true
Max: 17
Metrics/PerceivedComplexity:
Description: >-
A complexity metric geared towards measuring complexity for a
human reader.
Enabled: true
Max: 17
Metrics/ParameterLists:
Description: 'Avoid parameter lists longer than three or four parameters.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#too-many-params'
Enabled: true
Max: 8
Metrics/BlockNesting:
Description: 'Avoid excessive block nesting'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#three-is-the-number-thou-shalt-count'
Enabled: false
Enabled: true
Max: 4
Metrics/ClassLength:
Description: 'Avoid classes longer than 100 lines of code.'
Enabled: false
Metrics/CyclomaticComplexity:
Description: >-
A complexity metric that is strongly correlated to the number
of test cases needed to validate a method.
Enabled: false
Metrics/LineLength:
Description: 'Limit lines to 80 characters.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#80-character-limits'
......@@ -762,17 +778,6 @@ Metrics/MethodLength:
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#short-methods'
Enabled: false
Metrics/ParameterLists:
Description: 'Avoid parameter lists longer than three or four parameters.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#too-many-params'
Enabled: false
Metrics/PerceivedComplexity:
Description: >-
A complexity metric geared towards measuring complexity for a
human reader.
Enabled: false
#################### Lint ################################
### Warnings
......
......@@ -2,11 +2,17 @@ Please view this file on the master branch, on stable branches it's out of date.
v 8.3.0 (unreleased)
- Merge when build succeeds (Zeger-Jan van de Weg)
- Expand character set of usernames created by Omniauth (Corey Hinshaw)
- Add button to automatically merge a merge request when the build succeeds (Zeger-Jan van de Weg)
- Merge when build succeeds (Zeger-Jan van de Weg)
- Provide better diagnostic message upon project creation errors (Stan Hu)
- Bump devise to 3.5.3 to fix reset token expiring after account creation (Stan Hu)
- Bump gollum-lib to 4.1.0 (Stan Hu)
- Fix broken group avatar upload under "New group" (Stan Hu)
- Update project repositorize size and commit count during import:repos task (Stan Hu)
- Fix API setting of 'public' attribute to false will make a project private (Stan Hu)
- Handle and report SSL errors in Web hook test (Stan Hu)
- Bump Redis requirement to 2.8 for Sidekiq 4 (Stan Hu)
- Fix: Assignee selector is empty when 'Unassigned' is selected (Jose Corcuera)
- Add rake tasks for git repository maintainance (Zeger-Jan van de Weg)
- Fix 500 error when update group member permission
......@@ -20,17 +26,39 @@ v 8.3.0 (unreleased)
- Add API endpoint to fetch merge request commits list
- Expose events API with comment information and author info
- Fix: Ensure "Remove Source Branch" button is not shown when branch is being deleted. #3583
- Fix 500 error when creating a merge request that removes a submodule
- Run custom Git hooks when branch is created or deleted.
- Fix bug when simultaneously accepting multiple MRs results in MRs that are of "merged" status, but not merged to the target branch
- Add languages page to graphs
- Block LDAP user when they are no longer found in the LDAP server
- Improve wording on project visibility levels (Zeger-Jan van de Weg)
- Automatically select default clone protocol based on user preferences (Eirik Lygre)
- Fix editing notes on a merge request diff
- Automatically select default clone protocol based on user preferences (Eirik Lygre)
- Make Network page as sub tab of Commits
- Add copy-to-clipboard button for Snippets
- Add indication to merge request list item that MR cannot be merged automatically
- Default target branch to patch-n when editing file in protected branch
- Add Builds tab to merge request detail page
- Allow milestones, issues and MRs to be created from dashboard and group indexes
- Use new style for wiki
- Use new style for milestone detail page
- Fix sidebar tooltips when collapsed
- Prevent possible XSS attack with award-emoji
- Upgraded Sidekiq to 4.x
- Accept COPYING,COPYING.lesser, and licence as license file (Zeger-Jan van de Weg)
- Fix emoji aliases problem
- Fix award-emojis Flash alert's width
- Fix deleting notes on a merge request diff
- Display referenced merge request statuses in the issue description (Greg Smethells)
- Implement new sidebar for issue and merge request pages
- Emoji picker improvements
v 8.2.3
- Fix application settings cache not expiring after changes (Stan Hu)
- Fix Error 500s when creating global milestones with Unicode characters (Stan Hu)
- Update documentation for "Guest" permissions
- Properly convert Emoji-only comments into Award Emojis
- Enable devise paranoid mode to prevent user enumeration attack
v 8.2.3
- Webhook payload has an added, modified and removed properties for each commit
......
......@@ -18,7 +18,7 @@ gem "mysql2", '~> 0.3.16', group: :mysql
gem "pg", '~> 0.18.2', group: :postgres
# Authentication libraries
gem 'devise', '~> 3.5.2'
gem 'devise', '~> 3.5.3'
gem 'devise-async', '~> 0.9.0'
gem 'doorkeeper', '~> 2.2.0'
gem 'omniauth', '~> 1.2.2'
......@@ -125,8 +125,9 @@ gem 'acts-as-taggable-on', '~> 3.4'
# Background jobs
gem 'sinatra', '~> 1.4.4', require: nil
gem 'sidekiq', '~> 3.5.0'
gem 'sidekiq-cron', '~> 0.3.0'
gem 'sidekiq', '~> 4.0'
gem 'sidekiq-cron', '~> 0.4.0'
gem 'redis-namespace'
# HTTP requests
gem "httparty", '~> 0.13.3'
......@@ -199,7 +200,7 @@ gem 'jquery-turbolinks', '~> 2.1.0'
gem 'addressable', '~> 2.3.8'
gem 'bootstrap-sass', '~> 3.0'
gem 'font-awesome-rails', '~> 4.2'
gem 'gitlab_emoji', '~> 0.1'
gem 'gitlab_emoji', '~> 0.2.0'
gem 'gon', '~> 6.0.1'
gem 'jquery-atwho-rails', '~> 1.3.2'
gem 'jquery-rails', '~> 3.1.3'
......
......@@ -148,6 +148,7 @@ GEM
execjs
coffee-script-source (1.10.0)
colorize (0.7.7)
concurrent-ruby (1.0.0)
connection_pool (2.2.0)
coveralls (0.8.9)
json (~> 1.8)
......@@ -169,7 +170,7 @@ GEM
activerecord (>= 3.2.0, < 5.0)
descendants_tracker (0.0.4)
thread_safe (~> 0.3, >= 0.3.1)
devise (3.5.2)
devise (3.5.3)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 3.2.6, < 5)
......@@ -688,15 +689,15 @@ GEM
rack
shoulda-matchers (2.8.0)
activesupport (>= 3.0.0)
sidekiq (3.5.3)
celluloid (~> 0.17.2)
sidekiq (4.0.1)
concurrent-ruby (~> 1.0)
connection_pool (~> 2.2, >= 2.2.0)
json (~> 1.0)
redis (~> 3.2, >= 3.2.1)
redis-namespace (~> 1.5, >= 1.5.2)
sidekiq-cron (0.3.1)
sidekiq-cron (0.4.0)
redis-namespace (>= 1.5.2)
rufus-scheduler (>= 2.0.24)
sidekiq (>= 2.17.3)
sidekiq (>= 4.0.0)
simple_oauth (0.1.9)
simplecov (0.10.0)
docile (~> 1.1.0)
......@@ -865,7 +866,7 @@ DEPENDENCIES
d3_rails (~> 3.5.5)
database_cleaner (~> 1.4.0)
default_value_for (~> 3.0.0)
devise (~> 3.5.2)
devise (~> 3.5.3)
devise-async (~> 0.9.0)
devise-two-factor (~> 2.0.0)
diffy (~> 3.0.3)
......@@ -887,7 +888,7 @@ DEPENDENCIES
github-markup (~> 1.3.1)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-license (~> 0.0.4)
gitlab_emoji (~> 0.1)
gitlab_emoji (~> 0.2.0)
gitlab_git (~> 7.2.20)
gitlab_meta (= 7.0)
gitlab_omniauth-ldap (~> 1.2.1)
......@@ -945,6 +946,7 @@ DEPENDENCIES
rblineprof
rdoc (~> 3.6)
redcarpet (~> 3.3.3)
redis-namespace
redis-rails (~> 4.0.0)
request_store (~> 1.2.0)
rerun (~> 0.10.0)
......@@ -962,8 +964,8 @@ DEPENDENCIES
settingslogic (~> 2.0.9)
sham_rack
shoulda-matchers (~> 2.8.0)
sidekiq (~> 3.5.0)
sidekiq-cron (~> 0.3.0)
sidekiq (~> 4.0)
sidekiq-cron (~> 0.4.0)
simplecov (~> 0.10.0)
sinatra (~> 1.4.4)
six (~> 0.2.0)
......
......@@ -95,7 +95,7 @@ GitLab is a Ruby on Rails application that runs on the following software:
- Ubuntu/Debian/CentOS/RHEL
- Ruby (MRI) 2.1
- Git 1.7.10+
- Redis 2.4+
- Redis 2.8+
- MySQL or PostgreSQL
For more information please see the [architecture documentation](http://doc.gitlab.com/ce/development/architecture.html).
......
class @AwardsHandler
constructor: (@post_emoji_url, @noteable_type, @noteable_id) ->
constructor: (@post_emoji_url, @noteable_type, @noteable_id, @aliases) ->
addAward: (emoji) ->
emoji = @normilizeEmojiName(emoji)
@postEmoji emoji, =>
@addAwardToEmojiBar(emoji)
addAwardToEmojiBar: (emoji, custom_path = '') ->
emoji = @normilizeEmojiName(emoji)
if @exist(emoji)
if @isActive(emoji)
@decrementCounter(emoji)
......@@ -94,3 +96,6 @@ class @AwardsHandler
$('body, html').animate({
scrollTop: $('.awards').offset().top - 80
}, 200)
normilizeEmojiName: (emoji) ->
@aliases[emoji] || emoji
......@@ -12,5 +12,5 @@ class @Flash
@flash.click -> $(@).fadeOut()
@flash.show()
pin: ->
@flash.addClass('flash-pinned flash-raised')
pinTo: (selector) ->
@flash.detach().appendTo(selector)
......@@ -5,9 +5,9 @@ class @IssuableContext
new UsersSelect()
$('select.select2').select2({width: 'resolve', dropdownAutoWidth: true})
$(".context .inline-update").on "change", "select", ->
$(".issuable-sidebar .inline-update").on "change", "select", ->
$(this).submit()
$(".context .inline-update").on "change", ".js-assignee", ->
$(".issuable-sidebar .inline-update").on "change", ".js-assignee", ->
$(this).submit()
$('.issuable-details').waitForImages ->
......@@ -18,6 +18,12 @@ class @IssuableContext
$('.issuable-affix').affix offset:
top: ->
@top = ($('.issuable-affix').offset().top - 70)
@top = ($('.issuable-affix').offset().top - 60)
bottom: ->
@bottom = $('.footer').outerHeight(true)
$(".edit-link").click (e) ->
block = $(@).parents('.block')
block.find('.selectbox').show()
block.find('.value').hide()
block.find('.js-select2').select2("open")
......@@ -77,7 +77,7 @@ class @MergeRequestTabs
scrollToElement: (container) ->
if window.location.hash
$el = $("#{container} #{window.location.hash}")
$el = $("div#{container} #{window.location.hash}")
$('body').scrollTo($el.offset().top) if $el.length
# Activate a tab based on the current action
......@@ -133,7 +133,7 @@ class @MergeRequestTabs
@_get
url: "#{source}.json"
success: (data) =>
document.getElementById('commits').innerHTML = data.html
document.querySelector("div#commits").innerHTML = data.html
$('.js-timeago').timeago()
@commitsLoaded = true
@scrollToElement("#commits")
......@@ -144,7 +144,8 @@ class @MergeRequestTabs
@_get
url: "#{source}.json" + @_location.search
success: (data) =>
document.getElementById('diffs').innerHTML = data.html
document.querySelector("div#diffs").innerHTML = data.html
$('div#diffs .js-syntax-highlight').syntaxHighlight()
@diffsLoaded = true
@scrollToElement("#diffs")
......@@ -154,7 +155,7 @@ class @MergeRequestTabs
@_get
url: "#{source}.json"
success: (data) =>
document.getElementById('builds').innerHTML = data.html
document.querySelector("div#builds").innerHTML = data.html
$('.js-timeago').timeago()
@buildsLoaded = true
@scrollToElement("#builds")
......
......@@ -114,7 +114,7 @@ class @Notes
unless note.valid
if note.award
flash = new Flash('You have already used this award emoji!', 'alert')
flash.pin()
flash.pinTo('.header-content')
return
# render note if it not present in loaded list
......@@ -148,6 +148,8 @@ class @Notes
@note_ids.push(note.id)
form = $("form[rel='" + note.discussion_id + "']")
row = form.closest("tr")
note_html = $(note.html)
note_html.syntaxHighlight()
# is this the first note of discussion?
if row.is(".js-temp-notes-holder")
......@@ -158,14 +160,16 @@ class @Notes
row.next().find(".note").remove()
# 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)
discussion_html = $(note.discussion_with_diff_html)
discussion_html.syntaxHighlight()
$('ul.main-notes-list').append(discussion_html)
else
# append new note to all matching discussions
$(".notes[rel='" + note.discussion_id + "']").append note.html
$(".notes[rel='" + note.discussion_id + "']").append note_html
# cleanup after successfully creating a diff/discussion note
@removeDiscussionNoteForm(form)
......@@ -286,7 +290,7 @@ class @Notes
$html.find('.js-task-list-container').taskList('enable')
# Find the note's `li` element by ID and replace it with the updated HTML
$note_li = $("#note_#{note.id}")
$note_li = $('.note-row-' + note.id)
$note_li.replaceWith($html)
###
......@@ -346,18 +350,26 @@ class @Notes
###
removeNote: ->
note = $(this).closest(".note")
notes = note.closest(".notes")
note_id = note.attr('id')
# check if this is the last note for this line
if notes.find(".note").length is 1
$('.note[id="' + note_id + '"]').each ->
note = $(this)
notes = note.closest(".notes")
count = notes.closest(".notes_holder").find(".discussion-notes-count")
# for discussions
notes.closest(".discussion").remove()
# check if this is the last note for this line
if notes.find(".note").length is 1
# for diff lines
notes.closest("tr").remove()
# for discussions
notes.closest(".discussion").remove()
note.remove()
# for diff lines
notes.closest("tr").remove()
else
# update notes count
count.get(0).lastChild.nodeValue = " #{notes.children().length - 1}"
note.remove()
###
Called in response to clicking the delete attachment link
......
......@@ -470,3 +470,9 @@ table {
visibility: hidden;
}
}
.content-separator {
margin-left: -$gl-padding;
margin-right: -$gl-padding;
border-top: 1px solid $border-color;
}
......@@ -15,13 +15,3 @@
@extend .alert-danger;
}
}
.flash-pinned {
position: fixed;
top: 80px;
width: 80%;
}
.flash-raised {
z-index: 1000;
}
......@@ -7,7 +7,7 @@
padding: 0;
list-style: none;
li {
> li {
padding: 10px 15px;
min-height: 20px;
border-bottom: 1px solid #eee;
......@@ -88,8 +88,14 @@ ul.bordered-list {
}
}
li.task-list-item {
list-style-type: none;
ul.task-list {
li.task-list-item {
list-style-type: none;
}
ul:not(.task-list) {
padding-left: 1.3em;
}
}
ul.content-list {
......@@ -125,3 +131,27 @@ ul.content-list {
margin: 0;
}
}
ul.controls {
padding-top: 1px;
float: right;
list-style: none;
.btn {
padding: 10px 14px;
}
> li {
float: left;
padding-right: 10px;
.author_link {
display: inline-block;
.avatar-inline {
margin-left: 0;
margin-right: 0;
}
}
}
}
......@@ -190,4 +190,4 @@
.ajax-users-dropdown {
min-width: 250px !important;
}
\ No newline at end of file
}
.awards {
@include clearfix;
line-height: 34px;
.award {
@include border-radius(5px);
border: 1px solid;
padding: 0px 10px;
float: left;
margin-right: 5px;
border-color: $border-color;
cursor: pointer;
&:hover {
background-color: #dce0e5;
}
&.active {
border-color: $border-gray-light;
background-color: $gray-light;
&:hover {
background-color: #dce0e5;
}
.counter {
font-weight: bold;
}
}
.icon {
float: left;
margin-right: 10px;
}
.counter {
float: left;
}
}
.awards-controls {
margin-left: 10px;
float: left;
.add-award {
font-size: 24px;
color: $gl-gray;
position: relative;
top: 2px;
&:hover,
&:link {
text-decoration: none;
}
}
.awards-menu {
padding: $gl-padding;
min-width: 214px;
> li {
cursor: pointer;
width: 30px;
height: 30px;
text-align: center;
@include border-radius(5px);
img {
margin-bottom: 2px;
}
&:hover {
background-color: #ccc;
}
}
}
}
.awards-menu{
li {
float: left;
margin: 3px;
}
}
}
......@@ -18,26 +18,12 @@
&.affix {
position: fixed;
top: 70px;
top: 60px;
margin-right: 35px;
}
}
}
.issuable-context-title {
margin-bottom: 5px;
.avatar {
margin-left: 0;
}
label {
color: $gl-gray;
font-weight: normal;
margin-right: 4px;
}
}
.project-issuable-filter {
.controls {
float: right;
......@@ -50,23 +36,6 @@
}
.issuable-details {
.page-title {
margin-top: -$gl-padding;
padding: 7px 0;
margin-bottom: 0;
color: #5c5d5e;
font-size: 16px;
line-height: 42px;
.author {
color: #5c5d5e;
}
.issue-id {
color: #5c5d5e;
}
}
.issue-title {
margin: 0;
font-size: 23px;
......@@ -80,6 +49,21 @@
margin-bottom: 0;
}
}
section {
border-right: 1px solid #ECEEF1;
> .tab-content {
margin-right: 1px;
}
.issue-discussion > .gray-content-block,
> .gray-content-block {
margin-top: 0;
border-top: none;
margin-right: -15px;
}
}
}
.issuable-filter-count {
......@@ -101,84 +85,72 @@
}
}
.cross-project-reference {
text-align: center;
width: 100%;
.slead {
padding: 5px;
}
.issuable-sidebar {
.block {
@include clearfix;
padding: $gl-padding 0;
border-bottom: 1px solid #F0F0F0;
span, button {
background-color: $background-color;
&:last-child {
border: none;
}
}
}
.awards {
@include clearfix;
line-height: 34px;
margin: 2px 0;
.title {
color: $gl-text-color;
margin-bottom: 8px;
.award {
@include border-radius(5px);
border: 1px solid;
padding: 0px 10px;
float: left;
margin: 0 5px;
border-color: $border-color;
cursor: pointer;
&.active {
border-color: $border-gray-light;
background-color: $gray-light;
.avatar {
margin-left: 0;
}
.counter {
font-weight: bold;
}
label {
font-weight: normal;
margin-right: 4px;
}
.icon {
float: left;
margin-right: 10px;
.edit-link {
color: $gl-gray;
}
}
.cross-project-reference {
font-weight: bold;
color: $gl-link-color;
.counter {
float: left;
button {
float: right;
}
}
.awards-controls {
margin-left: 10px;
float: left;
.selectbox {
display: none
}
.add-award {
font-size: 24px;
color: $gl-gray;
position: relative;
top: 2px;
.btn-clipboard {
color: $gl-gray;
}
&:hover,
&:link {
text-decoration: none;
}
}
.participants .avatar {
margin-top: 6px;
margin-right: 2px;
}
}
.awards-menu {
padding: $gl-padding;
min-width: 214px;
.issuable-title {
margin: -$gl-padding;
padding: 7px $gl-padding;
margin-bottom: 0px;
border-bottom: 1px solid $border-color;
color: #5c5d5e;
font-size: 16px;
line-height: 42px;
> li {
cursor: pointer;
margin: 5px;
}
}
.author {
color: #5c5d5e;
}
.awards-menu{
li {
float: left;
margin: 3px;
}
.issuable-id {
color: #5c5d5e;
}
}
......@@ -60,6 +60,26 @@ form.edit-issue {
margin: 0;
}
.merge-requests-title {
font-size: 16px;
font-weight: 600;
}
.merge-request-id {
display: inline-block;
width: 3em;
}
.merge-request-info {
padding-left: 5px;
}
.merge-request-status {
color: $gl-gray;
font-size: 15px;
font-weight: bold;
}
.merge-request,
.issue {
&.today {
......
......@@ -18,6 +18,7 @@
.accept-merge-holder {
.accept-action {
display: inline-block;
float: left;
.accept_merge_request {
&.ci-pending,
......@@ -36,14 +37,15 @@
.accept-control {
display: inline-block;
float: left;
margin: 0;
margin-left: 20px;
padding: 5px;
padding-top: 12px;
line-height: 20px;
&.right {
float: right;
padding-top: 12px;
a {
color: $gl-gray;
}
......@@ -119,10 +121,6 @@
}
}
.merge-request-details {
margin-bottom: $gl-padding;
}
.mr_source_commit,
.mr_target_commit {
.commit {
......
......@@ -109,13 +109,9 @@ ul.notes {
}
}
// Reduce left padding of first task list ul element
ul.task-list:first-child {
padding-left: 10px;
// sub-tasks should be padded normally
ul {
padding-left: 20px;
ul.task-list {
ul:not(.task-list) {
padding-left: 1.3em;
}
}
......
......@@ -32,6 +32,12 @@
.file-holder {
border-top: 0;
}
.file-actions {
.btn-clipboard {
@extend .btn;
}
}
}
.snippet-box {
......
......@@ -40,7 +40,9 @@ class PasswordsController < Devise::PasswordsController
def throttle_reset
return unless resource && resource.recently_sent_password_reset?
redirect_to new_password_path(resource_name),
alert: I18n.t('devise.passwords.recently_reset')
# Throttle reset attempts, but return a normal message to
# avoid user enumeration attack.
redirect_to new_user_session_path,
notice: I18n.t('devise.passwords.send_paranoid_instructions')
end
end
......@@ -64,10 +64,10 @@ class Projects::IssuesController < Projects::ApplicationController
end
def show
@participants = @issue.participants(current_user)
@note = @project.notes.new(noteable: @issue)
@notes = @issue.notes.nonawards.with_associations.fresh
@noteable = @issue
@merge_requests = @issue.referenced_merge_requests
respond_with(@issue)
end
......
......@@ -2,11 +2,12 @@ class Projects::MergeRequestsController < Projects::ApplicationController
before_action :module_enabled
before_action :merge_request, only: [
:edit, :update, :show, :diffs, :commits, :builds, :merge, :merge_check,
:ci_status, :toggle_subscription, :approve, :ff_merge, :rebase
:ci_status, :toggle_subscription, :approve, :ff_merge, :rebase, :cancel_merge_when_build_succeeds
]
before_action :closes_issues, only: [:edit, :update, :show, :diffs, :commits, :builds]
before_action :validates_merge_request, only: [:show, :diffs, :commits, :builds]
before_action :define_show_vars, only: [:show, :diffs, :commits, :builds]
before_action :define_widget_vars, only: [:merge, :cancel_merge_when_build_succeeds]
before_action :ensure_ref_fetched, only: [:show, :diffs, :commits, :builds]
# Allow read any merge_request
......@@ -170,16 +171,30 @@ class Projects::MergeRequestsController < Projects::ApplicationController
render partial: "projects/merge_requests/widget/show.html.haml", layout: false
end
def cancel_merge_when_build_succeeds
return access_denied! unless @merge_request.can_cancel_merge_when_build_succeeds?(current_user)
MergeRequests::MergeWhenBuildSucceedsService.new(@project, current_user).cancel(@merge_request)
end
def merge
return access_denied! unless @merge_request.can_be_merged_by?(current_user)
return render_404 unless @merge_request.approved?
if @merge_request.mergeable?
@merge_request.update(merge_error: nil)
MergeWorker.perform_async(@merge_request.id, current_user.id, params)
@status = true
unless @merge_request.mergeable?
@status = :failed
return
end
@merge_request.update(merge_error: nil)
if params[:merge_when_build_succeeds] && @merge_request.ci_commit && @merge_request.ci_commit.active?
MergeRequests::MergeWhenBuildSucceedsService.new(@project, current_user, merge_params)
.execute(@merge_request)
@status = :merge_when_build_succeeds
else
@status = false
MergeWorker.perform_async(@merge_request.id, current_user.id, params)
@status = :success
end
end
......@@ -306,8 +321,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def define_show_vars
@participants = @merge_request.participants(current_user)
# Build a note object for comment form
@note = @project.notes.new(noteable: @merge_request)
@notes = @merge_request.mr_and_commit_notes.nonawards.inc_author.fresh
......@@ -329,6 +342,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
end
def define_widget_vars
@ci_commit = @merge_request.ci_commit
end
def invalid_mr
# Render special view for MR with removed source or target branch
render 'invalid'
......@@ -351,6 +368,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController
)
end
def merge_params
params.permit(:should_remove_source_branch, :commit_message)
end
# Make sure merge requests created before 8.0
# have head file in refs/merge-requests/
def ensure_ref_fetched
......
......@@ -52,13 +52,13 @@ module CiStatusHelper
'circle'
end
icon(icon_name)
icon(icon_name + ' fw')
end
def render_ci_status(ci_commit)
link_to ci_status_path(ci_commit),
class: "c#{ci_status_color(ci_commit)}",
title: "Build status: #{ci_status_label(ci_commit)}",
title: "Build #{ci_status_label(ci_commit)}",
data: { toggle: 'tooltip', placement: 'left' } do
ci_status_icon(ci_commit)
end
......
......@@ -20,7 +20,7 @@ module GitlabMarkdownHelper
end
user = current_user if defined?(current_user)
gfm_body = Gitlab::Markdown.gfm(escaped_body, project: @project, current_user: user)
gfm_body = Gitlab::Markdown.render(escaped_body, project: @project, current_user: user, pipeline: :single_line)
fragment = Nokogiri::HTML::DocumentFragment.parse(gfm_body)
if fragment.children.size == 1 && fragment.children[0].name == 'a'
......@@ -46,23 +46,35 @@ module GitlabMarkdownHelper
end
def markdown(text, context = {})
process_markdown(text, context)
end
return "" unless text.present?
context[:project] ||= @project
# TODO (rspeicher): Remove all usages of this helper and just call `markdown`
# with a custom pipeline depending on the content being rendered
def gfm(text, options = {})
process_markdown(text, options, :gfm)
html = Gitlab::Markdown.render(text, context)
context.merge!(
current_user: (current_user if defined?(current_user)),
# RelativeLinkFilter
requested_path: @path,
project_wiki: @project_wiki,
ref: @ref
)
Gitlab::Markdown.post_process(html, context)
end
def asciidoc(text)
Gitlab::Asciidoc.render(text, {
commit: @commit,
project: @project,
project_wiki: @project_wiki,
Gitlab::Asciidoc.render(text,
project: @project,
current_user: (current_user if defined?(current_user)),
# RelativeLinkFilter
project_wiki: @project_wiki,
requested_path: @path,
ref: @ref
})
ref: @ref,
commit: @commit
)
end
# Return the first line of +text+, up to +max_chars+, after parsing the line
......@@ -178,26 +190,4 @@ module GitlabMarkdownHelper
''
end
end
def process_markdown(text, options, method = :markdown)
return "" unless text.present?
options.reverse_merge!(
path: @path,
pipeline: :default,
project: @project,
project_wiki: @project_wiki,
ref: @ref
)
user = current_user if defined?(current_user)
html = if method == :gfm
Gitlab::Markdown.gfm(text, options)
else
Gitlab::Markdown.render(text, options)
end
Gitlab::Markdown.post_process(html, pipeline: options[:pipeline], project: @project, user: user)
end
end
......@@ -175,11 +175,19 @@ module ProjectsHelper
end
def default_url_to_repo(project = @project)
current_user ? project.url_to_repo : project.http_url_to_repo
if default_clone_protocol == "ssh"
project.ssh_url_to_repo
else
project.http_url_to_repo
end
end
def default_clone_protocol
current_user ? "ssh" : "http"
if !current_user || current_user.require_ssh_key?
"http"
else
"ssh"
end
end
# Given the current GitLab configuration, check whether the GitLab URL for Kerberos is going to be different than the HTTP URL
......
......@@ -59,85 +59,17 @@ module Emails
subject: subject("Project was moved"))
end
def repository_push_email(project_id, recipient, author_id: nil,
ref: nil,
action: nil,
compare: nil,
reverse_compare: false,
send_from_committer_email: false,
disable_diffs: false)
unless author_id && ref && action
raise ArgumentError, "missing keywords: author_id, ref, action"
end
def repository_push_email(project_id, recipient, opts = {})
@message =
Gitlab::Email::Message::RepositoryPush.new(self, project_id, recipient, opts)
@project = Project.find(project_id)
@current_user = @author = User.find(author_id)
@reverse_compare = reverse_compare
@compare = compare
@ref_name = Gitlab::Git.ref_name(ref)
@ref_type = Gitlab::Git.tag_ref?(ref) ? "tag" : "branch"
@action = action
@disable_diffs = disable_diffs
if @compare
@commits = Commit.decorate(compare.commits, @project)
@diffs = compare.diffs
end
@action_name =
case action
when :create
"pushed new"
when :delete
"deleted"
else
"pushed to"
end
@subject = "[Git]"
@subject << "[#{@project.path_with_namespace}]"
@subject << "[#{@ref_name}]" if action == :push
@subject << " "
if action == :push
if @commits.length > 1
@target_url = namespace_project_compare_url(@project.namespace,
@project,
from: Commit.new(@compare.base, @project),
to: Commit.new(@compare.head, @project))
@subject << "Deleted " if @reverse_compare
@subject << "#{@commits.length} commits: #{@commits.first.title}"
else
@target_url = namespace_project_commit_url(@project.namespace,
@project, @commits.first)
@subject << "Deleted 1 commit: " if @reverse_compare
@subject << @commits.first.title
end
else
unless action == :delete
@target_url = namespace_project_tree_url(@project.namespace,
@project, @ref_name)
end
subject_action = @action_name.dup
subject_action[0] = subject_action[0].capitalize
@subject << "#{subject_action} #{@ref_type} #{@ref_name}"
end
@disable_footer = true
reply_to =
if send_from_committer_email && can_send_from_user_email?(@author)
@author.email
else
Gitlab.config.gitlab.email_reply_to
end
mail(from: sender(author_id, send_from_committer_email),
reply_to: reply_to,
to: recipient,
subject: @subject)
# used in notify layout
@target_url = @message.target_url
mail(from: sender(@message.author_id, @message.send_from_committer_email?),
reply_to: @message.reply_to,
to: @message.recipient,
subject: @message.subject)
end
end
end
......@@ -34,13 +34,13 @@ class Notify < BaseMailer
allowed_domains
end
private
def can_send_from_user_email?(sender)
sender_domain = sender.email.split("@").last
self.class.allowed_email_domains.include?(sender_domain)
end
private
# Return an email address that displays the name of the sender.
# Only the displayed name changes; the actual email address is always the same.
def sender(sender_id, send_from_user_email = false)
......
......@@ -366,12 +366,10 @@ class Ability
unless group.last_owner?(target_user)
can_manage = group_abilities(user, group).include?(:admin_group_member)
if can_manage && user != target_user
if can_manage
rules << :update_group_member
rules << :destroy_group_member
end
if user == target_user
elsif user == target_user
rules << :destroy_group_member
end
end
......@@ -387,12 +385,10 @@ class Ability
unless target_user == project.owner
can_manage = project_abilities(user, project).include?(:admin_project_member)
if can_manage && user != target_user
if can_manage
rules << :update_project_member
rules << :destroy_project_member
end
if user == target_user
elsif user == target_user
rules << :destroy_project_member
end
end
......
......@@ -165,6 +165,14 @@ module Ci
status == 'canceled'
end
def active?
running? || pending?
end
def complete?
canceled? || success? || failed?
end
def duration
duration_array = latest_statuses.map(&:duration).compact
duration_array.reduce(:+).to_i
......
......@@ -7,7 +7,7 @@ class Commit
include Referable
include StaticModel
attr_mentionable :safe_message
attr_mentionable :safe_message, pipeline: :single_line
participant :author, :committer, :notes
attr_accessor :project
......@@ -175,11 +175,11 @@ class Commit
end
def author
@author ||= User.find_by_any_email(author_email)
@author ||= User.find_by_any_email(author_email.downcase)
end
def committer
@committer ||= User.find_by_any_email(committer_email)
@committer ||= User.find_by_any_email(committer_email.downcase)
end
def parents
......
# == Schema Information
#
# Table name: ci_builds
#
# id :integer not null, primary key
# project_id :integer
# status :string(255)
# finished_at :datetime
# trace :text
# created_at :datetime
# updated_at :datetime
# started_at :datetime
# runner_id :integer
# coverage :float
# commit_id :integer
# commands :text
# job_id :integer
# name :string(255)
# deploy :boolean default(FALSE)
# options :text
# allow_failure :boolean default(FALSE), not null
# stage :string(255)
# trigger_request_id :integer
# stage_idx :integer
# tag :boolean
# ref :string(255)
# user_id :integer
# type :string(255)
# target_url :string(255)
# description :string(255)
# artifacts_file :text
# project_id integer
# status string
# finished_at datetime
# trace text
# created_at datetime
# updated_at datetime
# started_at datetime
# runner_id integer
# coverage float
# commit_id integer
# commands text
# job_id integer
# name string
# deploy boolean default: false
# options text
# allow_failure boolean default: false, null: false
# stage string
# trigger_request_id integer
# stage_idx integer
# tag boolean
# ref string
# user_id integer
# type string
# target_url string
# description string
#
class CommitStatus < ActiveRecord::Base
......@@ -79,6 +75,10 @@ class CommitStatus < ActiveRecord::Base
build.update_attributes finished_at: Time.now
end
after_transition [:pending, :running] => :success do |build, transition|
MergeRequests::MergeWhenBuildSucceedsService.new(build.commit.gl_project, nil).trigger(build)
end
state :pending, value: 'pending'
state :running, value: 'running'
state :failed, value: 'failed'
......
......@@ -50,7 +50,8 @@ module Issuable
allow_nil: true,
prefix: true
attr_mentionable :title, :description
attr_mentionable :title, pipeline: :single_line
attr_mentionable :description, cache: true
participant :author, :assignee, :notes_with_associations
strip_attributes :title
end
......
......@@ -10,8 +10,9 @@ module Mentionable
module ClassMethods
# Indicate which attributes of the Mentionable to search for GFM references.
def attr_mentionable(*attrs)
mentionable_attrs.concat(attrs.map(&:to_s))
def attr_mentionable(attr, options = {})
attr = attr.to_s
mentionable_attrs << [attr, options]
end
# Accessor for attributes marked mentionable.
......@@ -37,19 +38,24 @@ module Mentionable
"#{friendly_name} #{to_reference(from_project)}"
end
# Construct a String that contains possible GFM references.
def mentionable_text
self.class.mentionable_attrs.map { |attr| send(attr) }.compact.join("\n\n")
end
# The GFM reference to this Mentionable, which shouldn't be included in its #references.
def local_reference
self
end
def all_references(current_user = self.author, text = self.mentionable_text, load_lazy_references: true)
def all_references(current_user = self.author, text = nil, load_lazy_references: true)
ext = Gitlab::ReferenceExtractor.new(self.project, current_user, load_lazy_references: load_lazy_references)
ext.analyze(text)
if text
ext.analyze(text)
else
self.class.mentionable_attrs.each do |attr, options|
text = send(attr)
options[:cache_key] = [self, attr] if options.delete(:cache)
ext.analyze(text, options)
end
end
ext
end
......@@ -58,9 +64,7 @@ module Mentionable
end
# Extract GFM references to other Mentionables from this Mentionable. Always excludes its #local_reference.
def referenced_mentionables(current_user = self.author, text = self.mentionable_text, load_lazy_references: true)
return [] if text.blank?
def referenced_mentionables(current_user = self.author, text = nil, load_lazy_references: true)
refs = all_references(current_user, text, load_lazy_references: load_lazy_references)
refs = (refs.issues + refs.merge_requests + refs.commits)
......@@ -70,8 +74,8 @@ module Mentionable
refs.reject { |ref| ref == local_reference }
end
# Create a cross-reference Note for each GFM reference to another Mentionable found in +mentionable_text+.
def create_cross_references!(author = self.author, without = [], text = self.mentionable_text)
# Create a cross-reference Note for each GFM reference to another Mentionable found in the +mentionable_attrs+.
def create_cross_references!(author = self.author, without = [], text = nil)
refs = referenced_mentionables(author, text)
# We're using this method instead of Array diffing because that requires
......@@ -111,7 +115,7 @@ module Mentionable
def detect_mentionable_changes
source = (changes.present? ? changes : previous_changes).dup
mentionable = self.class.mentionable_attrs
mentionable = self.class.mentionable_attrs.map { |attr, options| attr }
# Only include changed fields that are mentionable
source.select { |key, val| mentionable.include?(key) }
......
......@@ -83,6 +83,14 @@ class Issue < ActiveRecord::Base
reference
end
def referenced_merge_requests
references = [self, *notes].flat_map do |note|
note.all_references(load_lazy_references: false).merge_requests
end.uniq
Gitlab::Markdown::ReferenceFilter::LazyReference.load(references).uniq.sort_by(&:iid)
end
# Reset issue events cache
#
# Since we do cache @event we need to reset cache in special cases:
......
......@@ -2,25 +2,28 @@
#
# Table name: merge_requests
#
# id :integer not null, primary key
# target_branch :string(255) not null
# source_branch :string(255) not null
# source_project_id :integer not null
# author_id :integer
# assignee_id :integer
# title :string(255)
# created_at :datetime
# updated_at :datetime
# milestone_id :integer
# state :string(255)
# merge_status :string(255)
# target_project_id :integer not null
# iid :integer
# description :text
# position :integer default(0)
# locked_at :datetime
# updated_by_id :integer
# merge_error :string(255)
# id :integer not null, primary key
# target_branch :string(255) not null
# source_branch :string(255) not null
# source_project_id :integer not null
# author_id :integer
# assignee_id :integer
# title :string(255)
# created_at :datetime
# updated_at :datetime
# milestone_id :integer
# state :string(255)
# merge_status :string(255)
# target_project_id :integer not null
# iid :integer
# description :text
# position :integer default(0)
# locked_at :datetime
# updated_by_id :integer
# merge_error :string(255)
# merge_params :text (serialized to hash)
# merge_when_build_succeeds :boolean default(false), not null
# merge_user_id :integer
#
require Rails.root.join("app/models/commit")
......@@ -35,11 +38,14 @@ class MergeRequest < ActiveRecord::Base
belongs_to :target_project, foreign_key: :target_project_id, class_name: "Project"
belongs_to :source_project, foreign_key: :source_project_id, class_name: "Project"
belongs_to :merge_user, class_name: "User"
has_one :merge_request_diff, dependent: :destroy
has_many :approvals, dependent: :destroy
has_many :approvers, as: :target, dependent: :destroy
serialize :merge_params, Hash
after_create :create_merge_request_diff
after_update :update_merge_request_diff
......@@ -123,6 +129,7 @@ class MergeRequest < ActiveRecord::Base
validates :source_branch, presence: true
validates :target_project, presence: true
validates :target_branch, presence: true
validates :merge_user, presence: true, if: :merge_when_build_succeeds?
validate :validate_branches
validate :validate_fork
......@@ -261,6 +268,16 @@ class MergeRequest < ActiveRecord::Base
end
end
def can_cancel_merge_when_build_succeeds?(current_user)
can_be_merged_by?(current_user) || self.author == current_user
end
def can_remove_source_branch?(current_user)
!source_project.protected_branch?(source_branch) &&
!source_project.root_ref?(source_branch) &&
Ability.abilities.allowed?(current_user, :push_code, source_project)
end
def mr_and_commit_notes
# Fetch comments only from last 100 commits
commits_for_notes_limit = 100
......@@ -396,6 +413,16 @@ class MergeRequest < ActiveRecord::Base
message
end
def reset_merge_when_build_succeeds
return unless merge_when_build_succeeds?
self.merge_when_build_succeeds = false
self.merge_user = nil
self.merge_params = nil
self.save
end
# Return array of possible target branches
# depends on target project of MR
def target_branches
......@@ -551,8 +578,10 @@ class MergeRequest < ActiveRecord::Base
end
def ci_commit
if last_commit and source_project
source_project.ci_commit(last_commit.id)
end
@ci_commit ||= source_project.ci_commit(last_commit.id) if last_commit && source_project
end
def broken?
self.commits.blank? || branch_missing? || cannot_be_merged?
end
end
......@@ -29,7 +29,7 @@ class Note < ActiveRecord::Base
default_value_for :system, false
attr_mentionable :note
attr_mentionable :note, cache: true, pipeline: :note
participant :author
belongs_to :project
......@@ -350,7 +350,7 @@ class Note < ActiveRecord::Base
end
def editable?
!system?
!system? && !is_award
end
# Checks if note is an award added as a comment
......@@ -377,6 +377,7 @@ class Note < ActiveRecord::Base
end
def award_emoji_name
note.match(Gitlab::Markdown::EmojiFilter.emoji_pattern)[1]
original_name = note.match(Gitlab::Markdown::EmojiFilter.emoji_pattern)[1]
AwardEmoji.normilize_emoji_name(original_name)
end
end
......@@ -762,6 +762,7 @@ class Project < ActiveRecord::Base
gitlab_shell.mv_repository("#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki")
send_move_instructions(old_path_with_namespace)
reset_events_cache
@repository = nil
rescue
# Returning false does not rollback after_* transaction but gives
# us information about failing some of tasks
......
......@@ -287,9 +287,25 @@ class Repository
def license
cache.fetch(:license) do
tree(:head).blobs.find do |file|
file.name =~ /\Alicense/i
licenses = tree(:head).blobs.find_all do |file|
file.name =~ /\A(copying|license|licence)/i
end
preferences = [
/\Alicen[sc]e\z/i, # LICENSE, LICENCE
/\Alicen[sc]e\./i, # LICENSE.md, LICENSE.txt
/\Acopying\z/i, # COPYING
/\Acopying\.(?!lesser)/i, # COPYING.txt
/Acopying.lesser/i # COPYING.LESSER
]
license = nil
preferences.each do |r|
license = licenses.find { |l| l.name =~ r }
break if license
end
license
end
end
......
......@@ -6,15 +6,12 @@ module MergeRequests
# Executed when you do merge via GitLab UI
#
class MergeService < MergeRequests::BaseService
attr_reader :merge_request, :commit_message
attr_reader :merge_request
def execute(merge_request, commit_message)
@commit_message = commit_message
def execute(merge_request)
@merge_request = merge_request
unless @merge_request.mergeable?
return error('Merge request is not mergeable')
end
return error('Merge request is not mergeable') unless @merge_request.mergeable?
merge_request.in_locked_state do
if commit
......@@ -32,7 +29,7 @@ module MergeRequests
committer = repository.user_to_committer(current_user)
options = {
message: commit_message,
message: params[:commit_message] || merge_request.merge_commit_message,
author: committer,
committer: committer
}
......@@ -46,6 +43,11 @@ module MergeRequests
def after_merge
MergeRequests::PostMergeService.new(project, current_user).execute(merge_request)
if params[:should_remove_source_branch]
DeleteBranchService.new(@merge_request.source_project, current_user).
execute(merge_request.source_branch)
end
end
end
end
module MergeRequests
class MergeWhenBuildSucceedsService < MergeRequests::BaseService
# Marks the passed `merge_request` to be merged when the build succeeds or
# updates the params for the automatic merge
def execute(merge_request)
merge_request.merge_params.merge!(params)
# The service is also called when the merge params are updated.
already_approved = merge_request.merge_when_build_succeeds?
unless already_approved
merge_request.merge_when_build_succeeds = true
merge_request.merge_user = @current_user
SystemNoteService.merge_when_build_succeeds(merge_request, @project, @current_user, merge_request.last_commit)
end
merge_request.save
end
# Triggers the automatic merge of merge_request once the build succeeds
def trigger(build)
merge_requests = merge_request_from(build)
merge_requests.each do |merge_request|
next unless merge_request.merge_when_build_succeeds?
if merge_request.ci_commit && merge_request.ci_commit.success? && merge_request.mergeable?
MergeWorker.perform_async(merge_request.id, merge_request.merge_user_id, merge_request.merge_params)
end
end
end
# Cancels the automatic merge
def cancel(merge_request)
if merge_request.merge_when_build_succeeds? && merge_request.open?
merge_request.reset_merge_when_build_succeeds
SystemNoteService.cancel_merge_when_build_succeeds(merge_request, @project, @current_user)
success
else
error("Can't cancel the automatic merge", 406)
end
end
private
def merge_request_from(build)
merge_requests = @project.origin_merge_requests.opened.where(source_branch: build.ref).to_a
merge_requests += @project.fork_merge_requests.opened.where(source_branch: build.ref).to_a
merge_requests.uniq.select(&:source_project)
end
end
end
......@@ -11,6 +11,7 @@ module MergeRequests
# empty diff during a manual merge
close_merge_requests
reload_merge_requests
reset_merge_when_build_succeeds
# Leave a system note if a branch was deleted/added
if branch_added? || branch_removed?
......@@ -58,7 +59,6 @@ module MergeRequests
merge_requests = filter_merge_requests(merge_requests)
merge_requests.each do |merge_request|
if merge_request.source_branch == @branch_name || force_push?
merge_request.reload_code
merge_request.mark_as_unchecked
......@@ -91,6 +91,10 @@ module MergeRequests
end
end
def reset_merge_when_build_succeeds
merge_requests_for_source_branch.each(&:reset_merge_when_build_succeeds)
end
def find_new_commits
if branch_added?
@commits = []
......
......@@ -64,8 +64,10 @@ module Projects
after_create_actions if @project.persisted?
@project
rescue
@project.errors.add(:base, "Can't save project. Please try again later")
rescue => e
message = "Unable to save project: #{e.message}"
Rails.logger.error(message)
@project.errors.add(:base, message) if @project
@project
end
......
......@@ -130,6 +130,20 @@ class SystemNoteService
create_note(noteable: noteable, project: project, author: author, note: body)
end
# Called when 'merge when build succeeds' is executed
def self.merge_when_build_succeeds(noteable, project, author, last_commit)
body = "Enabled an automatic merge when the build for #{last_commit.to_reference(project)} succeeds"
create_note(noteable: noteable, project: project, author: author, note: body)
end
# Called when 'merge when build succeeds' is canceled
def self.cancel_merge_when_build_succeeds(noteable, project, author)
body = "Canceled the automatic merge"
create_note(noteable: noteable, project: project, author: author, note: body)
end
# Called when the title of a Noteable is changed
#
# noteable - Noteable object that responds to `title`
......
......@@ -12,7 +12,7 @@
.gray-content-block.middle-block
%h2.issue-title
= gfm escape_once(@milestone.title)
= markdown escape_once(@milestone.title), pipeline: :single_line
- if @milestone.complete? && @milestone.active?
.alert.alert-success.prepend-top-default
......
......@@ -2,4 +2,4 @@
.commit-row-title
= link_to truncate_sha(commit[:id]), namespace_project_commit_path(project.namespace, project, commit[:id]), class: "commit_short_id", alt: ''
&middot;
= gfm event_commit_title(commit[:message]), project: project
= markdown event_commit_title(commit[:message]), project: project, pipeline: :single_line
......@@ -18,7 +18,7 @@
.gray-content-block.middle-block
%h2.issue-title
= gfm escape_once(@milestone.title)
= markdown escape_once(@milestone.title), pipeline: :single_line
- if @milestone.complete? && @milestone.active?
.alert.alert-success.prepend-top-default
......
......@@ -32,7 +32,7 @@
Files
- if project_nav_tab? :commits
= nav_link(controller: %w(commit commits compare repositories tags branches releases)) do
= nav_link(controller: %w(commit commits compare repositories tags branches releases network)) do
= link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits' do
= icon('history fw')
%span
......@@ -46,13 +46,6 @@
Builds
%span.count.builds_counter= @project.ci_builds.running_or_pending.count(:all)
- if project_nav_tab? :network
= nav_link(controller: %w(network)) do
= link_to namespace_project_network_path(@project.namespace, @project, current_ref), title: 'Network', class: 'shortcuts-network' do
= icon('code-fork fw')
%span
Network
- if project_nav_tab? :graphs
= nav_link(controller: %w(graphs)) do
= link_to namespace_project_graph_path(@project.namespace, @project, current_ref), title: 'Graphs', class: 'shortcuts-graphs' do
......@@ -118,3 +111,10 @@
= icon('cogs fw')
%span
Settings
-# Global shortcut to network page for compatibility
- if project_nav_tab? :network
%li.hidden
= link_to namespace_project_network_path(@project.namespace, @project, current_ref), title: 'Network', class: 'shortcuts-network' do
Network
%div
"#{link_to @note.author_name, user_url(@note.author)} wrote:"
%div
= markdown(@note.note, pipeline: :email)
%h3 #{@author.name} #{@action_name} #{@ref_type} #{@ref_name} at #{link_to @project.name_with_namespace, namespace_project_url(@project.namespace, @project)}
%h3
#{@message.author_name} #{@message.action_name} #{@message.ref_type} #{@message.ref_name}
at #{link_to(@message.project_name_with_namespace, namespace_project_url(@message.project_namespace, @message.project))}
- if @compare
- if @reverse_compare
- if @message.compare
- if @message.reverse_compare?
%p
%strong WARNING:
The push did not contain any new commits, but force pushed to delete the commits and changes below.
%h4
= @reverse_compare ? "Deleted commits:" : "Commits:"
= @message.reverse_compare? ? "Deleted commits:" : "Commits:"
%ul
- @commits.each do |commit|
- @message.commits.each do |commit|
%li
%strong #{link_to commit.short_id, namespace_project_commit_url(@project.namespace, @project, commit)}
%strong #{link_to(commit.short_id, namespace_project_commit_url(@message.project_namespace, @message.project, commit))}
%div
%span by #{commit.author_name}
%i at #{commit.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ")}
%pre.commit-message
= commit.safe_message
%h4 #{pluralize @diffs.count, "changed file"}:
%h4 #{pluralize @message.diffs_count, "changed file"}:
%ul
- @diffs.each_with_index do |diff, i|
- @message.diffs.each_with_index do |diff, i|
%li.file-stats
%a{href: "#{@target_url if @disable_diffs}#diff-#{i}" }
%a{href: "#{@message.target_url if @message.disable_diffs?}#diff-#{i}" }
- if diff.deleted_file
%span.deleted-file
&minus;
......@@ -40,11 +42,11 @@
- else
= diff.new_path
- unless @disable_diffs
- unless @message.disable_diffs?
%h4 Changes:
- @diffs.each_with_index do |diff, i|
- @message.diffs.each_with_index do |diff, i|
%li{id: "diff-#{i}"}
%a{href: @target_url + "#diff-#{i}"}
%a{href: @message.target_url + "#diff-#{i}"}
- if diff.deleted_file
%strong
= diff.old_path
......@@ -62,5 +64,5 @@
= color_email_diff(diff.diff)
%br
- if @compare.timeout
- if @message.compare_timeout
%h5 Huge diff. To prevent performance issues changes are hidden
#{@author.name} #{@action_name} #{@ref_type} #{@ref_name} at #{@project.name_with_namespace}
- if @compare
#{@message.author_name} #{@message.action_name} #{@message.ref_type} #{@message.ref_name} at #{@message.project_name_with_namespace}
- if @message.compare
\
\
- if @reverse_compare
- if @message.reverse_compare?
WARNING: The push did not contain any new commits, but force pushed to delete the commits and changes below.
\
\
= @reverse_compare ? "Deleted commits:" : "Commits:"
- @commits.each do |commit|
= @message.reverse_compare? ? "Deleted commits:" : "Commits:"
- @message.commits.each do |commit|
#{commit.short_id} by #{commit.author_name} at #{commit.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ")}
#{commit.safe_message}
\- - - - -
\
\
#{pluralize @diffs.count, "changed file"}:
#{pluralize @message.diffs_count, "changed file"}:
\
- @diffs.each do |diff|
- @message.diffs.each do |diff|
- if diff.deleted_file
\- − #{diff.old_path}
- elsif diff.renamed_file
......@@ -24,11 +24,11 @@
\- + #{diff.new_path}
- else
\- #{diff.new_path}
- unless @disable_diffs
- unless @message.disable_diffs?
\
\
Changes:
- @diffs.each do |diff|
- @message.diffs.each do |diff|
\
\=====================================
- if diff.deleted_file
......@@ -39,11 +39,11 @@
= diff.new_path
\=====================================
!= diff.diff
- if @compare.timeout
- if @message.compare_timeout
\
\
Huge diff. To prevent performance issues it was hidden
- if @target_url
- if @message.target_url
\
\
View it on GitLab: #{@target_url}
View it on GitLab: #{@message.target_url}
......@@ -12,17 +12,20 @@
%li{class: ('active' if @scope.nil?)}
= link_to project_builds_path(@project) do
Running
%span.badge.js-running-count= @all_builds.running_or_pending.count(:id)
%span.badge.js-running-count
= number_with_delimiter(@all_builds.running_or_pending.count(:id))
%li{class: ('active' if @scope == 'finished')}
= link_to project_builds_path(@project, scope: :finished) do
Finished
%span.badge.js-running-count= @all_builds.finished.count(:id)
%span.badge.js-running-count
= number_with_delimiter(@all_builds.finished.count(:id))
%li{class: ('active' if @scope == 'all')}
= link_to project_builds_path(@project, scope: :all) do
All
%span.badge.js-totalbuilds-count= @all_builds.count(:id)
%span.badge.js-totalbuilds-count
= number_with_delimiter(@all_builds.count(:id))
.gray-content-block
#{(@scope || 'running').capitalize} builds from this project
......
......@@ -52,10 +52,10 @@
.commit-box.gray-content-block.middle-block
%h3.commit-title
= gfm escape_once(@commit.title)
= markdown escape_once(@commit.title), pipeline: :single_line
- if @commit.description.present?
%pre.commit-description
= preserve(gfm(escape_once(@commit.description)))
= preserve(markdown(escape_once(@commit.description), pipeline: :single_line))
:javascript
$(".commit-info-row.branches").load("#{branches_namespace_project_commit_path(@project.namespace, @project, @commit.id)}");
......@@ -32,7 +32,7 @@
- if commit.description?
.commit-row-description.js-toggle-content
%pre
= preserve(gfm(escape_once(commit.description)))
= preserve(markdown(escape_once(commit.description), pipeline: :single_line))
.commit-row-info
= commit_author_link(commit, avatar: true, size: 24)
......
......@@ -3,6 +3,11 @@
= link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do
Commits
%span.badge= number_with_delimiter(@repository.commit_count)
= nav_link(controller: %w(network)) do
= link_to namespace_project_network_path(@project.namespace, @project, current_ref) do
Network
= nav_link(controller: :compare) do
= link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: current_ref) do
Compare
......
......@@ -17,7 +17,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.name commit.author_name
xml.email commit.author_email
end
xml.summary gfm(commit.description)
xml.summary markdown(commit.description, pipeline: :single_line)
end
end
end
......@@ -24,10 +24,10 @@
.col-sm-10
= f.text_area :description, class: "form-control", rows: 3, maxlength: 250
- if @project.repository.exists? && @project.repository.branch_names.any?
- unless @project.empty_repo?
.form-group
= f.label :default_branch, "Default Branch", class: 'control-label'
.col-sm-10= f.select(:default_branch, @repository.branch_names, {}, {class: 'select2 select-wide'})
.col-sm-10= f.select(:default_branch, @project.repository.branch_names, {}, {class: 'select2 select-wide'})
= render 'shared/visibility_level', f: f, visibility_level: @project.visibility_level, can_change_visibility_level: can_change_visibility_level?(@project, current_user), form_model: @project
......
.alert_holder
= content_for :flash_message do
- if current_user && can?(current_user, :download_code, @project)
= render 'shared/no_ssh'
= render 'shared/no_password'
......
.issue-closed-by-widget
= icon('check')
This issue will be closed automatically when merge request #{gfm(merge_requests_sentence(@closed_by_merge_requests))} is accepted.
This issue will be closed automatically when merge request #{markdown(merge_requests_sentence(@closed_by_merge_requests), pipeline: :gfm)} is accepted.
......@@ -5,24 +5,8 @@
- else
= link_to 'Close Issue', issue_path(@issue, issue: {state_event: :close}, status_only: true), method: :put, class: 'btn btn-grouped btn-close js-note-target-close', title: 'Close Issue'
= render 'shared/show_aside'
.gray-content-block.second-block.oneline-block
.row
.col-md-9
.votes-holder.pull-right
#votes= render 'votes/votes_block', votable: @issue
= render "shared/issuable/participants"
.col-md-3
.input-group.cross-project-reference
%span#cross-project-reference.slead.has_tooltip{title: 'Cross-project reference'}
= cross_project_reference(@project, @issue)
= clipboard_button(clipboard_target: 'span#cross-project-reference')
= render 'votes/votes_block', votable: @issue
.row
%section.col-md-9
.voting_notes#notes= render 'projects/notes/notes_with_form'
%aside.col-md-3
.issuable-affix
.context
= render 'shared/issuable/context', issuable: @issue
#notes
= render 'projects/notes/notes_with_form'
......@@ -6,23 +6,26 @@
.issue-title
%span.issue-title-text
= link_to_gfm issue.title, issue_path(issue), class: "row_title"
.pull-right.light
%ul.controls.light
- if issue.closed?
%span
%li
CLOSED
- if issue.assignee
= link_to_member(@project, issue.assignee, name: false, title: "Assigned to :name")
%li
= link_to_member(@project, issue.assignee, name: false, title: "Assigned to :name")
- note_count = issue.notes.user.count
- if note_count > 0
&nbsp;
= link_to issue_path(issue) + "#notes" do
= icon('comments')
= note_count
%li
= link_to issue_path(issue) + "#notes" do
= icon('comments')
= note_count
- else
&nbsp;
= link_to issue_path(issue) + "#notes", class: "issue-no-comments" do
= icon('comments')
= 0
%li
= link_to issue_path(issue) + "#notes", class: "issue-no-comments" do
= icon('comments')
= note_count
.issue-info
#{issue.to_reference} &middot;
......
-if @merge_requests.any?
%h2.merge-requests-title
= pluralize(@merge_requests.count, 'Related Merge Request')
%ul.bordered-list
- has_any_ci = @merge_requests.any?(&:ci_commit)
- @merge_requests.each do |merge_request|
%li
%span.merge-request-ci-status
- if merge_request.ci_commit
= render_ci_status(merge_request.ci_commit)
- elsif has_any_ci
= icon('blank fw')
%span.merge-request-id
\##{merge_request.iid}
%span.merge-request-info
%strong
= link_to_gfm merge_request.title, merge_request_path(merge_request), class: "row_title"
in
- project = merge_request.target_project
= link_to project.name_with_namespace, namespace_project_path(project.namespace, project)
%span.merge-request-status.prepend-left-10
- if merge_request.merged?
MERGED
- elsif merge_request.closed?
CLOSED
......@@ -3,13 +3,13 @@
.issue
.issue-details.issuable-details
.page-title
.issuable-title
.issue-box{ class: issue_box_class(@issue) }
- if @issue.closed?
Closed
- else
Open
%span.issue-id Issue ##{@issue.iid}
%span.issuable-id Issue ##{@issue.iid}
%span.creator
&middot;
opened by #{link_to_member(@project, @issue.author, size: 24)}
......@@ -36,18 +36,29 @@
= icon('pencil-square-o')
Edit
.gray-content-block.middle-block
%h2.issue-title
= gfm escape_once(@issue.title)
%div
- if @issue.description.present?
.description{class: can?(current_user, :update_issue, @issue) ? 'js-task-list-container' : ''}
.wiki
= preserve do
= markdown(@issue.description)
%textarea.hidden.js-task-list-field
= @issue.description
- if @closed_by_merge_requests.present?
= render 'projects/issues/closed_by_box'
.issue-discussion
= render 'projects/issues/discussion'
.row
%section.col-md-9
.gray-content-block
%h2.issue-title
= markdown escape_once(@issue.title), pipeline: :single_line
%div
- if @issue.description.present?
.description{class: can?(current_user, :update_issue, @issue) ? 'js-task-list-container' : ''}
.wiki
= preserve do
= markdown(@issue.description, cache_key: [@issue, "description"])
%textarea.hidden.js-task-list-field
= @issue.description
.merge-requests
= render 'merge_requests'
- if @closed_by_merge_requests.present?
= render 'projects/issues/closed_by_box'
.issue-discussion
= render 'projects/issues/discussion'
%aside.col-md-3
= render 'shared/issuable/sidebar', issuable: @issue
= render 'shared/show_aside'
$('.context').html("#{escape_javascript(render 'shared/issuable/context', issuable: @issue)}");
$('.context').effect('highlight')
$('.issuable-sidebar').html("#{escape_javascript(render 'shared/issuable/sidebar', issuable: @issue)}");
$('.issuable-sidebar').parent().effect('highlight')
new Issue();
......@@ -5,24 +5,7 @@
- if @merge_request.closed?
= link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-grouped btn-reopen reopen-mr-link js-note-target-reopen", title: "Reopen merge request"
= render 'shared/show_aside'
.gray-content-block.second-block.oneline-block
= render 'votes/votes_block', votable: @merge_request
.gray-content-block.middle-block.oneline-block
.row
.col-md-9
.votes-holder.pull-right
#votes= render 'votes/votes_block', votable: @merge_request
= render "shared/issuable/participants"
.col-md-3
.input-group.cross-project-reference
%span#cross-project-reference.slead.has_tooltip{title: 'Cross-project reference'}
= cross_project_reference(@project, @merge_request)
= clipboard_button(clipboard_target: 'span#cross-project-reference')
.row
%section.col-md-9
.voting_notes#notes= render "projects/notes/notes_with_form"
%aside.col-md-3
.issuable-affix
.context
= render 'shared/issuable/context', issuable: @merge_request
#notes= render "projects/notes/notes_with_form"
- ci_commit = merge_request.ci_commit
%li{ class: mr_css_classes(merge_request) }
.merge-request-title
%span.merge-request-title-text
= link_to_gfm merge_request.title, merge_request_path(merge_request), class: "row_title"
.pull-right.light
- if ci_commit
= render_ci_status(ci_commit)
%ul.controls.light
- if merge_request.merged?
%span
= icon('check')
%li
MERGED
- elsif merge_request.closed?
%span
%li
= icon('ban')
CLOSED
- note_count = merge_request.mr_and_commit_notes.user.count
- if merge_request.ci_commit
%li
= render_ci_status(merge_request.ci_commit)
- if merge_request.open? && merge_request.broken?
%li
= link_to merge_request_path(merge_request), class: "has_tooltip", title: "Cannot be merged automatically", data: {container: 'body'} do
= icon('exclamation-triangle')
- if merge_request.assignee
&nbsp;
= link_to_member(merge_request.source_project, merge_request.assignee, name: false, title: "Assigned to :name")
%li
= link_to_member(merge_request.source_project, merge_request.assignee, name: false, title: "Assigned to :name")
- note_count = merge_request.mr_and_commit_notes.user.count
- if note_count > 0
&nbsp;
= link_to merge_request_path(merge_request) + "#notes" do
= icon('comments')
= note_count
%li
= link_to merge_request_path(merge_request) + "#notes" do
= icon('comments')
= note_count
- else
&nbsp;
= link_to merge_request_path(merge_request) + "#notes", class: "merge-request-no-comments" do
= icon('comments')
= 0
%li
= link_to merge_request_path(merge_request) + "#notes", class: "merge-request-no-comments" do
= icon('comments')
= note_count
.merge-request-info
\##{merge_request.iid} &middot;
......
......@@ -7,71 +7,79 @@
.merge-request{'data-url' => merge_request_path(@merge_request)}
.merge-request-details.issuable-details
= render "projects/merge_requests/show/mr_title"
= render "projects/merge_requests/show/mr_box"
.append-bottom-default.mr-source-target.prepend-top-default
- if @merge_request.open?
.pull-right
- if @merge_request.source_branch_exists?
= link_to "#modal_merge_info", class: "btn btn-sm", "data-toggle" => "modal" do
= icon('cloud-download fw')
Check out branch
.row
%section.col-md-9
= render "projects/merge_requests/show/mr_box"
.append-bottom-default.mr-source-target.prepend-top-default
- if @merge_request.open?
.pull-right
- if @merge_request.source_branch_exists?
= link_to "#modal_merge_info", class: "btn btn-sm", "data-toggle" => "modal" do
= icon('cloud-download fw')
Check out branch
%span.dropdown
%a.btn.btn-sm.dropdown-toggle{ data: {toggle: :dropdown} }
= icon('download')
Download as
%span.caret
%ul.dropdown-menu
%li= link_to "Email Patches", merge_request_path(@merge_request, format: :patch)
%li= link_to "Plain Diff", merge_request_path(@merge_request, format: :diff)
.normal
%span Request to merge
%span.label-branch= source_branch_with_namespace(@merge_request)
%span into
= link_to namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch" do
= @merge_request.target_branch
%span.dropdown
%a.btn.btn-sm.dropdown-toggle{ data: {toggle: :dropdown} }
= icon('download')
Download as
%span.caret
%ul.dropdown-menu
%li= link_to "Email Patches", merge_request_path(@merge_request, format: :patch)
%li= link_to "Plain Diff", merge_request_path(@merge_request, format: :diff)
.normal
%span Request to merge
%span.label-branch= source_branch_with_namespace(@merge_request)
%span into
= link_to namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch" do
= @merge_request.target_branch
= render "projects/merge_requests/show/how_to_merge"
= render "projects/merge_requests/widget/show.html.haml"
= render "projects/merge_requests/show/how_to_merge"
= render "projects/merge_requests/widget/show.html.haml"
- if @merge_request.open? && @merge_request.source_branch_exists? && @merge_request.can_be_merged? && @merge_request.can_be_merged_by?(current_user)
.light.prepend-top-default
You can also accept this merge request manually using the
= succeed '.' do
= link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal"
- if @merge_request.open? && @merge_request.source_branch_exists? && @merge_request.can_be_merged? && @merge_request.can_be_merged_by?(current_user)
.light.prepend-top-default
You can also accept this merge request manually using the
= succeed '.' do
= link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal"
- if @commits.present?
%ul.merge-request-tabs.center-top-menu.no-top.no-bottom
%li.notes-tab
= link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#notes', action: 'notes', toggle: 'tab'} do
Discussion
%span.badge= @merge_request.mr_and_commit_notes.user.count
%li.commits-tab
= link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#commits', action: 'commits', toggle: 'tab'} do
Commits
%span.badge= @commits.size
%li.diffs-tab
= link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'} do
Changes
%span.badge= @merge_request.diffs.size
- if @ci_commit
%li.builds-tab
= link_to builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#builds', action: 'builds', toggle: 'tab'} do
Builds
%span.badge= @statuses.size
- if @commits.present?
%ul.merge-request-tabs.center-top-menu.no-top.no-bottom
%li.notes-tab
= link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#notes', action: 'notes', toggle: 'tab'} do
Discussion
%span.badge= @merge_request.mr_and_commit_notes.user.count
%li.commits-tab
= link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#commits', action: 'commits', toggle: 'tab'} do
Commits
%span.badge= @commits.size
%li.diffs-tab
= link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'} do
Changes
%span.badge= @merge_request.diffs.size
- if @ci_commit
%li.builds-tab
= link_to builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#builds', action: 'builds', toggle: 'tab'} do
Builds
%span.badge= @statuses.size
.tab-content
#notes.notes.tab-pane.voting_notes
= render "projects/merge_requests/discussion"
#commits.commits.tab-pane
- # This tab is always loaded via AJAX
#diffs.diffs.tab-pane
- # This tab is always loaded via AJAX
#builds.builds.tab-pane
- # This tab is always loaded via AJAX
.tab-content
#notes.notes.tab-pane.voting_notes
= render "projects/merge_requests/discussion"
#commits.commits.tab-pane
- # This tab is always loaded via AJAX
#diffs.diffs.tab-pane
- # This tab is always loaded via AJAX
#builds.builds.tab-pane
- # This tab is always loaded via AJAX
.mr-loading-status
= spinner
%aside.col-md-3
= render 'shared/issuable/sidebar', issuable: @merge_request
= render 'shared/show_aside'
.mr-loading-status
= spinner
:javascript
var merge_request;
......
:plain
$('.mr-widget-body').html("#{escape_javascript(render('projects/merge_requests/widget/open/accept'))}");
- if @status
- case @status
- when :success
:plain
merge_request_widget.mergeInProgress(#{params[:should_remove_source_branch] == '1'});
- when :merge_when_build_succeeds
:plain
$('.mr-widget-body').html("#{escape_javascript(render('projects/merge_requests/widget/open/merge_when_build_succeeds'))}");
- else
:plain
$('.mr-widget-body').html("#{escape_javascript(render('projects/merge_requests/widget/open/reload'))}");
.gray-content-block.middle-block
%h2.issue-title
= gfm escape_once(@merge_request.title)
= markdown escape_once(@merge_request.title), pipeline: :single_line
%div
- if @merge_request.description.present?
.description{class: can?(current_user, :update_merge_request, @merge_request) ? 'js-task-list-container' : ''}
.wiki
= preserve do
= markdown(@merge_request.description)
= markdown(@merge_request.description, cache_key: [@merge_request, "description"])
%textarea.hidden.js-task-list-field
= @merge_request.description
.page-title
.issuable-title
.issue-box{ class: issue_box_class(@merge_request) }
= @merge_request.state_human_name
%span.issue-id Merge Request ##{@merge_request.iid}
%span.issuable-id Merge Request ##{@merge_request.iid}
%span.creator
&middot;
opened by #{link_to_member(@project, @merge_request.author, size: 24)}
......
$('.context').html("#{escape_javascript(render 'shared/issuable/context', issuable: @merge_request)}");
$('.context').effect('highlight')
$('.issuable-sidebar').html("#{escape_javascript(render 'shared/issuable/sidebar', issuable: @merge_request)}");
$('.issuable-sidebar').parent().effect('highlight')
merge_request = new MergeRequest();
......@@ -14,7 +14,7 @@
= @merge_request.target_branch
The source branch has been removed.
- elsif can_remove_branch?(@merge_request.source_project, @merge_request.source_branch)
- elsif @merge_request.can_remove_source_branch?(current_user)
.remove_source_branch_widget
%p
= succeed '.' do
......@@ -33,7 +33,7 @@
.remove_source_branch_in_progress.hide
%p
= icon('spinner spin')
Removing source branch '#{@merge_request.source_branch}'. Please wait. This page will be automatically reload.
Removing source branch '#{@merge_request.source_branch}'. Please wait, this page will be automatically reloaded.
:javascript
$('.remove_source_branch').on('click', function() {
......@@ -50,5 +50,3 @@
$('.remove_source_branch_in_progress').hide();
$('.remove_source_branch_widget.failed').show();
});
......@@ -15,6 +15,8 @@
= render 'projects/merge_requests/widget/open/wip'
- elsif @merge_request.requires_approve? && !@merge_request.approved?
= render 'projects/merge_requests/widget/open/approve'
- elsif @merge_request.merge_when_build_succeeds?
= render 'projects/merge_requests/widget/open/merge_when_build_succeeds'
- elsif !@merge_request.can_be_merged_by?(current_user)
= render 'projects/merge_requests/widget/open/not_allowed'
- elsif @project.merge_requests_ff_only_enabled
......@@ -28,7 +30,7 @@
%i.fa.fa-check
Accepting this merge request will close #{"issue".pluralize(@closes_issues.size)}
= succeed '.' do
!= gfm(issues_sentence(@closes_issues))
!= markdown issues_sentence(@closes_issues), pipeline: :gfm
- if @merge_request.approvals.any?
.mr-widget-footer.approved-by-users
......
......@@ -3,26 +3,60 @@
= form_for [:merge, @project.namespace.becomes(Namespace), @project, @merge_request], remote: true, method: :post, html: { class: 'accept-mr-form js-requires-input' } do |f|
= hidden_field_tag :authenticity_token, form_authenticity_token
.accept-merge-holder.clearfix.js-toggle-container
.accept-action
= f.button class: "btn btn-create accept_merge_request#{status_class}" do
Accept Merge Request
- if can_remove_branch?(@merge_request.source_project, @merge_request.source_branch) && !@merge_request.for_fork?
.accept-control.checkbox
= label_tag :should_remove_source_branch, class: "remove_source_checkbox" do
= check_box_tag :should_remove_source_branch
Remove source branch
.accept-control.right
= link_to "#", class: "modify-merge-commit-link js-toggle-button" do
= icon('edit')
Modify commit message
.js-toggle-content.hide.prepend-top-20
.clearfix
.accept-action
- if @ci_commit && @ci_commit.active?
%span.btn-group
= link_to "#", class: "btn btn-create merge_when_build_succeeds" do
Merge When Build Succeeds
%a.btn.btn-success.dropdown-toggle{ 'data-toggle' => 'dropdown' }
%span.caret
%span.sr-only
Select Merge Moment
%ul.dropdown-menu.dropdown-menu-right{ role: 'menu' }
%li
= link_to "#", class: "merge_when_build_succeeds" do
= icon('check fw')
Merge When Build Succeeds
%li
= link_to "#", class: "accept_merge_request" do
= icon('warning fw')
Merge Immediately
- else
= f.button class: "btn btn-create btn-grouped accept_merge_request #{status_class}" do
Accept Merge Request
- if @merge_request.can_remove_source_branch?(current_user)
.accept-control.checkbox
= label_tag :should_remove_source_branch, class: "remove_source_checkbox" do
= check_box_tag :should_remove_source_branch
Remove source branch
.accept-control.right
= link_to "#", class: "modify-merge-commit-link js-toggle-button" do
= icon('edit')
Modify commit message
.js-toggle-content.hide.prepend-top-default
= render 'shared/commit_message_container', params: params,
text: @merge_request.merge_commit_message,
rows: 14, hint: true
= hidden_field_tag :merge_when_build_succeeds, "", autocomplete: "off"
:javascript
$('.accept-mr-form').on('ajax:before', function() {
var btn = $('.accept_merge_request');
btn.disable();
btn.html("<i class='fa fa-spinner fa-spin'></i> Merge in progress");
$('.accept_merge_request').on('click', function() {
$(this).html("<i class='fa fa-spinner fa-spin'></i> Merge in progress");
});
$('.accept-mr-form').on('ajax:send', function() {
$(".accept-mr-form :input").disable();
});
$('a.accept_merge_request').on('click', function(e) {
e.preventDefault();
$(this).closest("form").submit();
});
$('a.merge_when_build_succeeds').on('click', function(e) {
e.preventDefault();
$("#merge_when_build_succeeds").val("1");
$(this).closest("form").submit();
});
%h4
Set by #{link_to_member(@project, @merge_request.merge_user, avatar: true)}
to be merged automatically when the build succeeds.
%div
- should_remove_source_branch = @merge_request.merge_params["should_remove_source_branch"].present?
%p
= succeed '.' do
The changes will be merged into
%span.label-branch= @merge_request.target_branch
- if should_remove_source_branch
The source branch will be removed.
- else
The source branch will not be removed.
- remove_source_branch_button = @merge_request.can_remove_source_branch?(current_user) && !should_remove_source_branch
- user_can_cancel_automatic_merge = @merge_request.can_cancel_merge_when_build_succeeds?(current_user)
- if remove_source_branch_button || user_can_cancel_automatic_merge
.clearfix.prepend-top-10
- if remove_source_branch_button
= link_to merge_namespace_project_merge_request_path(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request, merge_when_build_succeeds: true, should_remove_source_branch: true), remote: true, method: :post, class: "btn btn-grouped btn-primary btn-sm remove_source_branch" do
= icon('times')
Remove Source Branch When Merged
- if user_can_cancel_automatic_merge
= link_to cancel_merge_when_build_succeeds_namespace_project_merge_request_path(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request), remote: true, method: :post, class: "btn btn-grouped btn-warning btn-sm" do
Cancel Automatic Merge
......@@ -32,7 +32,7 @@
.gray-content-block.middle-block
%h2.issue-title
= gfm escape_once(@milestone.title)
= markdown escape_once(@milestone.title), pipeline: :single_line
%div
- if @milestone.description.present?
.description
......
.gray-content-block.top-block.append-bottom-default
.gray-content-block.append-bottom-default
.tree-ref-holder
= render partial: 'shared/ref_switcher', locals: {destination: 'graph'}
......
- page_title "Network", @ref
= header_title project_title(@project, "Network", namespace_project_network_path(@project.namespace, @project, current_ref))
= render "projects/commits/header_title"
= render "projects/commits/head"
= render "head"
.project-network
.controls
......
......@@ -38,7 +38,7 @@
.note-body{class: note_editable?(note) ? 'js-task-list-container' : ''}
.note-text
= preserve do
= markdown(note.note, {no_header_anchors: true})
= markdown(note.note, pipeline: :note, cache_key: [note, "note"])
- if note_editable?(note)
= render 'projects/notes/edit_form', note: note
......
......@@ -12,7 +12,7 @@
= link_to namespace_project_commits_path(@project.namespace, @project, commit.id) do
%code= commit.short_id
= image_tag avatar_icon(commit.author_email), class: "", width: 16, alt: ''
= gfm escape_once(truncate(commit.title, length: 40))
= markdown escape_once(truncate(commit.title, length: 40)), pipeline: :single_line
%td
%span.pull-right.cgray
= time_ago_with_tooltip(commit.committed_date)
......@@ -12,8 +12,8 @@
&nbsp; for this project
- if @shared_runners_count.zero?
This application has no shared runners yet.
Please use specific runners or ask administrator to create one
This GitLab server does not provide any shared runners yet.
Please use specific runners or ask the administrator to create one.
- else
%h4.underlined-title Available shared runners - #{@shared_runners_count}
%ul.bordered-list.available-shared-runners
......
......@@ -10,8 +10,8 @@
%strong
= @snippet.file_name
.file-actions.hidden-xs
.btn-group.tree-btn-group
= link_to 'Raw', raw_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-sm", target: "_blank"
= clipboard_button(clipboard_target: ".blob-content[data-blob-id='#{@snippet.id}']")
= link_to 'Raw', raw_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-sm", target: "_blank"
= render 'shared/snippets/blob'
......
......@@ -32,5 +32,5 @@
New directory
- elsif !on_top_of_branch?
%li
%span.btn.add-to-tree.disabled.has_tooltip{title: "You can only add files when you are on a branch.", data: {container: 'body'}}
%span.btn.btn-sm.add-to-tree.disabled.has_tooltip{title: "You can only add files when you are on a branch.", data: {container: 'body'}}
= icon('plus')
......@@ -8,5 +8,6 @@
%a{href: "#L#{i}", id: "L#{i}", 'data-line-number' => i}
%i.fa.fa-link
= i
:preserve
#{highlight(blob.name, blob.data)}
.blob-content{data: {blob_id: blob.id}}
:preserve
#{highlight(blob.name, blob.data)}
= form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f|
%div.prepend-top-default
.issuable-context-title
%label
Assignee:
- if issuable.assignee
%strong= link_to_member(@project, issuable.assignee, size: 24)
- else
none
.issuable-context-selectbox
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
= users_select_tag("#{issuable.class.table_name.singularize}[assignee_id]", placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: issuable.assignee_id, project: @target_project, null_user: true, current_user: true, first_user: true)
%div.prepend-top-default.clearfix
.issuable-context-title
%label
Milestone:
- if issuable.milestone
%span.back-to-milestone
= link_to namespace_project_milestone_path(@project.namespace, @project, issuable.milestone) do
%strong
= icon('clock-o')
= issuable.milestone.title
- else
none
.issuable-context-selectbox
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
= f.select(:milestone_id, milestone_options(issuable), { include_blank: true }, { class: 'select2 select2-compact js-select2 js-milestone', data: { placeholder: 'Select milestone' }})
= hidden_field_tag :issuable_context
= f.submit class: 'btn hide'
- if issuable.labels.any?
%div.prepend-top-default.clearfix
.issuable-context-title
%label Labels
.issuable-show-labels
- issuable.labels.each do |label|
= link_to_label(label)
- if current_user
- subscribed = issuable.subscribed?(current_user)
%div.prepend-top-default.clearfix
.issuable-context-title
%label Subscription
- subscribtion_status = subscribed ? 'subscribed' : 'unsubscribed'
.subscription-status{data: {status: subscribtion_status}}
.description-block.unsubscribed{class: ( 'hidden' if subscribed )}
You're not receiving notifications from this thread.
.description-block.subscribed{class: ( 'hidden' unless subscribed )}
You're receiving notifications because you're subscribed to this thread.
%button.btn.btn-block.subscribe-button{:type => 'button'}
= icon('eye')
%span= subscribed ? 'Unsubscribe' : 'Subscribe'
:javascript
new Subscription("#{toggle_subscription_path(issuable)}");
new IssuableContext();
.participants
%span
= pluralize @participants.count, "participant"
- @participants.each do |participant|
.block.participants
.title
= pluralize participants.count, "participant"
- participants.each do |participant|
= link_to_member(@project, participant, name: false, size: 24)
.issuable-sidebar.issuable-affix
= form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f|
.block
.title
Cross-project reference
.cross-project-reference
%span#cross-project-reference
= cross_project_reference(@project, issuable)
= clipboard_button(clipboard_target: 'span#cross-project-reference')
.block.assignee
.title
%label
Assignee
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
.pull-right
= link_to 'Edit', '#', class: 'edit-link'
.value
- if issuable.assignee
%strong= link_to_member(@project, issuable.assignee, size: 24)
- else
.light None
.selectbox
= users_select_tag("#{issuable.class.table_name.singularize}[assignee_id]", placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: issuable.assignee_id, project: @target_project, null_user: true, current_user: true, first_user: true)
.block.milestone
.title
%label
Milestone
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
.pull-right
= link_to 'Edit', '#', class: 'edit-link'
.value
- if issuable.milestone
%span.back-to-milestone
= link_to namespace_project_milestone_path(@project.namespace, @project, issuable.milestone) do
%strong
= icon('clock-o')
= issuable.milestone.title
- else
.light None
.selectbox
= f.select(:milestone_id, milestone_options(issuable), { include_blank: true }, { class: 'select2 select2-compact js-select2 js-milestone', data: { placeholder: 'Select milestone' }})
= hidden_field_tag :issuable_context
= f.submit class: 'btn hide'
- if issuable.project.labels.any?
.block
.title
%label Labels
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
.pull-right
= link_to 'Edit', '#', class: 'edit-link'
.value.issuable-show-labels
- if issuable.labels.any?
- issuable.labels.each do |label|
= link_to_label(label)
- else
.light None
.selectbox
= f.collection_select :label_ids, issuable.project.labels.all, :id, :name,
{ selected: issuable.label_ids }, multiple: true, class: 'select2 js-select2', data: { placeholder: "Select labels" }
= render "shared/issuable/participants", participants: issuable.participants(current_user)
- if current_user
- subscribed = issuable.subscribed?(current_user)
.block.light
.title
%label.light Notifications
- subscribtion_status = subscribed ? 'subscribed' : 'unsubscribed'
%button.btn.btn-block.btn-gray.subscribe-button{:type => 'button'}
%span= subscribed ? 'Unsubscribe' : 'Subscribe'
.subscription-status{data: {status: subscribtion_status}}
.unsubscribed{class: ( 'hidden' if subscribed )}
You're not receiving notifications from this thread.
.subscribed{class: ( 'hidden' unless subscribed )}
You're receiving notifications because you're subscribed to this thread.
:javascript
new Subscription("#{toggle_subscription_path(issuable)}");
new IssuableContext();
......@@ -22,4 +22,4 @@
.gray-content-block.middle-block
%h2.issue-title
= gfm escape_once(@snippet.title)
= markdown escape_once(@snippet.title), pipeline: :single_line
......@@ -9,6 +9,6 @@
%strong
= @snippet.file_name
.file-actions.hidden-xs
.btn-group.tree-btn-group
= link_to 'Raw', raw_snippet_path(@snippet), class: "btn btn-sm", target: "_blank"
= clipboard_button(clipboard_target: ".blob-content[data-blob-id='#{@snippet.id}']")
= link_to 'Raw', raw_snippet_path(@snippet), class: "btn btn-sm", target: "_blank"
= render 'shared/snippets/blob'
......@@ -19,7 +19,8 @@
post_emoji_url = "#{award_toggle_namespace_project_notes_path(@project.namespace, @project)}"
noteable_type = "#{votable.class.name.underscore}"
noteable_id = "#{votable.id}"
window.awards_handler = new AwardsHandler(post_emoji_url, noteable_type, noteable_id)
aliases = #{AwardEmoji::ALIASES.to_json}
window.awards_handler = new AwardsHandler(post_emoji_url, noteable_type, noteable_id, aliases)
$(".awards-menu li").click (e)->
emoji = $(this).data("emoji")
......
......@@ -8,16 +8,7 @@ class MergeWorker
current_user = User.find(current_user_id)
merge_request = MergeRequest.find(merge_request_id)
result = MergeRequests::MergeService.new(merge_request.target_project, current_user).
execute(merge_request, params[:commit_message])
if result[:status] == :success && params[:should_remove_source_branch].present?
DeleteBranchService.new(merge_request.source_project, current_user).
execute(merge_request.source_branch)
merge_request.source_project.repository.expire_branch_names
end
result
MergeRequests::MergeService.new(merge_request.target_project, current_user, params).
execute(merge_request)
end
end
......@@ -7,6 +7,8 @@ Rails.application.configure do
# and recreated between test runs. Don't rely on the data there!
config.cache_classes = false
config.cache_store = :null_store
# Configure static asset server for tests with Cache-Control for performance
config.serve_static_files = true
config.static_cache_control = "public, max-age=3600"
......
......@@ -60,7 +60,7 @@ Devise.setup do |config|
# It will change confirmation, password recovery and other workflows
# to behave the same regardless if the e-mail provided was right or wrong.
# Does not affect registerable.
# config.paranoid = true
config.paranoid = true
# ==> Configuration for :database_authenticatable
# For bcrypt, this is the cost for hashing the password and defaults to 10. If
......
......@@ -10,7 +10,9 @@ rescue
Settings.gitlab['session_expire_delay'] ||= 10080
end
unless Rails.env.test?
if Rails.env.test?
Gitlab::Application.config.session_store :cookie_store, key: "_gitlab_session"
else
Gitlab::Application.config.session_store(
:redis_store, # Using the cookie_store would enable session replay attacks.
servers: Rails.application.config.cache_store[1].merge(namespace: 'session:gitlab'), # re-use the Redis config from the Rails cache store
......
......@@ -23,6 +23,14 @@ Sidekiq.configure_server do |config|
if File.exists?(schedule_file)
Sidekiq::Cron::Job.load_from_hash YAML.load(ERB.new(File.read(schedule_file)).result)
end
# Database pool should be at least `sidekiq_concurrency` + 2
# For more info, see: https://github.com/mperham/sidekiq/blob/master/4.0-Upgrade.md
config = ActiveRecord::Base.configurations[Rails.env] ||
Rails.application.config.database_configuration[Rails.env]
config['pool'] = Sidekiq.options[:concurrency] + 2
ActiveRecord::Base.establish_connection(config)
Rails.logger.debug("Connection Pool size for Sidekiq Server is now: #{ActiveRecord::Base.connection.pool.instance_variable_get('@size')}")
end
Sidekiq.configure_client do |config|
......
......@@ -30,7 +30,6 @@ en:
success: "Successfully authenticated from %{kind} account."
passwords:
no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided."
recently_reset: "Instructions about how to reset your password have already been sent recently. Please wait a few minutes to try again."
send_instructions: "You will receive an email with instructions on how to reset your password in a few minutes."
send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes."
updated: "Your password has been changed successfully. You are now signed in."
......
......@@ -614,8 +614,9 @@ Rails.application.routes.draw do
get :commits
get :diffs
get :builds
post :merge
get :merge_check
post :merge
post :cancel_merge_when_build_succeeds
get :ci_status
post :toggle_subscription
post :approve
......
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.
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.
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