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