summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/application.js.coffee12
-rw-r--r--app/assets/javascripts/behaviors/quick_submit.js.coffee6
-rw-r--r--app/assets/javascripts/dispatcher.js.coffee12
-rw-r--r--app/assets/javascripts/dropzone_input.js.coffee9
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js.coffee63
-rw-r--r--app/assets/javascripts/gl_form.js.coffee51
-rw-r--r--app/assets/javascripts/issue.js.coffee23
-rw-r--r--app/assets/javascripts/labels_select.js.coffee6
-rw-r--r--app/assets/javascripts/merge_request_tabs.js.coffee36
-rw-r--r--app/assets/javascripts/notes.js.coffee58
-rw-r--r--app/assets/javascripts/subscription.js.coffee2
-rw-r--r--app/assets/javascripts/users_select.js.coffee3
-rw-r--r--app/assets/stylesheets/framework/gfm.scss18
-rw-r--r--app/assets/stylesheets/framework/issue_box.scss2
-rw-r--r--app/assets/stylesheets/framework/markdown_area.scss13
-rw-r--r--app/assets/stylesheets/framework/mobile.scss7
-rw-r--r--app/assets/stylesheets/framework/tw_bootstrap.scss1
-rw-r--r--app/assets/stylesheets/framework/typography.scss8
-rw-r--r--app/assets/stylesheets/framework/variables.scss1
-rw-r--r--app/assets/stylesheets/highlight/dark.scss6
-rw-r--r--app/assets/stylesheets/highlight/monokai.scss6
-rw-r--r--app/assets/stylesheets/highlight/solarized_dark.scss6
-rw-r--r--app/assets/stylesheets/highlight/solarized_light.scss6
-rw-r--r--app/assets/stylesheets/highlight/white.scss6
-rw-r--r--app/assets/stylesheets/pages/commits.scss5
-rw-r--r--app/assets/stylesheets/pages/detail_page.scss7
-rw-r--r--app/assets/stylesheets/pages/diff.scss38
-rw-r--r--app/assets/stylesheets/pages/issuable.scss63
-rw-r--r--app/assets/stylesheets/pages/issues.scss40
-rw-r--r--app/assets/stylesheets/pages/labels.scss27
-rw-r--r--app/assets/stylesheets/pages/note_form.scss14
-rw-r--r--app/assets/stylesheets/pages/notes.scss10
-rw-r--r--app/controllers/admin/application_settings_controller.rb10
-rw-r--r--app/controllers/admin/projects_controller.rb12
-rw-r--r--app/controllers/application_controller.rb33
-rw-r--r--app/controllers/autocomplete_controller.rb9
-rw-r--r--app/controllers/groups_controller.rb1
-rw-r--r--app/controllers/help_controller.rb1
-rw-r--r--app/controllers/oauth/applications_controller.rb2
-rw-r--r--app/controllers/profiles/keys_controller.rb5
-rw-r--r--app/controllers/projects/issues_controller.rb44
-rw-r--r--app/controllers/projects/repositories_controller.rb1
-rw-r--r--app/helpers/diff_helper.rb3
-rw-r--r--app/helpers/issuables_helper.rb9
-rw-r--r--app/helpers/selects_helper.rb16
-rw-r--r--app/mailers/repository_check_mailer.rb14
-rw-r--r--app/models/application_setting.rb3
-rw-r--r--app/models/commit.rb4
-rw-r--r--app/models/external_issue.rb6
-rw-r--r--app/models/issue.rb4
-rw-r--r--app/models/merge_request.rb6
-rw-r--r--app/models/oauth_access_token.rb19
-rw-r--r--app/models/project.rb33
-rw-r--r--app/models/project_import_data.rb14
-rw-r--r--app/models/project_services/bamboo_service.rb20
-rw-r--r--app/models/project_services/builds_email_service.rb10
-rw-r--r--app/models/project_services/teamcity_service.rb33
-rw-r--r--app/models/repository.rb11
-rw-r--r--app/services/issues/base_service.rb2
-rw-r--r--app/services/merge_requests/base_service.rb3
-rw-r--r--app/services/merge_requests/build_service.rb2
-rw-r--r--app/services/projects/housekeeping_service.rb14
-rw-r--r--app/services/projects/participants_service.rb43
-rw-r--r--app/services/projects/transfer_service.rb5
-rw-r--r--app/services/system_note_service.rb2
-rw-r--r--app/views/admin/application_settings/_form.html.haml19
-rw-r--r--app/views/admin/logs/show.html.haml3
-rw-r--r--app/views/admin/projects/index.html.haml10
-rw-r--r--app/views/admin/projects/show.html.haml36
-rw-r--r--app/views/doorkeeper/applications/index.html.haml2
-rw-r--r--app/views/groups/milestones/new.html.haml4
-rw-r--r--app/views/help/ui.html.haml12
-rw-r--r--app/views/layouts/header/_default.html.haml2
-rw-r--r--app/views/projects/_builds_settings.html.haml3
-rw-r--r--app/views/projects/_md_preview.html.haml2
-rw-r--r--app/views/projects/_zen.html.haml4
-rw-r--r--app/views/projects/blob/diff.html.haml12
-rw-r--r--app/views/projects/commits/_commit.html.haml2
-rw-r--r--app/views/projects/diffs/_line.html.haml20
-rw-r--r--app/views/projects/diffs/_parallel_view.html.haml8
-rw-r--r--app/views/projects/edit.html.haml1
-rw-r--r--app/views/projects/issues/_form.html.haml2
-rw-r--r--app/views/projects/issues/_issue.html.haml2
-rw-r--r--app/views/projects/issues/show.html.haml127
-rw-r--r--app/views/projects/labels/_label.html.haml2
-rw-r--r--app/views/projects/merge_requests/_form.html.haml2
-rw-r--r--app/views/projects/merge_requests/_merge_request.html.haml2
-rw-r--r--app/views/projects/merge_requests/_new_compare.html.haml32
-rw-r--r--app/views/projects/merge_requests/_new_submit.html.haml2
-rw-r--r--app/views/projects/merge_requests/_show.html.haml8
-rw-r--r--app/views/projects/merge_requests/dropdowns/_branch.html.haml5
-rw-r--r--app/views/projects/merge_requests/dropdowns/_project.html.haml5
-rw-r--r--app/views/projects/merge_requests/show/_mr_title.html.haml59
-rw-r--r--app/views/projects/merge_requests/update_branches.html.haml8
-rw-r--r--app/views/projects/milestones/_form.html.haml5
-rw-r--r--app/views/projects/notes/_edit_form.html.haml2
-rw-r--r--app/views/projects/notes/_form.html.haml4
-rw-r--r--app/views/projects/notes/discussions/_diff.html.haml8
-rw-r--r--app/views/projects/releases/edit.html.haml12
-rw-r--r--app/views/projects/tags/new.html.haml6
-rw-r--r--app/views/projects/wikis/_form.html.haml4
-rw-r--r--app/views/repository_check_mailer/notify.html.haml5
-rw-r--r--app/views/repository_check_mailer/notify.text.haml3
-rw-r--r--app/views/search/results/_note.html.haml2
-rw-r--r--app/views/shared/issuable/_form.html.haml17
-rw-r--r--app/views/shared/issuable/_label_dropdown.html.haml2
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml6
-rw-r--r--app/views/shared/milestones/_labels_tab.html.haml13
-rw-r--r--app/workers/admin_email_worker.rb12
-rw-r--r--app/workers/post_receive.rb2
-rw-r--r--app/workers/repository_check/batch_worker.rb63
-rw-r--r--app/workers/repository_check/clear_worker.rb17
-rw-r--r--app/workers/repository_check/single_repository_worker.rb36
113 files changed, 1046 insertions, 528 deletions
diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee
index b05138ac1ac..6f435e4c542 100644
--- a/app/assets/javascripts/application.js.coffee
+++ b/app/assets/javascripts/application.js.coffee
@@ -22,7 +22,17 @@
#= require cal-heatmap
#= require turbolinks
#= require autosave
-#= require bootstrap
+#= require bootstrap/affix
+#= require bootstrap/alert
+#= require bootstrap/button
+#= require bootstrap/collapse
+#= require bootstrap/dropdown
+#= require bootstrap/modal
+#= require bootstrap/scrollspy
+#= require bootstrap/tab
+#= require bootstrap/transition
+#= require bootstrap/tooltip
+#= require bootstrap/popover
#= require select2
#= require raphael
#= require g.raphael
diff --git a/app/assets/javascripts/behaviors/quick_submit.js.coffee b/app/assets/javascripts/behaviors/quick_submit.js.coffee
index 6e29d374267..3cb96bacaa7 100644
--- a/app/assets/javascripts/behaviors/quick_submit.js.coffee
+++ b/app/assets/javascripts/behaviors/quick_submit.js.coffee
@@ -29,7 +29,11 @@ $(document).on 'keydown.quick_submit', '.js-quick-submit', (e) ->
e.preventDefault()
$form = $(e.target).closest('form')
- $form.find('input[type=submit], button[type=submit]').disable()
+ $submit_button = $form.find('input[type=submit], button[type=submit]')
+
+ return if $submit_button.attr('disabled')
+
+ $submit_button.disable()
$form.submit()
# If the user tabs to a submit button on a `js-quick-submit` form, display a
diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee
index 70fd6f50e9c..0b9110d35fa 100644
--- a/app/assets/javascripts/dispatcher.js.coffee
+++ b/app/assets/javascripts/dispatcher.js.coffee
@@ -28,26 +28,26 @@ class Dispatcher
new Todos()
when 'projects:milestones:new', 'projects:milestones:edit'
new ZenMode()
- new DropzoneInput($('.milestone-form'))
+ new GLForm($('.milestone-form'))
when 'groups:milestones:new'
new ZenMode()
when 'projects:compare:show'
new Diff()
when 'projects:issues:new','projects:issues:edit'
shortcut_handler = new ShortcutsNavigation()
- new DropzoneInput($('.issue-form'))
+ new GLForm($('.issue-form'))
new IssuableForm($('.issue-form'))
when 'projects:merge_requests:new', 'projects:merge_requests:edit'
new Diff()
shortcut_handler = new ShortcutsNavigation()
- new DropzoneInput($('.merge-request-form'))
+ new GLForm($('.merge-request-form'))
new IssuableForm($('.merge-request-form'))
when 'projects:tags:new'
new ZenMode()
- new DropzoneInput($('.tag-form'))
+ new GLForm($('.tag-form'))
when 'projects:releases:edit'
new ZenMode()
- new DropzoneInput($('.release-form'))
+ new GLForm($('.release-form'))
when 'projects:merge_requests:show'
new Diff()
shortcut_handler = new ShortcutsIssuable(true)
@@ -137,7 +137,7 @@ class Dispatcher
new Wikis()
shortcut_handler = new ShortcutsNavigation()
new ZenMode()
- new DropzoneInput($('.wiki-form'))
+ new GLForm($('.wiki-form'))
when 'snippets'
shortcut_handler = new ShortcutsNavigation()
new ZenMode() if path[2] == 'show'
diff --git a/app/assets/javascripts/dropzone_input.js.coffee b/app/assets/javascripts/dropzone_input.js.coffee
index b502131a99d..6eb8d27ee2b 100644
--- a/app/assets/javascripts/dropzone_input.js.coffee
+++ b/app/assets/javascripts/dropzone_input.js.coffee
@@ -15,11 +15,13 @@ class @DropzoneInput
project_uploads_path = window.project_uploads_path or null
max_file_size = gon.max_file_size or 10
- form_textarea = $(form).find("textarea.markdown-area")
+ form_textarea = $(form).find(".js-gfm-input")
form_textarea.wrap "<div class=\"div-dropzone\"></div>"
form_textarea.on 'paste', (event) =>
handlePaste(event)
+ $mdArea = $(form_textarea).closest('.md-area')
+
$(form).setupMarkdownPreview()
form_dropzone = $(form).find('.div-dropzone')
@@ -49,17 +51,16 @@ class @DropzoneInput
$(".div-dropzone-alert").alert "close"
dragover: ->
- form_textarea.addClass "div-dropzone-focus"
+ $mdArea.addClass 'is-dropzone-hover'
form.find(".div-dropzone-hover").css "opacity", 0.7
return
dragleave: ->
- form_textarea.removeClass "div-dropzone-focus"
+ $mdArea.removeClass 'is-dropzone-hover'
form.find(".div-dropzone-hover").css "opacity", 0
return
drop: ->
- form_textarea.removeClass "div-dropzone-focus"
form.find(".div-dropzone-hover").css "opacity", 0
form_textarea.focus()
return
diff --git a/app/assets/javascripts/gfm_auto_complete.js.coffee b/app/assets/javascripts/gfm_auto_complete.js.coffee
index 4718bcf7a1e..61e3f811e73 100644
--- a/app/assets/javascripts/gfm_auto_complete.js.coffee
+++ b/app/assets/javascripts/gfm_auto_complete.js.coffee
@@ -2,6 +2,8 @@
window.GitLab ?= {}
GitLab.GfmAutoComplete =
+ dataLoading: false
+
dataSource: ''
# Emoji
@@ -17,17 +19,41 @@ GitLab.GfmAutoComplete =
template: '<li><small>${id}</small> ${title}</li>'
# Add GFM auto-completion to all input fields, that accept GFM input.
- setup: ->
- input = $('.js-gfm-input')
+ setup: (wrap) ->
+ @input = $('.js-gfm-input')
+
+ # destroy previous instances
+ @destroyAtWho()
+
+ # set up instances
+ @setupAtWho()
+
+ if @dataSource
+ if !@dataLoading
+ @dataLoading = true
+ # We should wait until initializations are done
+ # and only trigger the last .setup since
+ # The previous .dataSource belongs to the previous issuable
+ # and the last one will have the **proper** .dataSource property
+ # TODO: Make this a singleton and turn off events when moving to another page
+ setTimeout( =>
+ fetch = @fetchData(@dataSource)
+ fetch.done (data) =>
+ @dataLoading = false
+ @loadData(data)
+ , 1000)
+
+
+ setupAtWho: ->
# Emoji
- input.atwho
+ @input.atwho
at: ':'
displayTpl: @Emoji.template
insertTpl: ':${name}:'
# Team Members
- input.atwho
+ @input.atwho
at: '@'
displayTpl: @Members.template
insertTpl: '${atwho-at}${username}'
@@ -42,7 +68,7 @@ GitLab.GfmAutoComplete =
title: sanitize(title)
search: sanitize("#{m.username} #{m.name}")
- input.atwho
+ @input.atwho
at: '#'
alias: 'issues'
searchKey: 'search'
@@ -55,7 +81,7 @@ GitLab.GfmAutoComplete =
title: sanitize(i.title)
search: "#{i.iid} #{i.title}"
- input.atwho
+ @input.atwho
at: '!'
alias: 'mergerequests'
searchKey: 'search'
@@ -68,13 +94,18 @@ GitLab.GfmAutoComplete =
title: sanitize(m.title)
search: "#{m.iid} #{m.title}"
- if @dataSource
- $.getJSON(@dataSource).done (data) ->
- # load members
- input.atwho 'load', '@', data.members
- # load issues
- input.atwho 'load', 'issues', data.issues
- # load merge requests
- input.atwho 'load', 'mergerequests', data.mergerequests
- # load emojis
- input.atwho 'load', ':', data.emojis
+ destroyAtWho: ->
+ @input.atwho('destroy')
+
+ fetchData: (dataSource) ->
+ $.getJSON(dataSource)
+
+ loadData: (data) ->
+ # load members
+ @input.atwho 'load', '@', data.members
+ # load issues
+ @input.atwho 'load', 'issues', data.issues
+ # load merge requests
+ @input.atwho 'load', 'mergerequests', data.mergerequests
+ # load emojis
+ @input.atwho 'load', ':', data.emojis
diff --git a/app/assets/javascripts/gl_form.js.coffee b/app/assets/javascripts/gl_form.js.coffee
new file mode 100644
index 00000000000..d540cc4dc46
--- /dev/null
+++ b/app/assets/javascripts/gl_form.js.coffee
@@ -0,0 +1,51 @@
+class @GLForm
+ constructor: (@form) ->
+ @textarea = @form.find('textarea.js-gfm-input')
+
+ # Before we start, we should clean up any previous data for this form
+ @destroy()
+
+ # Setup the form
+ @setupForm()
+
+ @form.data 'gl-form', @
+
+ destroy: ->
+ # Clean form listeners
+ @clearEventListeners()
+ @form.data 'gl-form', null
+
+ setupForm: ->
+ isNewForm = @form.is(':not(.gfm-form)')
+
+ @form.removeClass 'js-new-note-form'
+
+ if isNewForm
+ @form.find('.div-dropzone').remove()
+ @form.addClass('gfm-form')
+ disableButtonIfEmptyField @form.find('.js-note-text'), @form.find('.js-comment-button')
+
+ # remove notify commit author checkbox for non-commit notes
+ GitLab.GfmAutoComplete.setup()
+ new DropzoneInput(@form)
+
+ autosize(@textarea)
+
+ # form and textarea event listeners
+ @addEventListeners()
+
+ # hide discard button
+ @form.find('.js-note-discard').hide()
+
+ @form.show()
+
+ clearEventListeners: ->
+ @textarea.off 'focus'
+ @textarea.off 'blur'
+
+ addEventListeners: ->
+ @textarea.on 'focus', ->
+ $(@).closest('.md-area').addClass 'is-focused'
+
+ @textarea.on 'blur', ->
+ $(@).closest('.md-area').removeClass 'is-focused'
diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee
index 946d83b7bdd..c7d74a12f99 100644
--- a/app/assets/javascripts/issue.js.coffee
+++ b/app/assets/javascripts/issue.js.coffee
@@ -10,6 +10,9 @@ class @Issue
@initTaskList()
@initIssueBtnEventListeners()
+ @initMergeRequests()
+ @initRelatedBranches()
+
initTaskList: ->
$('.detail-page-description .js-task-list-container').taskList('enable')
$(document).on 'tasklist:changed', '.detail-page-description .js-task-list-container', @updateTaskList
@@ -69,3 +72,23 @@ class @Issue
type: 'PATCH'
url: $('form.js-issuable-update').attr('action')
data: patchData
+
+ initMergeRequests: ->
+ $container = $('#merge-requests')
+
+ $.getJSON($container.data('url'))
+ .error ->
+ new Flash('Failed to load referenced merge requests', 'alert')
+ .success (data) ->
+ if 'html' of data
+ $container.html(data.html)
+
+ initRelatedBranches: ->
+ $container = $('#related-branches')
+
+ $.getJSON($container.data('url'))
+ .error ->
+ new Flash('Failed to load related branches', 'alert')
+ .success (data) ->
+ if 'html' of data
+ $container.html(data.html)
diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee
index d1fe116397a..bc80980acb7 100644
--- a/app/assets/javascripts/labels_select.js.coffee
+++ b/app/assets/javascripts/labels_select.js.coffee
@@ -34,7 +34,7 @@ class @LabelsSelect
labelHTMLTemplate = _.template(
'<% _.each(labels, function(label){ %>
<a href="<%= ["",issueURLSplit[1], issueURLSplit[2],""].join("/") %>issues?label_name=<%= label.title %>">
- <span class="label color-label" style="background-color: <%= label.color %>;">
+ <span class="label has-tooltip color-label" title="<%= label.description %>" style="background-color: <%= label.color %>;">
<%= label.title %>
</span>
</a>
@@ -165,6 +165,8 @@ class @LabelsSelect
.html(template)
$sidebarCollapsedValue.text(labelCount)
+ $('.has-tooltip', $value).tooltip(container: 'body')
+
$value
.find('a')
.each((i) ->
@@ -218,7 +220,7 @@ class @LabelsSelect
selectable: true
toggleLabel: (selected) ->
- if selected and selected.title isnt 'Any Label'
+ if selected and selected.title?
selected.title
else
defaultLabel
diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee
index ef0b534a709..1ab6e5114bc 100644
--- a/app/assets/javascripts/merge_request_tabs.js.coffee
+++ b/app/assets/javascripts/merge_request_tabs.js.coffee
@@ -85,8 +85,10 @@ class @MergeRequestTabs
scrollToElement: (container) ->
if window.location.hash
- $el = $("div#{container} #{window.location.hash}")
- $('body').scrollTo($el.offset().top) if $el.length
+ navBarHeight = $('.navbar-gitlab').outerHeight()
+
+ $el = $("#{container} #{window.location.hash}")
+ $.scrollTo("#{container} #{window.location.hash}", offset: -navBarHeight) if $el.length
# Activate a tab based on the current action
activateTab: (action) ->
@@ -152,12 +154,38 @@ class @MergeRequestTabs
@_get
url: "#{source}.json" + @_location.search
success: (data) =>
- document.querySelector("div#diffs").innerHTML = data.html
+ $('#diffs').html data.html
gl.utils.localTimeAgo($('.js-timeago', 'div#diffs'))
- $('div#diffs .js-syntax-highlight').syntaxHighlight()
+ $('#diffs .js-syntax-highlight').syntaxHighlight()
@expandViewContainer() if @diffViewType() is 'parallel'
@diffsLoaded = true
@scrollToElement("#diffs")
+ @highlighSelectedLine()
+
+ $(document)
+ .off 'click', '.diff-line-num a'
+ .on 'click', '.diff-line-num a', (e) =>
+ e.preventDefault()
+ window.location.hash = $(e.currentTarget).attr 'href'
+ @highlighSelectedLine()
+ @scrollToElement("#diffs")
+
+ highlighSelectedLine: ->
+ $('.hll').removeClass 'hll'
+ locationHash = window.location.hash
+
+ if locationHash isnt ''
+ hashClassString = ".#{locationHash.replace('#', '')}"
+ $diffLine = $(locationHash)
+
+ if $diffLine.is ':not(tr)'
+ $diffLine = $("td#{locationHash}, td#{hashClassString}")
+ else
+ $diffLine = $('td', $diffLine)
+
+ $diffLine.addClass 'hll'
+ diffLineTop = $diffLine.offset().top
+ navBarHeight = $('.navbar-gitlab').outerHeight()
loadBuilds: (source) ->
return if @buildsLoaded
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
index a67890200dd..fa91baa07c0 100644
--- a/app/assets/javascripts/notes.js.coffee
+++ b/app/assets/javascripts/notes.js.coffee
@@ -283,32 +283,10 @@ class @Notes
show the form
###
setupNoteForm: (form) ->
- disableButtonIfEmptyField form.find(".js-note-text"), form.find(".js-comment-button")
- form.removeClass "js-new-note-form"
- form.find('.div-dropzone').remove()
-
- # hide discard button
- form.find('.js-note-discard').hide()
-
- # setup preview buttons
- previewButton = form.find(".js-md-preview-button")
+ new GLForm form
textarea = form.find(".js-note-text")
- textarea.on "input", ->
- if $(this).val().trim() isnt ""
- previewButton.removeClass("turn-off").addClass "turn-on"
- else
- previewButton.removeClass("turn-on").addClass "turn-off"
-
- textarea.on 'focus', ->
- $(this).closest('.md-area').addClass 'is-focused'
-
- textarea.on 'blur', ->
- $(this).closest('.md-area').removeClass 'is-focused'
-
- autosize(textarea)
-
new Autosave textarea, [
"Note"
form.find("#note_commit_id").val()
@@ -317,11 +295,6 @@ class @Notes
form.find("#note_noteable_id").val()
]
- # remove notify commit author checkbox for non-commit notes
- GitLab.GfmAutoComplete.setup()
- new DropzoneInput(form)
- form.show()
-
###
Called in response to the new note form being submitted
@@ -375,34 +348,15 @@ class @Notes
note = $(this).closest(".note")
note.addClass "is-editting"
form = note.find(".note-edit-form")
- isNewForm = form.is(':not(.gfm-form)')
- if isNewForm
- form.addClass('gfm-form')
+
form.addClass('current-note-edit-form')
# Show the attachment delete link
note.find(".js-note-attachment-delete").show()
- # Setup markdown form
- if isNewForm
- GitLab.GfmAutoComplete.setup()
- new DropzoneInput(form)
-
- textarea = form.find("textarea")
- textarea.focus()
-
- if isNewForm
- autosize(textarea)
-
- # HACK (rspeicher/DouweM): Work around a Chrome 43 bug(?).
- # The textarea has the correct value, Chrome just won't show it unless we
- # modify it, so let's clear it and re-set it!
- value = textarea.val()
- textarea.val ""
- textarea.val value
+ new GLForm form
- if isNewForm
- disableButtonIfEmptyField textarea, form.find(".js-comment-button")
+ form.find(".js-note-text").focus()
###
Called in response to clicking the edit note link
@@ -559,6 +513,9 @@ class @Notes
removeDiscussionNoteForm: (form)->
row = form.closest("tr")
+ glForm = form.data 'gl-form'
+ glForm.destroy()
+
form.find(".js-note-text").data("autosave").reset()
# show the reply button (will only work for replies)
@@ -570,7 +527,6 @@ class @Notes
# only remove the form
form.remove()
-
cancelDiscussionForm: (e) =>
e.preventDefault()
form = $(e.target).closest(".js-discussion-note-form")
diff --git a/app/assets/javascripts/subscription.js.coffee b/app/assets/javascripts/subscription.js.coffee
index e4b7a3172ec..1a430f3aa47 100644
--- a/app/assets/javascripts/subscription.js.coffee
+++ b/app/assets/javascripts/subscription.js.coffee
@@ -2,7 +2,7 @@ class @Subscription
constructor: (container) ->
$container = $(container)
@url = $container.attr('data-url')
- @subscribe_button = $container.find('.subscribe-button')
+ @subscribe_button = $container.find('.js-subscribe-button')
@subscription_status = $container.find('.subscription-status')
@subscribe_button.unbind('click').click(@toggleSubscription)
diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee
index eee9b6e690e..a7e934936e9 100644
--- a/app/assets/javascripts/users_select.js.coffee
+++ b/app/assets/javascripts/users_select.js.coffee
@@ -12,6 +12,7 @@ class @UsersSelect
showNullUser = $dropdown.data('null-user')
showAnyUser = $dropdown.data('any-user')
firstUser = $dropdown.data('first-user')
+ @authorId = $dropdown.data('author-id')
selectedId = $dropdown.data('selected')
defaultLabel = $dropdown.data('default-label')
issueURL = $dropdown.data('issueUpdate')
@@ -207,6 +208,7 @@ class @UsersSelect
@projectId = $(select).data('project-id')
@groupId = $(select).data('group-id')
@showCurrentUser = $(select).data('current-user')
+ @authorId = $(select).data('author-id')
showNullUser = $(select).data('null-user')
showAnyUser = $(select).data('any-user')
showEmailUser = $(select).data('email-user')
@@ -312,6 +314,7 @@ class @UsersSelect
project_id: @projectId
group_id: @groupId
current_user: @showCurrentUser
+ author_id: @authorId
dataType: "json"
).done (users) ->
callback(users)
diff --git a/app/assets/stylesheets/framework/gfm.scss b/app/assets/stylesheets/framework/gfm.scss
index 5ae0520fd7b..f4d35c4b4b1 100644
--- a/app/assets/stylesheets/framework/gfm.scss
+++ b/app/assets/stylesheets/framework/gfm.scss
@@ -1,24 +1,6 @@
/**
* Styles that apply to all GFM related forms.
*/
-.issue-form, .merge-request-form, .wiki-form {
- .description {
- height: 16em;
- border-top-left-radius: 0;
- }
-}
-
-.wiki-form {
- .description {
- height: 26em;
- }
-}
-
-.milestone-form {
- .description {
- height: 14em;
- }
-}
.gfm-commit, .gfm-commit_range {
font-family: $monospace_font;
diff --git a/app/assets/stylesheets/framework/issue_box.scss b/app/assets/stylesheets/framework/issue_box.scss
index 7f7b7c806e7..8bfc0d583c5 100644
--- a/app/assets/stylesheets/framework/issue_box.scss
+++ b/app/assets/stylesheets/framework/issue_box.scss
@@ -5,7 +5,7 @@
*/
.status-box {
-
+
/* Extra small devices (phones, less than 768px) */
/* No media query since this is the default in Bootstrap */
padding: 5px 11px;
diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss
index c8f86d60e3b..0f32d36d59c 100644
--- a/app/assets/stylesheets/framework/markdown_area.scss
+++ b/app/assets/stylesheets/framework/markdown_area.scss
@@ -1,22 +1,15 @@
.div-dropzone-wrapper {
.div-dropzone {
position: relative;
- margin-bottom: -5px;
-
- .div-dropzone-focus {
- border-color: #66afe9 !important;
- box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6) !important;
- outline: 0 !important;
- }
.div-dropzone-hover {
position: absolute;
top: 50%;
left: 50%;
- margin-top: -0.5em;
- margin-left: -0.6em;
+ margin-top: -11.5px;
+ margin-left: -15px;
opacity: 0;
- font-size: 50px;
+ font-size: 30px;
transition: opacity 200ms ease-in-out;
pointer-events: none;
}
diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss
index 66180f38a4f..7eb451c124e 100644
--- a/app/assets/stylesheets/framework/mobile.scss
+++ b/app/assets/stylesheets/framework/mobile.scss
@@ -70,13 +70,6 @@
display: none;
}
- .issue-details {
- .creator,
- .page-title .btn-close {
- display: none;
- }
- }
-
%ul.notes .note-role, .note-actions {
display: none;
}
diff --git a/app/assets/stylesheets/framework/tw_bootstrap.scss b/app/assets/stylesheets/framework/tw_bootstrap.scss
index dd42db1840f..96bab7880c2 100644
--- a/app/assets/stylesheets/framework/tw_bootstrap.scss
+++ b/app/assets/stylesheets/framework/tw_bootstrap.scss
@@ -43,7 +43,6 @@
@import "bootstrap/modals";
@import "bootstrap/tooltip";
@import "bootstrap/popovers";
-@import "bootstrap/carousel";
// Utility classes
.clearfix {
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index 7b2aada5a0d..0a5b4b8834c 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -250,14 +250,6 @@ a > code {
* Textareas intended for GFM
*
*/
-.js-gfm-input {
- font-family: $monospace_font;
- color: $gl-text-color;
-}
-
-.md-preview {
-}
-
.strikethrough {
text-decoration: line-through;
}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 0b6be86ce6a..f910cf61817 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -150,6 +150,7 @@ $light-grey-header: #faf9f9;
*/
$gl-primary: $blue-normal;
$gl-success: $green-normal;
+$gl-success-focus: rgba($gl-success, .4);
$gl-info: $blue-normal;
$gl-warning: $orange-normal;
$gl-danger: $red-normal;
diff --git a/app/assets/stylesheets/highlight/dark.scss b/app/assets/stylesheets/highlight/dark.scss
index 47673944896..77a73dc379b 100644
--- a/app/assets/stylesheets/highlight/dark.scss
+++ b/app/assets/stylesheets/highlight/dark.scss
@@ -21,6 +21,12 @@
// Diff line
.line_holder {
+ td.diff-line-num.hll:not(.empty-cell),
+ td.line_content.hll:not(.empty-cell) {
+ background-color: #557;
+ border-color: darken(#557, 15%);
+ }
+
.diff-line-num.new, .line_content.new {
@include diff_background(rgba(51, 255, 51, 0.1), rgba(51, 255, 51, 0.2), #808080);
}
diff --git a/app/assets/stylesheets/highlight/monokai.scss b/app/assets/stylesheets/highlight/monokai.scss
index 806401c21ae..28253d4ccb4 100644
--- a/app/assets/stylesheets/highlight/monokai.scss
+++ b/app/assets/stylesheets/highlight/monokai.scss
@@ -21,6 +21,12 @@
// Diff line
.line_holder {
+ td.diff-line-num.hll:not(.empty-cell),
+ td.line_content.hll:not(.empty-cell) {
+ background-color: #49483e;
+ border-color: darken(#49483e, 15%);
+ }
+
.diff-line-num.new, .line_content.new {
@include diff_background(rgba(166, 226, 46, 0.1), rgba(166, 226, 46, 0.15), #808080);
}
diff --git a/app/assets/stylesheets/highlight/solarized_dark.scss b/app/assets/stylesheets/highlight/solarized_dark.scss
index 6a809d4dfd2..c62bd021aef 100644
--- a/app/assets/stylesheets/highlight/solarized_dark.scss
+++ b/app/assets/stylesheets/highlight/solarized_dark.scss
@@ -21,6 +21,12 @@
// Diff line
.line_holder {
+ td.diff-line-num.hll:not(.empty-cell),
+ td.line_content.hll:not(.empty-cell) {
+ background-color: #174652;
+ border-color: darken(#174652, 15%);
+ }
+
.diff-line-num.new, .line_content.new {
@include diff_background(rgba(133, 153, 0, 0.15), rgba(133, 153, 0, 0.25), #113b46);
}
diff --git a/app/assets/stylesheets/highlight/solarized_light.scss b/app/assets/stylesheets/highlight/solarized_light.scss
index c482a1258f7..524cfaf90c3 100644
--- a/app/assets/stylesheets/highlight/solarized_light.scss
+++ b/app/assets/stylesheets/highlight/solarized_light.scss
@@ -21,6 +21,12 @@
// Diff line
.line_holder {
+ td.diff-line-num.hll:not(.empty-cell),
+ td.line_content.hll:not(.empty-cell) {
+ background-color: #ddd8c5;
+ border-color: darken(#ddd8c5, 15%);
+ }
+
.diff-line-num.new, .line_content.new {
@include diff_background(rgba(133, 153, 0, 0.2), rgba(133, 153, 0, 0.25), #c5d0d4);
}
diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss
index 28331f59754..1ff6ad75e07 100644
--- a/app/assets/stylesheets/highlight/white.scss
+++ b/app/assets/stylesheets/highlight/white.scss
@@ -21,6 +21,12 @@
// Diff line
.line_holder {
+ td.diff-line-num.hll:not(.empty-cell),
+ td.line_content.hll:not(.empty-cell) {
+ background-color: #f8eec7;
+ border-color: darken(#f8eec7, 15%);
+ }
+
.diff-line-num {
&.old {
background-color: $line-number-old;
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index 6453c91d955..c8c6bbde084 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -75,6 +75,11 @@ li.commit {
}
}
+ .item-title {
+ display: inline-block;
+ max-width: 70%;
+ }
+
.commit-row-description {
font-size: 14px;
border-left: 1px solid #eee;
diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss
index 5917f089720..2c2ac903f29 100644
--- a/app/assets/stylesheets/pages/detail_page.scss
+++ b/app/assets/stylesheets/pages/detail_page.scss
@@ -1,5 +1,5 @@
.detail-page-header {
- padding: 11px 0;
+ padding: $gl-padding-top 0;
border-bottom: 1px solid $border-color;
color: #5c5d5e;
font-size: 16px;
@@ -16,11 +16,6 @@
.issue_created_ago, .author_link {
white-space: nowrap;
}
-
- .issue-meta {
- display: inline-block;
- line-height: 20px;
- }
}
.detail-page-description {
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index d0855f66911..183f22a1b24 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -67,6 +67,24 @@
line-height: $code_line_height;
font-size: $code_font_size;
+ &.noteable_line {
+ position: relative;
+
+ &.old {
+ &:before {
+ content: '-';
+ position: absolute;
+ }
+ }
+
+ &.new {
+ &:before {
+ content: '+';
+ position: absolute;
+ }
+ }
+ }
+
span {
white-space: pre;
}
@@ -391,3 +409,23 @@
margin-bottom: 0;
}
}
+
+.file-holder {
+ .diff-line-num:not(.js-unfold-bottom) {
+ a {
+ &:before {
+ content: attr(data-linenumber);
+ }
+ }
+ }
+}
+
+.discussion {
+ .diff-content {
+ .diff-line-num {
+ &:before {
+ content: attr(data-linenumber);
+ }
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 8b6f37f21b5..5bf44c1cdb6 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -173,12 +173,6 @@
}
}
- .subscribe-button {
- span {
- margin-top: 0;
- }
- }
-
&.right-sidebar-collapsed {
/* Extra small devices (phones, less than 768px) */
display: none;
@@ -279,10 +273,6 @@
}
}
-.btn-default.gutter-toggle {
- margin-top: 4px;
-}
-
.detail-page-description {
small {
color: $gray-darkest;
@@ -322,3 +312,56 @@
color: #8c8c8c;
}
}
+
+.issuable-form-padding-top {
+ @media (min-width: $screen-sm-min) {
+ padding-top: 7px;
+ }
+}
+
+.issuable-status-box {
+ float: none;
+ display: inline-block;
+ margin-top: 0;
+
+ @media (max-width: $screen-xs-max) {
+ position: absolute;
+ top: 0;
+ left: 0;
+ }
+}
+
+.issuable-header {
+ position: relative;
+ padding-left: 45px;
+ padding-right: 45px;
+ line-height: 35px;
+
+ @media (min-width: $screen-sm-min) {
+ float: left;
+ padding-left: 0;
+ padding-right: 0;
+ }
+}
+
+.issuable-actions {
+ padding-top: 10px;
+
+ @media (min-width: $screen-sm-min) {
+ float: right;
+ padding-top: 0;
+ }
+}
+
+.issuable-gutter-toggle {
+ @media (max-width: $screen-sm-max) {
+ position: absolute;
+ top: 0;
+ right: 0;
+ }
+}
+
+.issuable-meta {
+ display: inline-block;
+ line-height: 18px;
+}
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index 6a1d28590c2..fc9db97132d 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -86,41 +86,9 @@ form.edit-issue {
@media (max-width: $screen-xs-max) {
.issue-btn-group {
width: 100%;
- margin-top: 5px;
-
- .btn-group {
- width: 100%;
-
- ul {
- width: 100%;
- text-align: center;
- }
- }
.btn {
width: 100%;
-
- &:first-child:not(:last-child) {
-
- }
-
- &:not(:first-child):not(:last-child) {
- margin-top: 10px;
- }
-
- &:last-child:not(:first-child) {
- margin-top: 10px;
- }
- }
- }
-
- .issue {
- &:hover .issue-actions {
- display: none !important;
- }
-
- .issue-updated-at {
- display: none;
}
}
}
@@ -133,11 +101,3 @@ form.edit-issue {
color: $gl-text-color;
margin-left: 52px;
}
-
-.editor-details {
- display: block;
-
- @media (min-width: $screen-sm-min) {
- display: inline-block;
- }
-}
diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss
index 3e0a3140be7..e179bdf0048 100644
--- a/app/assets/stylesheets/pages/labels.scss
+++ b/app/assets/stylesheets/pages/labels.scss
@@ -79,19 +79,30 @@
color: $white-light;
}
+@mixin labels-mobile {
+ @media (max-width: $screen-xs-min) {
+ display: block;
+ width: 100%;
+ margin-left: 0;
+ padding: 10px 0;
+ }
+}
+
+
.manage-labels-list {
- .prepend-left-10 {
+ .prepend-left-10, .prepend-description-left {
display: inline-block;
width: 40%;
vertical-align: middle;
- @media (max-width: $screen-xs-min) {
- display: block;
- width: 100%;
- margin-left: 0;
- padding: 10px 0;
- }
+ @include labels-mobile;
+ }
+
+ .prepend-description-left {
+ width: 57%;
+
+ @include labels-mobile;
}
.pull-info-right {
@@ -106,7 +117,7 @@
padding: 6px;
color: $gl-text-color;
- &.subscribe-button {
+ &.label-subscribe-button {
padding-left: 0;
}
}
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index f4da17fadaa..07c707e7b77 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -40,6 +40,7 @@
}
.note-textarea {
+ display: block;
padding: 10px 0;
font-family: $regular_font;
border: 0;
@@ -63,7 +64,7 @@
&.is-focused {
border-color: $focus-border-color;
- box-shadow: 0 0 2px rgba(#000, .2),
+ box-shadow: 0 0 2px $black-transparent,
0 0 4px rgba($focus-border-color, .4);
.comment-toolbar,
@@ -72,6 +73,17 @@
}
}
+ &.is-dropzone-hover {
+ border-color: $gl-success;
+ box-shadow: 0 0 2px $black-transparent,
+ 0 0 4px $gl-success-focus;
+
+ .comment-toolbar,
+ .nav-links {
+ border-color: $gl-success;
+ }
+ }
+
p {
code {
white-space: normal;
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index e421a31549a..ce44f5aa13b 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -276,8 +276,7 @@ ul.notes {
.diff-file tr.line_holder {
@mixin show-add-diff-note {
- filter: alpha(opacity=100);
- opacity: 1.0;
+ display: inline-block;
}
.add-diff-note {
@@ -291,13 +290,8 @@ ul.notes {
position: absolute;
z-index: 10;
width: 32px;
-
- transition: all 0.2s ease;
-
// "hide" it by default
- opacity: 0.0;
- filter: alpha(opacity=0);
-
+ display: none;
&:hover {
background: $gl-info;
color: #fff;
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index f010436bd36..b4a28b8dd3f 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -19,6 +19,15 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
redirect_to admin_runners_path
end
+ def clear_repository_check_states
+ RepositoryCheck::ClearWorker.perform_async
+
+ redirect_to(
+ admin_application_settings_path,
+ notice: 'Started asynchronous removal of all repository check states.'
+ )
+ end
+
private
def set_application_setting
@@ -82,6 +91,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:akismet_enabled,
:akismet_api_key,
:email_author_in_body,
+ :repository_checks_enabled,
restricted_visibility_levels: [],
import_sources: []
)
diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb
index c6b3105544a..87986fdf8b1 100644
--- a/app/controllers/admin/projects_controller.rb
+++ b/app/controllers/admin/projects_controller.rb
@@ -1,5 +1,5 @@
class Admin::ProjectsController < Admin::ApplicationController
- before_action :project, only: [:show, :transfer]
+ before_action :project, only: [:show, :transfer, :repository_check]
before_action :group, only: [:show, :transfer]
def index
@@ -8,6 +8,7 @@ class Admin::ProjectsController < Admin::ApplicationController
@projects = @projects.where("projects.visibility_level IN (?)", params[:visibility_levels]) if params[:visibility_levels].present?
@projects = @projects.with_push if params[:with_push].present?
@projects = @projects.abandoned if params[:abandoned].present?
+ @projects = @projects.where(last_repository_check_failed: true) if params[:last_repository_check_failed].present?
@projects = @projects.non_archived unless params[:with_archived].present?
@projects = @projects.search(params[:name]) if params[:name].present?
@projects = @projects.sort(@sort = params[:sort])
@@ -30,6 +31,15 @@ class Admin::ProjectsController < Admin::ApplicationController
redirect_to admin_namespace_project_path(@project.namespace, @project)
end
+ def repository_check
+ RepositoryCheck::SingleRepositoryWorker.perform_async(@project.id)
+
+ redirect_to(
+ admin_namespace_project_path(@project.namespace, @project),
+ notice: 'Repository check was triggered.'
+ )
+ end
+
protected
def project
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 97d53acde94..1c53b0b21a3 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -3,6 +3,7 @@ require 'fogbugz'
class ApplicationController < ActionController::Base
include Gitlab::CurrentSettings
+ include Gitlab::GonHelper
include GitlabRoutingHelper
include PageLayoutHelper
@@ -13,7 +14,7 @@ class ApplicationController < ActionController::Base
before_action :check_password_expiration
before_action :check_2fa_requirement
before_action :ldap_security_check
- before_action :sentry_user_context
+ before_action :sentry_context
before_action :default_headers
before_action :add_gon_variables
before_action :configure_permitted_parameters, if: :devise_controller?
@@ -40,13 +41,15 @@ class ApplicationController < ActionController::Base
protected
- def sentry_user_context
- if Rails.env.production? && current_application_settings.sentry_enabled && current_user
- Raven.user_context(
- id: current_user.id,
- email: current_user.email,
- username: current_user.username,
- )
+ def sentry_context
+ if Rails.env.production? && current_application_settings.sentry_enabled
+ if current_user
+ Raven.user_context(
+ id: current_user.id,
+ email: current_user.email,
+ username: current_user.username,
+ )
+ end
Raven.tags_context(program: sentry_program_context)
end
@@ -158,20 +161,6 @@ class ApplicationController < ActionController::Base
end
end
- def add_gon_variables
- gon.api_version = API::API.version
- gon.default_avatar_url = URI::join(Gitlab.config.gitlab.url, ActionController::Base.helpers.image_path('no_avatar.png')).to_s
- gon.default_issues_tracker = Project.new.default_issue_tracker.to_param
- gon.max_file_size = current_application_settings.max_attachment_size
- gon.relative_url_root = Gitlab.config.gitlab.relative_url_root
- gon.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class
-
- if current_user
- gon.current_user_id = current_user.id
- gon.api_token = current_user.private_token
- end
- end
-
def validate_user_service_ticket!
return unless signed_in? && session[:service_tickets]
diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb
index 81ba58ce49c..eb0abc80ab4 100644
--- a/app/controllers/autocomplete_controller.rb
+++ b/app/controllers/autocomplete_controller.rb
@@ -12,8 +12,15 @@ class AutocompleteController < ApplicationController
if params[:search].blank?
# Include current user if available to filter by "Me"
if params[:current_user] && current_user
- @users = [*@users, current_user].uniq
+ @users = [*@users, current_user]
end
+
+ if params[:author_id].present?
+ author = User.find_by_id(params[:author_id])
+ @users = [author, *@users] if author
+ end
+
+ @users.uniq!
end
render json: @users, only: [:name, :username, :id], methods: [:avatar_url]
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index c1adc999567..ee4fcc4e360 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -40,6 +40,7 @@ class GroupsController < Groups::ApplicationController
@last_push = current_user.recent_push if current_user
@projects = @projects.includes(:namespace)
+ @projects = @projects.sorted_by_activity
@projects = filter_projects(@projects)
@projects = @projects.sort(@sort = params[:sort])
@projects = @projects.page(params[:page]) if params[:filter_projects].blank?
diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb
index 55050615473..9b5c43b17e2 100644
--- a/app/controllers/help_controller.rb
+++ b/app/controllers/help_controller.rb
@@ -51,6 +51,7 @@ class HelpController < ApplicationController
end
def ui
+ @user = User.new(id: 0, name: 'John Doe', username: '@johndoe')
end
private
diff --git a/app/controllers/oauth/applications_controller.rb b/app/controllers/oauth/applications_controller.rb
index d1e4ac10f6c..c6bdd0602c1 100644
--- a/app/controllers/oauth/applications_controller.rb
+++ b/app/controllers/oauth/applications_controller.rb
@@ -1,9 +1,11 @@
class Oauth::ApplicationsController < Doorkeeper::ApplicationsController
include Gitlab::CurrentSettings
+ include Gitlab::GonHelper
include PageLayoutHelper
before_action :verify_user_oauth_applications_enabled
before_action :authenticate_user!
+ before_action :add_gon_variables
layout 'profile'
diff --git a/app/controllers/profiles/keys_controller.rb b/app/controllers/profiles/keys_controller.rb
index b88c080352b..a12549d6bcb 100644
--- a/app/controllers/profiles/keys_controller.rb
+++ b/app/controllers/profiles/keys_controller.rb
@@ -10,6 +10,11 @@ class Profiles::KeysController < Profiles::ApplicationController
@key = current_user.keys.find(params[:id])
end
+ # Back-compat: We need to support this URL since git-annex webapp points to it
+ def new
+ redirect_to profile_keys_path
+ end
+
def create
@key = current_user.keys.new(key_params)
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 6d649e72f84..38214f04793 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -3,7 +3,8 @@ class Projects::IssuesController < Projects::ApplicationController
include IssuableActions
before_action :module_enabled
- before_action :issue, only: [:edit, :update, :show]
+ before_action :issue,
+ only: [:edit, :update, :show, :referenced_merge_requests, :related_branches]
# Allow read any issue
before_action :authorize_read_issue!, only: [:show]
@@ -17,9 +18,6 @@ class Projects::IssuesController < Projects::ApplicationController
# Allow issues bulk update
before_action :authorize_admin_issues!, only: [:bulk_update]
- # Cross-reference merge requests
- before_action :closed_by_merge_requests, only: [:show]
-
respond_to :html
def index
@@ -62,11 +60,9 @@ class Projects::IssuesController < Projects::ApplicationController
end
def show
- @note = @project.notes.new(noteable: @issue)
- @notes = @issue.notes.nonawards.with_associations.fresh
+ @note = @project.notes.new(noteable: @issue)
+ @notes = @issue.notes.nonawards.with_associations.fresh
@noteable = @issue
- @merge_requests = @issue.referenced_merge_requests(current_user)
- @related_branches = @issue.related_branches - @merge_requests.map(&:source_branch)
respond_to do |format|
format.html
@@ -118,15 +114,39 @@ class Projects::IssuesController < Projects::ApplicationController
end
end
+ def referenced_merge_requests
+ @merge_requests = @issue.referenced_merge_requests(current_user)
+ @closed_by_merge_requests = @issue.closed_by_merge_requests(current_user)
+
+ respond_to do |format|
+ format.json do
+ render json: {
+ html: view_to_html_string('projects/issues/_merge_requests')
+ }
+ end
+ end
+ end
+
+ def related_branches
+ merge_requests = @issue.referenced_merge_requests(current_user)
+
+ @related_branches = @issue.related_branches -
+ merge_requests.map(&:source_branch)
+
+ respond_to do |format|
+ format.json do
+ render json: {
+ html: view_to_html_string('projects/issues/_related_branches')
+ }
+ end
+ end
+ end
+
def bulk_update
result = Issues::BulkUpdateService.new(project, current_user, bulk_update_params).execute
redirect_back_or_default(default: { action: 'index' }, options: { notice: "#{result[:count]} issues updated" })
end
- def closed_by_merge_requests
- @closed_by_merge_requests ||= @issue.closed_by_merge_requests(current_user)
- end
-
protected
def issue
diff --git a/app/controllers/projects/repositories_controller.rb b/app/controllers/projects/repositories_controller.rb
index 5c7614cfbaf..bb7a6b6a5ab 100644
--- a/app/controllers/projects/repositories_controller.rb
+++ b/app/controllers/projects/repositories_controller.rb
@@ -11,7 +11,6 @@ class Projects::RepositoriesController < Projects::ApplicationController
end
def archive
- RepositoryArchiveCacheWorker.perform_async
headers.store(*Gitlab::Workhorse.send_git_archive(@project, params[:ref], params[:format]))
head :ok
rescue => ex
diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb
index ff32e834499..6a3ec83b8c0 100644
--- a/app/helpers/diff_helper.rb
+++ b/app/helpers/diff_helper.rb
@@ -40,10 +40,11 @@ module DiffHelper
(unfold) ? 'unfold js-unfold' : ''
end
- def diff_line_content(line)
+ def diff_line_content(line, line_type = nil)
if line.blank?
" &nbsp;".html_safe
else
+ line[0] = ' ' if %w[new old].include?(line_type)
line
end
end
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index b14b8218d02..49b6b79ce35 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -55,6 +55,15 @@ module IssuablesHelper
h(milestone_title.presence || default_label)
end
+ def issuable_meta(issuable, project, text)
+ output = content_tag :strong, "#{text} #{issuable.to_reference}", class: "identifier"
+ output << " opened #{time_ago_with_tooltip(issuable.created_at)} by".html_safe
+ output << content_tag(:strong) do
+ author_output = link_to_member(project, issuable.author, size: 24, mobile_classes: "hidden-xs")
+ author_output << link_to_member(project, issuable.author, size: 24, by_username: true, avatar: false, mobile_classes: "hidden-sm hidden-md hidden-lg")
+ end
+ end
+
private
def sidebar_gutter_collapsed?
diff --git a/app/helpers/selects_helper.rb b/app/helpers/selects_helper.rb
index 05386d790ca..4fc6de59a8b 100644
--- a/app/helpers/selects_helper.rb
+++ b/app/helpers/selects_helper.rb
@@ -6,12 +6,13 @@ module SelectsHelper
value = opts[:selected] || ''
placeholder = opts[:placeholder] || 'Search for a user'
- null_user = opts[:null_user] || false
- any_user = opts[:any_user] || false
- email_user = opts[:email_user] || false
- first_user = opts[:first_user] && current_user ? current_user.username : false
- current_user = opts[:current_user] || false
- project = opts[:project] || @project
+ null_user = opts[:null_user] || false
+ any_user = opts[:any_user] || false
+ email_user = opts[:email_user] || false
+ first_user = opts[:first_user] && current_user ? current_user.username : false
+ current_user = opts[:current_user] || false
+ author_id = opts[:author_id] || ''
+ project = opts[:project] || @project
html = {
class: css_class,
@@ -21,7 +22,8 @@ module SelectsHelper
any_user: any_user,
email_user: email_user,
first_user: first_user,
- current_user: current_user
+ current_user: current_user,
+ author_id: author_id
}
}
diff --git a/app/mailers/repository_check_mailer.rb b/app/mailers/repository_check_mailer.rb
new file mode 100644
index 00000000000..2bff5b63cc4
--- /dev/null
+++ b/app/mailers/repository_check_mailer.rb
@@ -0,0 +1,14 @@
+class RepositoryCheckMailer < BaseMailer
+ def notify(failed_count)
+ if failed_count == 1
+ @message = "One project failed its last repository check"
+ else
+ @message = "#{failed_count} projects failed their last repository check"
+ end
+
+ mail(
+ to: User.admins.pluck(:email),
+ subject: @message
+ )
+ end
+end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 052cd874733..36f88154232 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -153,7 +153,8 @@ class ApplicationSetting < ActiveRecord::Base
require_two_factor_authentication: false,
two_factor_grace_period: 48,
recaptcha_enabled: false,
- akismet_enabled: false
+ akismet_enabled: false,
+ repository_checks_enabled: true,
)
end
diff --git a/app/models/commit.rb b/app/models/commit.rb
index d09876a07d9..d1f07ccd55c 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -150,13 +150,11 @@ class Commit
end
def hook_attrs(with_changed_files: false)
- path_with_namespace = project.path_with_namespace
-
data = {
id: id,
message: safe_message,
timestamp: committed_date.xmlschema,
- url: "#{Gitlab.config.gitlab.url}/#{path_with_namespace}/commit/#{id}",
+ url: Gitlab::UrlBuilder.build(self),
author: {
name: author_name,
email: author_email
diff --git a/app/models/external_issue.rb b/app/models/external_issue.rb
index b8585d4e577..b7894c99846 100644
--- a/app/models/external_issue.rb
+++ b/app/models/external_issue.rb
@@ -37,4 +37,10 @@ class ExternalIssue
def to_reference(_from_project = nil)
id
end
+
+ def reference_link_text(from_project = nil)
+ return "##{id}" if /^\d+$/.match(id)
+
+ id
+ end
end
diff --git a/app/models/issue.rb b/app/models/issue.rb
index e064b0f8b95..3f188e04770 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -106,7 +106,7 @@ class Issue < ActiveRecord::Base
def related_branches
project.repository.branch_names.select do |branch|
- branch.end_with?("-#{iid}")
+ branch =~ /\A#{iid}-(?!\d+-stable)/i
end
end
@@ -151,7 +151,7 @@ class Issue < ActiveRecord::Base
end
def to_branch_name
- "#{title.parameterize}-#{iid}"
+ "#{iid}-#{title.parameterize}"
end
def can_be_worked_on?(current_user)
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index bf185cb5dd8..e410febdfff 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -128,7 +128,7 @@ class MergeRequest < ActiveRecord::Base
validates :target_project, presence: true
validates :target_branch, presence: true
validates :merge_user, presence: true, if: :merge_when_build_succeeds?
- validate :validate_branches
+ validate :validate_branches, unless: :allow_broken
validate :validate_fork
scope :by_branch, ->(branch_name) { where("(source_branch LIKE :branch) OR (target_branch LIKE :branch)", branch: branch_name) }
@@ -218,7 +218,7 @@ class MergeRequest < ActiveRecord::Base
end
if opened? || reopened?
- similar_mrs = self.target_project.merge_requests.where(source_branch: source_branch, target_branch: target_branch, source_project_id: source_project.id).opened
+ similar_mrs = self.target_project.merge_requests.where(source_branch: source_branch, target_branch: target_branch, source_project_id: source_project.try(:id)).opened
similar_mrs = similar_mrs.where('id not in (?)', self.id) if self.id
if similar_mrs.any?
errors.add :validate_branches,
@@ -345,7 +345,7 @@ class MergeRequest < ActiveRecord::Base
def hook_attrs
attrs = {
- source: source_project.hook_attrs,
+ source: source_project.try(:hook_attrs),
target: target_project.hook_attrs,
last_commit: nil,
work_in_progress: work_in_progress?
diff --git a/app/models/oauth_access_token.rb b/app/models/oauth_access_token.rb
new file mode 100644
index 00000000000..c78c7f4aa0e
--- /dev/null
+++ b/app/models/oauth_access_token.rb
@@ -0,0 +1,19 @@
+# == Schema Information
+#
+# Table name: oauth_access_tokens
+#
+# id :integer not null, primary key
+# resource_owner_id :integer
+# application_id :integer
+# token :string not null
+# refresh_token :string
+# expires_in :integer
+# revoked_at :datetime
+# created_at :datetime not null
+# scopes :string
+#
+
+class OauthAccessToken < ActiveRecord::Base
+ belongs_to :resource_owner, class_name: 'User'
+ belongs_to :application, class_name: 'Doorkeeper::Application'
+end
diff --git a/app/models/project.rb b/app/models/project.rb
index fadc8bb2c9e..8f20922e3c5 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -409,6 +409,35 @@ class Project < ActiveRecord::Base
self.import_data.destroy if self.import_data
end
+ def import_url=(value)
+ import_url = Gitlab::ImportUrl.new(value)
+ create_or_update_import_data(credentials: import_url.credentials)
+ super(import_url.sanitized_url)
+ end
+
+ def import_url
+ if import_data && super
+ import_url = Gitlab::ImportUrl.new(super, credentials: import_data.credentials)
+ import_url.full_url
+ else
+ super
+ end
+ end
+
+ def create_or_update_import_data(data: nil, credentials: nil)
+ project_import_data = import_data || build_import_data
+ if data
+ project_import_data.data ||= {}
+ project_import_data.data = project_import_data.data.merge(data)
+ end
+ if credentials
+ project_import_data.credentials ||= {}
+ project_import_data.credentials = project_import_data.credentials.merge(credentials)
+ end
+
+ project_import_data.save
+ end
+
def import?
external_import? || forked?
end
@@ -865,7 +894,9 @@ class Project < ActiveRecord::Base
def change_head(branch)
repository.before_change_head
- gitlab_shell.update_repository_head(self.path_with_namespace, branch)
+ repository.rugged.references.create('HEAD',
+ "refs/heads/#{branch}",
+ force: true)
reload_default_branch
end
diff --git a/app/models/project_import_data.rb b/app/models/project_import_data.rb
index cd3319f077e..79efb403058 100644
--- a/app/models/project_import_data.rb
+++ b/app/models/project_import_data.rb
@@ -12,8 +12,20 @@ require 'file_size_validator'
class ProjectImportData < ActiveRecord::Base
belongs_to :project
-
+ attr_encrypted :credentials,
+ key: Gitlab::Application.secrets.db_key_base,
+ marshal: true,
+ encode: true,
+ mode: :per_attribute_iv_and_salt
+
serialize :data, JSON
validates :project, presence: true
+
+ before_validation :symbolize_credentials
+
+ def symbolize_credentials
+ # bang doesn't work here - attr_encrypted makes it not to work
+ self.credentials = self.credentials.deep_symbolize_keys unless self.credentials.blank?
+ end
end
diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb
index 9e7f642180e..060062aaf7a 100644
--- a/app/models/project_services/bamboo_service.rb
+++ b/app/models/project_services/bamboo_service.rb
@@ -82,17 +82,17 @@ class BambooService < CiService
end
def build_info(sha)
- url = URI.parse("#{bamboo_url}/rest/api/latest/result?label=#{sha}")
+ url = URI.join(bamboo_url, "/rest/api/latest/result?label=#{sha}").to_s
if username.blank? && password.blank?
- @response = HTTParty.get(parsed_url.to_s, verify: false)
+ @response = HTTParty.get(url, verify: false)
else
- get_url = "#{url}&os_authType=basic"
+ url << '&os_authType=basic'
auth = {
- username: username,
- password: password,
+ username: username,
+ password: password
}
- @response = HTTParty.get(get_url, verify: false, basic_auth: auth)
+ @response = HTTParty.get(url, verify: false, basic_auth: auth)
end
end
@@ -101,11 +101,11 @@ class BambooService < CiService
if @response.code != 200 || @response['results']['results']['size'] == '0'
# If actual build link can't be determined, send user to build summary page.
- "#{bamboo_url}/browse/#{build_key}"
+ URI.join(bamboo_url, "/browse/#{build_key}").to_s
else
# If actual build link is available, go to build result page.
result_key = @response['results']['results']['result']['planResultKey']['key']
- "#{bamboo_url}/browse/#{result_key}"
+ URI.join(bamboo_url, "/browse/#{result_key}").to_s
end
end
@@ -134,7 +134,7 @@ class BambooService < CiService
return unless supported_events.include?(data[:object_kind])
# Bamboo requires a GET and does not take any data.
- self.class.get("#{bamboo_url}/updateAndBuild.action?buildKey=#{build_key}",
- verify: false)
+ url = URI.join(bamboo_url, "/updateAndBuild.action?buildKey=#{build_key}").to_s
+ self.class.get(url, verify: false)
end
end
diff --git a/app/models/project_services/builds_email_service.rb b/app/models/project_services/builds_email_service.rb
index f9f04838766..6ab6d7417b7 100644
--- a/app/models/project_services/builds_email_service.rb
+++ b/app/models/project_services/builds_email_service.rb
@@ -23,7 +23,7 @@ class BuildsEmailService < Service
prop_accessor :recipients
boolean_accessor :add_pusher
boolean_accessor :notify_only_broken_builds
- validates :recipients, presence: true, if: :activated?
+ validates :recipients, presence: true, if: ->(s) { s.activated? && !s.add_pusher? }
def initialize_properties
if properties.nil?
@@ -87,10 +87,14 @@ class BuildsEmailService < Service
end
def all_recipients(data)
- all_recipients = recipients.split(',').compact.reject(&:blank?)
+ all_recipients = []
+
+ unless recipients.blank?
+ all_recipients += recipients.split(',').compact.reject(&:blank?)
+ end
if add_pusher? && data[:user][:email]
- all_recipients << "#{data[:user][:email]}"
+ all_recipients << data[:user][:email]
end
all_recipients
diff --git a/app/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb
index b8e9416131a..8dceee5e2c5 100644
--- a/app/models/project_services/teamcity_service.rb
+++ b/app/models/project_services/teamcity_service.rb
@@ -85,13 +85,15 @@ class TeamcityService < CiService
end
def build_info(sha)
- url = URI.parse("#{teamcity_url}/httpAuth/app/rest/builds/"\
- "branch:unspecified:any,number:#{sha}")
+ url = URI.join(
+ teamcity_url,
+ "/httpAuth/app/rest/builds/branch:unspecified:any,number:#{sha}"
+ ).to_s
auth = {
username: username,
- password: password,
+ password: password
}
- @response = HTTParty.get("#{url}", verify: false, basic_auth: auth)
+ @response = HTTParty.get(url, verify: false, basic_auth: auth)
end
def build_page(sha, ref)
@@ -100,12 +102,14 @@ class TeamcityService < CiService
if @response.code != 200
# If actual build link can't be determined,
# send user to build summary page.
- "#{teamcity_url}/viewLog.html?buildTypeId=#{build_type}"
+ URI.join(teamcity_url, "/viewLog.html?buildTypeId=#{build_type}").to_s
else
# If actual build link is available, go to build result page.
built_id = @response['build']['id']
- "#{teamcity_url}/viewLog.html?buildId=#{built_id}"\
- "&buildTypeId=#{build_type}"
+ URI.join(
+ teamcity_url,
+ "/viewLog.html?buildId=#{built_id}&buildTypeId=#{build_type}"
+ ).to_s
end
end
@@ -140,12 +144,13 @@ class TeamcityService < CiService
branch = Gitlab::Git.ref_name(data[:ref])
- self.class.post("#{teamcity_url}/httpAuth/app/rest/buildQueue",
- body: "<build branchName=\"#{branch}\">"\
- "<buildType id=\"#{build_type}\"/>"\
- '</build>',
- headers: { 'Content-type' => 'application/xml' },
- basic_auth: auth
- )
+ self.class.post(
+ URI.join(teamcity_url, '/httpAuth/app/rest/buildQueue').to_s,
+ body: "<build branchName=\"#{branch}\">"\
+ "<buildType id=\"#{build_type}\"/>"\
+ '</build>',
+ headers: { 'Content-type' => 'application/xml' },
+ basic_auth: auth
+ )
end
end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 462b48118ef..308c590e3f8 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -169,7 +169,12 @@ class Repository
def rm_tag(tag_name)
before_remove_tag
- gitlab_shell.rm_tag(path_with_namespace, tag_name)
+ begin
+ rugged.tags.delete(tag_name)
+ true
+ rescue Rugged::ReferenceError
+ false
+ end
end
def branch_names
@@ -253,6 +258,8 @@ class Repository
# This ensures this particular cache is flushed after the first commit to a
# new repository.
expire_emptiness_caches if empty?
+ expire_branch_count_cache
+ expire_tag_count_cache
end
def expire_branch_cache(branch_name = nil)
@@ -795,7 +802,7 @@ class Repository
def search_files(query, ref)
offset = 2
- args = %W(#{Gitlab.config.git.bin_path} grep -i -I -n --before-context #{offset} --after-context #{offset} -e #{query} #{ref || root_ref})
+ args = %W(#{Gitlab.config.git.bin_path} grep -i -I -n --before-context #{offset} --after-context #{offset} -e #{Regexp.escape(query)} #{ref || root_ref})
Gitlab::Popen.popen(args, path_to_repo).first.scrub.split(/^--$/)
end
diff --git a/app/services/issues/base_service.rb b/app/services/issues/base_service.rb
index 770f32de944..772f5c5fffa 100644
--- a/app/services/issues/base_service.rb
+++ b/app/services/issues/base_service.rb
@@ -3,7 +3,7 @@ module Issues
def hook_data(issue, action)
issue_data = issue.to_hook_data(current_user)
- issue_url = Gitlab::UrlBuilder.new(:issue).build(issue.id)
+ issue_url = Gitlab::UrlBuilder.build(issue)
issue_data[:object_attributes].merge!(url: issue_url, action: action)
issue_data
end
diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb
index ac5b58db862..e6837a18696 100644
--- a/app/services/merge_requests/base_service.rb
+++ b/app/services/merge_requests/base_service.rb
@@ -20,8 +20,7 @@ module MergeRequests
def hook_data(merge_request, action)
hook_data = merge_request.to_hook_data(current_user)
- merge_request_url = Gitlab::UrlBuilder.new(:merge_request).build(merge_request.id)
- hook_data[:object_attributes][:url] = merge_request_url
+ hook_data[:object_attributes][:url] = Gitlab::UrlBuilder.build(merge_request)
hook_data[:object_attributes][:action] = action
hook_data
end
diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb
index 6e9152e444e..fa34753c4fd 100644
--- a/app/services/merge_requests/build_service.rb
+++ b/app/services/merge_requests/build_service.rb
@@ -51,7 +51,7 @@ module MergeRequests
# be interpreted as the use wants to close that issue on this project
# Pattern example: 112-fix-mep-mep
# Will lead to appending `Closes #112` to the description
- if match = merge_request.source_branch.match(/-(\d+)\z/)
+ if match = merge_request.source_branch.match(/\A(\d+)-/)
iid = match[1]
closes_issue = "Closes ##{iid}"
diff --git a/app/services/projects/housekeeping_service.rb b/app/services/projects/housekeeping_service.rb
index a0973c5d260..3b7c36f0908 100644
--- a/app/services/projects/housekeeping_service.rb
+++ b/app/services/projects/housekeeping_service.rb
@@ -26,7 +26,9 @@ module Projects
GitlabShellOneShotWorker.perform_async(:gc, @project.path_with_namespace)
ensure
- @project.update_column(:pushes_since_gc, 0)
+ Gitlab::Metrics.measure(:reset_pushes_since_gc) do
+ @project.update_column(:pushes_since_gc, 0)
+ end
end
def needed?
@@ -34,14 +36,18 @@ module Projects
end
def increment!
- @project.increment!(:pushes_since_gc)
+ Gitlab::Metrics.measure(:increment_pushes_since_gc) do
+ @project.increment!(:pushes_since_gc)
+ end
end
private
def try_obtain_lease
- lease = ::Gitlab::ExclusiveLease.new("project_housekeeping:#{@project.id}", timeout: LEASE_TIMEOUT)
- lease.try_obtain
+ Gitlab::Metrics.measure(:obtain_housekeeping_lease) do
+ lease = ::Gitlab::ExclusiveLease.new("project_housekeeping:#{@project.id}", timeout: LEASE_TIMEOUT)
+ lease.try_obtain
+ end
end
end
end
diff --git a/app/services/projects/participants_service.rb b/app/services/projects/participants_service.rb
index 0004a399f47..02c4eee3d02 100644
--- a/app/services/projects/participants_service.rb
+++ b/app/services/projects/participants_service.rb
@@ -1,28 +1,37 @@
module Projects
class ParticipantsService < BaseService
- def execute(note_type, note_id)
- participating =
- if note_type && note_id
- participants_in(note_type, note_id)
- else
- []
- end
+ def execute(noteable_type, noteable_id)
+ @noteable_type = noteable_type
+ @noteable_id = noteable_id
project_members = sorted(project.team.members)
- participants = all_members + groups + project_members + participating
+ participants = target_owner + participants_in_target + all_members + groups + project_members
participants.uniq
end
- def participants_in(type, id)
- target =
- case type
+ def target
+ @target ||=
+ case @noteable_type
when "Issue"
- project.issues.find_by_iid(id)
+ project.issues.find_by_iid(@noteable_id)
when "MergeRequest"
- project.merge_requests.find_by_iid(id)
+ project.merge_requests.find_by_iid(@noteable_id)
when "Commit"
- project.commit(id)
+ project.commit(@noteable_id)
+ else
+ nil
end
-
+ end
+
+ def target_owner
+ return [] unless target && target.author.present?
+
+ [{
+ name: target.author.name,
+ username: target.author.username
+ }]
+ end
+
+ def participants_in_target
return [] unless target
users = target.participants(current_user)
@@ -30,13 +39,13 @@ module Projects
end
def sorted(users)
- users.uniq.to_a.compact.sort_by(&:username).map do |user|
+ users.uniq.to_a.compact.sort_by(&:username).map do |user|
{ username: user.username, name: user.name }
end
end
def groups
- current_user.authorized_groups.sort_by(&:path).map do |group|
+ current_user.authorized_groups.sort_by(&:path).map do |group|
count = group.users.count
{ username: group.path, name: group.name, count: count }
end
diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb
index 2e734654466..79a27f4af7e 100644
--- a/app/services/projects/transfer_service.rb
+++ b/app/services/projects/transfer_service.rb
@@ -34,8 +34,9 @@ module Projects
raise TransferError.new("Project with same path in target namespace already exists")
end
- # Apply new namespace id
+ # Apply new namespace id and visibility level
project.namespace = new_namespace
+ project.visibility_level = new_namespace.visibility_level unless project.visibility_level_allowed_by_group?
project.save!
# Notifications
@@ -56,7 +57,7 @@ module Projects
Gitlab::UploadsTransfer.new.move_project(project.path, old_namespace.path, new_namespace.path)
project.old_path_with_namespace = old_path
-
+
SystemHooksService.new.execute_hooks_for(project, :transfer)
true
end
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index 658b086496f..82a0e2fd1f5 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -222,7 +222,7 @@ class SystemNoteService
# Called when a branch is created from the 'new branch' button on a issue
# Example note text:
#
- # "Started branch `issue-branch-button-201`"
+ # "Started branch `201-issue-branch-button`"
def self.new_issue_branch(issue, project, author, branch)
h = Gitlab::Routing.url_helpers
link = h.namespace_project_compare_url(project.namespace, project, from: project.default_branch, to: branch)
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index a8cca1a81cb..555aea554f0 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -271,5 +271,24 @@
.col-sm-10
= f.text_field :sentry_dsn, class: 'form-control'
+ %fieldset
+ %legend Repository Checks
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ .checkbox
+ = f.label :repository_checks_enabled do
+ = f.check_box :repository_checks_enabled
+ Enable Repository Checks
+ .help-block
+ GitLab will periodically run
+ %a{ href: 'https://www.kernel.org/pub/software/scm/git/docs/git-fsck.html', target: 'blank' } 'git fsck'
+ in all project and wiki repositories to look for silent disk corruption issues.
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ = link_to 'Clear all repository checks', clear_repository_check_states_admin_application_settings_path, data: { confirm: 'This will clear repository check states for ALL projects in the database. This cannot be undone. Are you sure?' }, method: :put, class: "btn btn-sm btn-remove"
+ .help-block
+ If you got a lot of false alarms from repository checks you can choose to clear all repository check information from the database.
+
+
.form-actions
= f.submit 'Save', class: 'btn btn-save'
diff --git a/app/views/admin/logs/show.html.haml b/app/views/admin/logs/show.html.haml
index af9fdeb0734..4b475a4d8fa 100644
--- a/app/views/admin/logs/show.html.haml
+++ b/app/views/admin/logs/show.html.haml
@@ -1,6 +1,7 @@
- page_title "Logs"
- loggers = [Gitlab::GitLogger, Gitlab::AppLogger,
- Gitlab::ProductionLogger, Gitlab::SidekiqLogger]
+ Gitlab::ProductionLogger, Gitlab::SidekiqLogger,
+ Gitlab::RepositoryCheckLogger]
%ul.nav-links.log-tabs
- loggers.each do |klass|
%li{ class: (klass == Gitlab::GitLogger ? 'active' : '') }
diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml
index d39c0f44031..aa07afa0d62 100644
--- a/app/views/admin/projects/index.html.haml
+++ b/app/views/admin/projects/index.html.haml
@@ -3,7 +3,7 @@
.row.prepend-top-default
%aside.col-md-3
- .admin-filter
+ .panel.admin-filter
= form_tag admin_namespaces_projects_path, method: :get, class: '' do
.form-group
= label_tag :name, 'Name:'
@@ -38,7 +38,13 @@
%span.descr
= visibility_level_icon(level)
= label
- %hr
+ %fieldset
+ %strong Problems
+ .checkbox
+ = label_tag :last_repository_check_failed do
+ = check_box_tag :last_repository_check_failed, 1, params[:last_repository_check_failed]
+ %span Last repository check failed
+
= hidden_field_tag :sort, params[:sort]
= button_tag "Search", class: "btn submit btn-primary"
= link_to "Reset", admin_namespaces_projects_path, class: "btn btn-cancel"
diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml
index c638c32a654..73986d21bcf 100644
--- a/app/views/admin/projects/show.html.haml
+++ b/app/views/admin/projects/show.html.haml
@@ -5,6 +5,16 @@
%i.fa.fa-pencil-square-o
Edit
%hr
+- if @project.last_repository_check_failed?
+ .row
+ .col-md-12
+ .panel
+ .panel-heading.alert.alert-danger
+ Last repository check
+ = "(#{time_ago_in_words(@project.last_repository_check_at)} ago)"
+ failed. See
+ = link_to 'repocheck.log', admin_logs_path
+ for error messages.
.row
.col-md-6
.panel.panel-default
@@ -95,6 +105,32 @@
.col-sm-offset-2.col-sm-10
= f.submit 'Transfer', class: 'btn btn-primary'
+ .panel.panel-default.repository-check
+ .panel-heading
+ Repository check
+ .panel-body
+ = form_for @project, url: repository_check_admin_namespace_project_path(@project.namespace, @project), method: :post do |f|
+ .form-group
+ - if @project.last_repository_check_at.nil?
+ This repository has never been checked.
+ - else
+ This repository was last checked
+ = @project.last_repository_check_at.to_s(:medium) + '.'
+ The check
+ - if @project.last_repository_check_failed?
+ = succeed '.' do
+ %strong.cred failed
+ See
+ = link_to 'repocheck.log', admin_logs_path
+ for error messages.
+ - else
+ passed.
+
+ = link_to icon('question-circle'), help_page_path('administration', 'repository_checks')
+
+ .form-group
+ = f.submit 'Trigger repository check', class: 'btn btn-primary'
+
.col-md-6
- if @group
.panel.panel-default
diff --git a/app/views/doorkeeper/applications/index.html.haml b/app/views/doorkeeper/applications/index.html.haml
index 55f4a6f287d..0aff79749ef 100644
--- a/app/views/doorkeeper/applications/index.html.haml
+++ b/app/views/doorkeeper/applications/index.html.haml
@@ -68,7 +68,7 @@
%td= app.name
%td= token.created_at
%td= token.scopes
- %td= render 'delete_form', application: app
+ %td= render 'doorkeeper/authorized_applications/delete_form', application: app
- @authorized_anonymous_tokens.each do |token|
%tr
%td
diff --git a/app/views/groups/milestones/new.html.haml b/app/views/groups/milestones/new.html.haml
index 4290e0bf72e..7d9d27ae1fc 100644
--- a/app/views/groups/milestones/new.html.haml
+++ b/app/views/groups/milestones/new.html.haml
@@ -8,7 +8,7 @@
This will create milestone in every selected project
%hr
-= form_for @milestone, url: group_milestones_path(@group), html: { class: 'form-horizontal milestone-form gfm-form js-quick-submit js-requires-input' } do |f|
+= form_for @milestone, url: group_milestones_path(@group), html: { class: 'form-horizontal milestone-form common-note-form js-quick-submit js-requires-input' } do |f|
.row
- if @milestone.errors.any?
#error_explanation
@@ -27,7 +27,7 @@
= f.label :description, "Description", class: "control-label"
.col-sm-10
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do
- = render 'projects/zen', f: f, attr: :description, classes: 'description form-control'
+ = render 'projects/zen', f: f, attr: :description, classes: 'note-textarea', placeholder: 'Write milestone description...'
.clearfix
.error-alert
.form-group
diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml
index d084559abc3..f12df5c8ffe 100644
--- a/app/views/help/ui.html.haml
+++ b/app/views/help/ui.html.haml
@@ -345,11 +345,11 @@
%ul
%li
%a.dropdown-menu-user-link.is-active{href: "#"}
- = link_to_member_avatar(current_user, size: 30)
+ = link_to_member_avatar(@user, size: 30)
%strong.dropdown-menu-user-full-name
- = current_user.name
+ = @user.name
.dropdown-menu-user-username
- = current_user.to_reference
+ = @user.to_reference
.example
%div
@@ -372,11 +372,11 @@
%ul
%li
%a.dropdown-menu-user-link.is-active{href: "#"}
- = link_to_member_avatar(current_user, size: 30)
+ = link_to_member_avatar(@user, size: 30)
%strong.dropdown-menu-user-full-name
- = current_user.name
+ = @user.name
.dropdown-menu-user-username
- = current_user.to_reference
+ = @user.to_reference
.dropdown-page-two
.dropdown-title
%button.dropdown-title-button.dropdown-menu-back{aria: {label: "Go back"}}
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index 44339293095..3beb8ff7c0d 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -8,7 +8,7 @@
.navbar-collapse.collapse
%ul.nav.navbar-nav
%li.hidden-sm.hidden-xs
- = render 'layouts/search'
+ = render 'layouts/search' unless current_controller?(:search)
%li.visible-sm.visible-xs
= link_to search_path, title: 'Search', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('search')
diff --git a/app/views/projects/_builds_settings.html.haml b/app/views/projects/_builds_settings.html.haml
index 9ae6964aaac..b6074373e2b 100644
--- a/app/views/projects/_builds_settings.html.haml
+++ b/app/views/projects/_builds_settings.html.haml
@@ -52,6 +52,9 @@
%li
phpunit --coverage-text --colors=never (PHP) -
%code ^\s*Lines:\s*\d+.\d+\%
+ %li
+ gcovr (C/C++) -
+ %code ^TOTAL.*\s+(\d+\%)$
.form-group
.col-sm-offset-2.col-sm-10
diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml
index 7a78d61a611..8de44a6c914 100644
--- a/app/views/projects/_md_preview.html.haml
+++ b/app/views/projects/_md_preview.html.haml
@@ -1,6 +1,6 @@
.md-area
.md-header
- %ul.nav-links
+ %ul.nav-links.clearfix
%li.active
%a.js-md-write-button{ href: "#md-write-holder", tabindex: -1 }
Write
diff --git a/app/views/projects/_zen.html.haml b/app/views/projects/_zen.html.haml
index bddff5cdcbc..e1e35013968 100644
--- a/app/views/projects/_zen.html.haml
+++ b/app/views/projects/_zen.html.haml
@@ -1,8 +1,8 @@
.zen-backdrop
- classes << ' js-gfm-input js-autosize markdown-area'
- if defined?(f) && f
- = f.text_area attr, class: classes, placeholder: "Write a comment or drag your files here..."
+ = f.text_area attr, class: classes, placeholder: placeholder
- else
- = text_area_tag attr, nil, class: classes, placeholder: "Write a comment or drag your files here..."
+ = text_area_tag attr, nil, class: classes, placeholder: placeholder
%a.zen-cotrol.zen-control-leave.js-zen-leave{ href: "#" }
= icon('compress')
diff --git a/app/views/projects/blob/diff.html.haml b/app/views/projects/blob/diff.html.haml
index abcfca4cd11..38e62c81fed 100644
--- a/app/views/projects/blob/diff.html.haml
+++ b/app/views/projects/blob/diff.html.haml
@@ -1,20 +1,20 @@
- if @lines.present?
- if @form.unfold? && @form.since != 1 && !@form.bottom?
%tr.line_holder{ id: @form.since }
- = render "projects/diffs/match_line", {line: @match_line,
- line_old: @form.since, line_new: @form.since, bottom: false, new_file: false}
+ = render "projects/diffs/match_line", { line: @match_line,
+ line_old: @form.since, line_new: @form.since, bottom: false, new_file: false }
- @lines.each_with_index do |line, index|
- line_new = index + @form.since
- line_old = line_new - @form.offset
%tr.line_holder
- %td.old_line.diff-line-num{data: {linenumber: line_old}}
+ %td.old_line.diff-line-num{ data: { linenumber: line_old } }
= link_to raw(line_old), "#"
- %td.new_line.diff-line-num
+ %td.new_line.diff-line-num{ data: { linenumber: line_old } }
= link_to raw(line_new) , "#"
%td.line_content.noteable_line==#{' ' * @form.indent}#{line}
- if @form.unfold? && @form.bottom? && @form.to < @blob.loc
%tr.line_holder{ id: @form.to }
- = render "projects/diffs/match_line", {line: @match_line,
- line_old: @form.to, line_new: @form.to, bottom: true, new_file: false}
+ = render "projects/diffs/match_line", { line: @match_line,
+ line_old: @form.to, line_new: @form.to, bottom: true, new_file: false }
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index 7da89231243..34f27f1e793 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -11,7 +11,7 @@
= cache(cache_key) do
%li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" }
.commit-row-title
- %span.item-title.str-truncated
+ %span.item-title
= link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message"
- if commit.description?
%a.text-expander.js-toggle-button ...
diff --git a/app/views/projects/diffs/_line.html.haml b/app/views/projects/diffs/_line.html.haml
index 9464c8dc996..107097ad963 100644
--- a/app/views/projects/diffs/_line.html.haml
+++ b/app/views/projects/diffs/_line.html.haml
@@ -1,26 +1,26 @@
- type = line.type
-%tr.line_holder{id: line_code, class: type}
+%tr.line_holder{ id: line_code, class: type }
- case type
- when 'match'
- = render "projects/diffs/match_line", {line: line.text,
- line_old: line.old_pos, line_new: line.new_pos, bottom: false, new_file: diff_file.new_file}
+ = render "projects/diffs/match_line", { line: line.text,
+ line_old: line.old_pos, line_new: line.new_pos, bottom: false, new_file: diff_file.new_file }
- when 'nonewline'
%td.old_line.diff-line-num
%td.new_line.diff-line-num
%td.line_content.match= line.text
- else
- %td.old_line.diff-line-num{class: type}
- - link_text = raw(type == "new" ? "&nbsp;" : line.old_pos)
+ %td.old_line.diff-line-num{ class: type, data: { linenumber: line.new_pos } }
+ - link_text = type == "new" ? "&nbsp;".html_safe : line.old_pos
- if defined?(plain) && plain
= link_text
- else
- = link_to link_text, "##{line_code}", id: line_code
+ = link_to "", "##{line_code}", id: line_code, data: { linenumber: link_text }
- if @comments_allowed && can?(current_user, :create_note, @project)
= link_to_new_diff_note(line_code)
- %td.new_line.diff-line-num{class: type, data: {linenumber: line.new_pos}}
- - link_text = raw(type == "old" ? "&nbsp;" : line.new_pos)
+ %td.new_line.diff-line-num{ class: type, data: { linenumber: line.new_pos } }
+ - link_text = type == "old" ? "&nbsp;".html_safe : line.new_pos
- if defined?(plain) && plain
= link_text
- else
- = link_to link_text, "##{line_code}", id: line_code
- %td.line_content{class: "noteable_line #{type} #{line_code}", data: { line_code: line_code }}= diff_line_content(line.text)
+ = link_to "", "##{line_code}", id: line_code, data: { linenumber: link_text }
+ %td.line_content{ class: ['noteable_line', type, line_code], data: { line_code: line_code } }= diff_line_content(line.text, type)
diff --git a/app/views/projects/diffs/_parallel_view.html.haml b/app/views/projects/diffs/_parallel_view.html.haml
index d7c49068745..81948513e43 100644
--- a/app/views/projects/diffs/_parallel_view.html.haml
+++ b/app/views/projects/diffs/_parallel_view.html.haml
@@ -14,11 +14,11 @@
%td.new_line.diff-line-num
%td.line_content.parallel.match= left[:text]
- else
- %td.old_line.diff-line-num{id: left[:line_code], class: "#{left[:type]}"}
+ %td.old_line.diff-line-num{id: left[:line_code], class: "#{left[:type]} #{'empty-cell' if !left[:number]}"}
= link_to raw(left[:number]), "##{left[:line_code]}", id: left[:line_code]
- if @comments_allowed && can?(current_user, :create_note, @project)
= link_to_new_diff_note(left[:line_code], 'old')
- %td.line_content{class: "parallel noteable_line #{left[:type]} #{left[:line_code]}", data: { line_code: left[:line_code] }}= diff_line_content(left[:text])
+ %td.line_content{class: "parallel noteable_line #{left[:type]} #{left[:line_code]} #{'empty-cell' if left[:text].empty?}", data: { line_code: left[:line_code] }}= diff_line_content(left[:text])
- if right[:type] == 'new'
- new_line_class = 'new'
@@ -27,11 +27,11 @@
- new_line_class = nil
- new_line_code = left[:line_code]
- %td.new_line.diff-line-num{id: new_line_code, class: "#{new_line_class}", data: { linenumber: right[:number] }}
+ %td.new_line.diff-line-num{id: new_line_code, class: "#{new_line_class} #{'empty-cell' if !right[:number]}", data: { linenumber: right[:number] }}
= link_to raw(right[:number]), "##{new_line_code}", id: new_line_code
- if @comments_allowed && can?(current_user, :create_note, @project)
= link_to_new_diff_note(right[:line_code], 'new')
- %td.line_content.parallel{class: "noteable_line #{new_line_class} #{new_line_code}", data: { line_code: new_line_code }}= diff_line_content(right[:text])
+ %td.line_content.parallel{class: "noteable_line #{new_line_class} #{new_line_code} #{'empty-cell' if right[:text].empty?}", data: { line_code: new_line_code }}= diff_line_content(right[:text])
- if @reply_allowed
- comments_left, comments_right = organize_comments(left[:type], right[:type], left[:line_code], right[:line_code])
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index 6d872cd0b21..76a4f41193c 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -210,6 +210,7 @@
%li Be careful. Changing the project's namespace can have unintended side effects.
%li You can only transfer the project to namespaces you manage.
%li You will need to update your local repositories to point to the new location.
+ %li Project visibility level will be changed to match namespace rules when transfering to a group.
.form-actions
= f.submit 'Transfer project', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => transfer_project_message(@project) }
- else
diff --git a/app/views/projects/issues/_form.html.haml b/app/views/projects/issues/_form.html.haml
index 33c48199ba5..7076f5db015 100644
--- a/app/views/projects/issues/_form.html.haml
+++ b/app/views/projects/issues/_form.html.haml
@@ -1,4 +1,4 @@
-= form_for [@project.namespace.becomes(Namespace), @project, @issue], html: { class: 'form-horizontal issue-form gfm-form js-quick-submit js-requires-input' } do |f|
+= form_for [@project.namespace.becomes(Namespace), @project, @issue], html: { class: 'form-horizontal issue-form common-note-form js-quick-submit js-requires-input' } do |f|
= render 'shared/issuable/form', f: f, issuable: @issue
:javascript
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index 4aa92d0b39e..7a8009f6da4 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -27,7 +27,7 @@
= icon('thumbs-down')
= downvotes
- - note_count = issue.notes.user.count
+ - note_count = issue.notes.user.nonawards.count
- if note_count > 0
%li
= link_to issue_path(issue) + "#notes" do
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index 6fa059cbe68..bde80bbb54b 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -1,80 +1,79 @@
- page_title "#{@issue.title} (##{@issue.iid})", "Issues"
- page_description @issue.description
- page_card_attributes @issue.card_attributes
+- header_title project_title(@project, "Issues", namespace_project_issues_path(@project.namespace, @project))
-= render "header_title"
+.clearfix.detail-page-header
+ .issuable-header
+ .issuable-status-box.status-box.status-box-closed{ class: issue_button_visibility(@issue, false) }
+ = icon('check', class: "hidden-sm hidden-md hidden-lg")
+ %span.hidden-xs
+ Closed
+ .issuable-status-box.status-box.status-box-open{ class: issue_button_visibility(@issue, true) }
+ = icon('circle-o', class: "hidden-sm hidden-md hidden-lg")
+ %span.hidden-xs Open
-.issue
- .detail-page-header.issuable-header
- .pull-left
- .status-box{ class: "status-box-closed #{issue_button_visibility(@issue, false)}"}
- %span.hidden-xs
- Closed
- %span.hidden-sm.hidden-md.hidden-lg
- = icon('check')
- .status-box{ class: "status-box-open #{issue_button_visibility(@issue, true)}"}
- %span.hidden-xs
- Open
- %span.hidden-sm.hidden-md.hidden-lg
- = icon('circle-o')
-
- %a.btn.btn-default.pull-right.visible-xs-block.gutter-toggle.js-sidebar-toggle{ href: "#" }
+ %a.btn.btn-default.pull-right.visible-xs-block.gutter-toggle.issuable-gutter-toggle.js-sidebar-toggle{ href: "#" }
= icon('angle-double-left')
- .issue-meta
+ .issuable-meta
= confidential_icon(@issue)
- %strong.identifier
- Issue ##{@issue.iid}
- %span.creator
- opened
- .editor-details
- .editor-details
- = time_ago_with_tooltip(@issue.created_at)
- by
- %strong
- = link_to_member(@project, @issue.author, size: 24, mobile_classes: "hidden-xs")
- %strong
- = link_to_member(@project, @issue.author, size: 24, mobile_classes: "hidden-sm hidden-md hidden-lg",
- by_username: true, avatar: false)
+ = issuable_meta(@issue, @project, "Issue")
+
+ - if can?(current_user, :create_issue, @project) || can?(current_user, :update_issue, @issue)
+ .issuable-actions
+ .clearfix.issue-btn-group.dropdown
+ %button.btn.btn-default.pull-left.hidden-md.hidden-lg{ data: { toggle: "dropdown" } }
+ %span.caret
+ Options
+ .dropdown-menu.dropdown-menu-align-right.hidden-lg
+ %ul
+ - if can?(current_user, :create_issue, @project)
+ %li
+ = link_to 'New issue', new_namespace_project_issue_path(@project.namespace, @project), title: 'New issue', id: 'new_issue_link'
+ - if can?(current_user, :update_issue, @issue)
+ %li
+ = link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
+ %li
+ = link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
+ %li
+ = link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue)
+ - if can?(current_user, :create_issue, @project)
+ = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'hidden-xs hidden-sm btn btn-nr btn-grouped new-issue-link btn-success', title: 'New issue', id: 'new_issue_link' do
+ = icon('plus')
+ New issue
+ - if can?(current_user, :update_issue, @issue)
+ = link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-nr btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
+ = link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-nr btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
+ = link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'hidden-xs hidden-sm btn btn-nr btn-grouped issuable-edit' do
+ = icon('pencil-square-o')
+ Edit
- .pull-right.issue-btn-group
- - if can?(current_user, :create_issue, @project)
- = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'btn btn-nr btn-grouped new-issue-link btn-success', title: 'New issue', id: 'new_issue_link' do
- = icon('plus')
- New issue
- - if can?(current_user, :update_issue, @issue)
- = link_to 'Reopen issue', issue_path(@issue, issue: {state_event: :reopen}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
- = link_to 'Close issue', issue_path(@issue, issue: {state_event: :close}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
- = link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'btn btn-nr btn-grouped issuable-edit' do
- = icon('pencil-square-o')
- Edit
+.issue-details.issuable-details
+ .detail-page-description.content-block
+ %h2.title
+ = markdown escape_once(@issue.title), pipeline: :single_line
+ - if @issue.description.present?
+ .description{ class: can?(current_user, :update_issue, @issue) ? 'js-task-list-container' : '' }
+ .wiki
+ = preserve do
+ = markdown(@issue.description, cache_key: [@issue, "description"])
+ %textarea.hidden.js-task-list-field
+ = @issue.description
+ = edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue_edited_ago')
- .issue-details.issuable-details
- .detail-page-description.content-block
- %h2.title
- = markdown escape_once(@issue.title), pipeline: :single_line
- %div
- - if @issue.description.present?
- .description{class: can?(current_user, :update_issue, @issue) ? 'js-task-list-container' : ''}
- .wiki
- = preserve do
- = markdown(@issue.description, cache_key: [@issue, "description"])
- %textarea.hidden.js-task-list-field
- = @issue.description
- = edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue_edited_ago')
+ #merge-requests{ data: { url: referenced_merge_requests_namespace_project_issue_url(@project.namespace, @project, @issue) } }
+ // This element is filled in using JavaScript.
- .merge-requests
- = render 'merge_requests'
- = render 'related_branches'
+ #related-branches{ data: { url: related_branches_namespace_project_issue_url(@project.namespace, @project, @issue) } }
+ // This element is filled in using JavaScript.
- .content-block.content-block-small
- = render 'new_branch'
- = render 'votes/votes_block', votable: @issue
+ .content-block.content-block-small
+ = render 'new_branch'
+ = render 'votes/votes_block', votable: @issue
- .row
- %section.col-md-12
- .issuable-discussion
- = render 'projects/issues/discussion'
+ %section.issuable-discussion
+ = render 'projects/issues/discussion'
= render 'shared/issuable/sidebar', issuable: @issue
diff --git a/app/views/projects/labels/_label.html.haml b/app/views/projects/labels/_label.html.haml
index 097a65969a6..8bf544b8371 100644
--- a/app/views/projects/labels/_label.html.haml
+++ b/app/views/projects/labels/_label.html.haml
@@ -14,7 +14,7 @@
.label-subscription{data: {url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label)}}
.subscription-status{data: {status: label_subscription_status(label)}}
- %a.subscribe-button.btn.action-buttons{data: {toggle: "tooltip"}}
+ %button.js-subscribe-button.label-subscribe-button.btn.action-buttons{ type: "button", data: { toggle: "tooltip" } }
%span= label_subscription_toggle_button_text(label)
- if can? current_user, :admin_label, @project
diff --git a/app/views/projects/merge_requests/_form.html.haml b/app/views/projects/merge_requests/_form.html.haml
index 1e6724fc92b..88525f4036a 100644
--- a/app/views/projects/merge_requests/_form.html.haml
+++ b/app/views/projects/merge_requests/_form.html.haml
@@ -1,4 +1,4 @@
-= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal gfm-form js-requires-input js-quick-submit' } do |f|
+= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal common-note-form js-requires-input js-quick-submit' } do |f|
= render 'shared/issuable/form', f: f, issuable: @merge_request
:javascript
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index 391193eed6c..e740fe8c84d 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -35,7 +35,7 @@
= icon('thumbs-down')
= downvotes
- - note_count = merge_request.mr_and_commit_notes.user.count
+ - note_count = merge_request.mr_and_commit_notes.user.nonawards.count
- if note_count > 0
%li
= link_to merge_request_path(merge_request) + "#notes" do
diff --git a/app/views/projects/merge_requests/_new_compare.html.haml b/app/views/projects/merge_requests/_new_compare.html.haml
index 7d7c487e970..b08524574e4 100644
--- a/app/views/projects/merge_requests/_new_compare.html.haml
+++ b/app/views/projects/merge_requests/_new_compare.html.haml
@@ -16,11 +16,9 @@
= dropdown_title("Select source project")
= dropdown_filter("Search projects")
= dropdown_content do
- - is_active = f.object.source_project_id == @merge_request.source_project.id
- %ul
- %li
- %a{ href: "#", class: "#{("is-active" if is_active)}", data: { id: @merge_request.source_project.id } }
- = @merge_request.source_project_path
+ = render 'projects/merge_requests/dropdowns/project',
+ projects: [@merge_request.source_project],
+ selected: f.object.source_project_id
.merge-request-select.dropdown
= f.hidden_field :source_branch
= dropdown_toggle "Select source branch", { toggle: "dropdown", field_name: "#{f.object_name}[source_branch]" }, { toggle_class: "js-compare-dropdown js-source-branch" }
@@ -28,11 +26,9 @@
= dropdown_title("Select source branch")
= dropdown_filter("Search branches")
= dropdown_content do
- %ul
- - @merge_request.source_branches.each do |branch|
- %li
- %a{ href: "#", class: "#{("is-active" if f.object.source_branch == branch)}", data: { id: branch } }
- = branch
+ = render 'projects/merge_requests/dropdowns/branch',
+ branches: @merge_request.source_branches,
+ selected: f.object.source_branch
.panel-footer
= icon('spinner spin', class: 'js-source-loading')
%ul.list-unstyled.mr_source_commit
@@ -50,11 +46,9 @@
= dropdown_title("Select target project")
= dropdown_filter("Search projects")
= dropdown_content do
- %ul
- - projects.each do |project|
- %li
- %a{ href: "#", class: "#{("is-active" if f.object.target_project_id == project.id)}", data: { id: project.id } }
- = project.path_with_namespace
+ = render 'projects/merge_requests/dropdowns/project',
+ projects: projects,
+ selected: f.object.target_project_id
.merge-request-select.dropdown
= f.hidden_field :target_branch
= dropdown_toggle f.object.target_branch, { toggle: "dropdown", field_name: "#{f.object_name}[target_branch]" }, { toggle_class: "js-compare-dropdown js-target-branch" }
@@ -62,11 +56,9 @@
= dropdown_title("Select target branch")
= dropdown_filter("Search branches")
= dropdown_content do
- %ul
- - @merge_request.target_branches.each do |branch|
- %li
- %a{ href: "#", class: "#{("is-active" if f.object.target_branch == branch)}", data: { id: branch } }
- = branch
+ = render 'projects/merge_requests/dropdowns/branch',
+ branches: @merge_request.target_branches,
+ selected: f.object.target_branch
.panel-footer
= icon('spinner spin', class: "js-target-loading")
%ul.list-unstyled.mr_target_commit
diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml
index 9e59f7df71b..2f14a91e64f 100644
--- a/app/views/projects/merge_requests/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/_new_submit.html.haml
@@ -10,7 +10,7 @@
%span.pull-right
= link_to 'Change branches', mr_change_branches_path(@merge_request)
%hr
-= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal gfm-form js-requires-input' } do |f|
+= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal common-note-form js-requires-input' } do |f|
= render 'shared/issuable/form', f: f, issuable: @merge_request
= f.hidden_field :source_project_id
= f.hidden_field :source_branch
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index 1dd8f721f7e..285ad26316c 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -1,8 +1,7 @@
- page_title "#{@merge_request.title} (#{@merge_request.to_reference})", "Merge Requests"
- page_description @merge_request.description
- page_card_attributes @merge_request.card_attributes
-
-= render "header_title"
+- header_title project_title(@project, "Merge Requests", namespace_project_merge_requests_path(@project.namespace, @project))
- if params[:view] == 'parallel'
- fluid_layout true
@@ -32,8 +31,7 @@
%span Request to merge
%span.label-branch= source_branch_with_namespace(@merge_request)
%span into
- = link_to namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch" do
- = @merge_request.target_branch
+ = link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch"
- if @merge_request.open? && @merge_request.diverged_from_target_branch?
%span (#{pluralize(@merge_request.diverged_commits_count, 'commit')} behind)
@@ -51,7 +49,7 @@
%li.notes-tab
= link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#notes', action: 'notes', toggle: 'tab'} do
Discussion
- %span.badge= @merge_request.mr_and_commit_notes.user.count
+ %span.badge= @merge_request.mr_and_commit_notes.user.nonawards.count
%li.commits-tab
= link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#commits', action: 'commits', toggle: 'tab'} do
Commits
diff --git a/app/views/projects/merge_requests/dropdowns/_branch.html.haml b/app/views/projects/merge_requests/dropdowns/_branch.html.haml
new file mode 100644
index 00000000000..ba8d9a5835c
--- /dev/null
+++ b/app/views/projects/merge_requests/dropdowns/_branch.html.haml
@@ -0,0 +1,5 @@
+%ul
+ - branches.each do |branch|
+ %li
+ %a{ href: '#', class: "#{('is-active' if selected == branch)}", data: { id: branch } }
+ = branch
diff --git a/app/views/projects/merge_requests/dropdowns/_project.html.haml b/app/views/projects/merge_requests/dropdowns/_project.html.haml
new file mode 100644
index 00000000000..25d5dc92f8a
--- /dev/null
+++ b/app/views/projects/merge_requests/dropdowns/_project.html.haml
@@ -0,0 +1,5 @@
+%ul
+ - projects.each do |project|
+ %li
+ %a{ href: "#", class: "#{('is-active' if selected == project.id)}", data: { id: project.id } }
+ = project.path_with_namespace
diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml
index ab4b1f14be5..0a99e8c9591 100644
--- a/app/views/projects/merge_requests/show/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_title.html.haml
@@ -1,35 +1,32 @@
-.detail-page-header
- .status-box{ class: status_box_class(@merge_request) }
- %span.hidden-xs
- = @merge_request.state_human_name
- %span.hidden-sm.hidden-md.hidden-lg
- = icon(@merge_request.state_icon_name)
- %a.btn.btn-default.pull-right.visible-xs-block.gutter-toggle.js-sidebar-toggle{ href: "#" }
- = icon('angle-double-left')
- .issue-meta
- %strong.identifier
- %span.hidden-sm.hidden-md.hidden-lg
- MR
+.clearfix.detail-page-header
+ .issuable-header
+ .issuable-status-box.status-box{ class: status_box_class(@merge_request) }
+ = icon(@merge_request.state_icon_name, class: "hidden-sm hidden-md hidden-lg")
%span.hidden-xs
- Merge Request
- !#{@merge_request.iid}
- %span.creator
- opened
- .editor-details
- = time_ago_with_tooltip(@merge_request.created_at)
- by
- %strong
- = link_to_member(@project, @merge_request.author, size: 24, mobile_classes: "hidden-xs")
- %strong
- = link_to_member(@project, @merge_request.author, size: 24, mobile_classes: "hidden-sm hidden-md hidden-lg",
- by_username: true, avatar: false)
+ = @merge_request.state_human_name
- .issue-btn-group.pull-right
- - if can?(current_user, :update_merge_request, @merge_request)
- - if @merge_request.open?
- = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: 'btn btn-nr btn-grouped btn-close', title: 'Close merge request'
- = link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn btn-nr btn-grouped issuable-edit', id: 'edit_merge_request' do
+ %a.btn.btn-default.pull-right.visible-xs-block.gutter-toggle.issuable-gutter-toggle.js-sidebar-toggle{ href: "#" }
+ = icon('angle-double-left')
+
+ .issuable-meta
+ = issuable_meta(@merge_request, @project, "Merge Request")
+
+ - if can?(current_user, :update_merge_request, @merge_request)
+ .issuable-actions
+ .clearfix.issue-btn-group.dropdown
+ %button.btn.btn-default.pull-left.hidden-md.hidden-lg{ data: { toggle: "dropdown" } }
+ %span.caret
+ Options
+ .dropdown-menu.dropdown-menu-align-right.hidden-lg
+ %ul
+ %li{ class: issue_button_visibility(@merge_request, true) }
+ = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, title: 'Close merge request'
+ %li{ class: issue_button_visibility(@merge_request, false) }
+ = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'reopen-mr-link', title: 'Reopen merge request'
+ %li
+ = link_to 'Edit', edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'issuable-edit', id: 'edit_merge_request'
+ = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: "hidden-xs hidden-sm btn btn-nr btn-grouped btn-close #{issue_button_visibility(@merge_request, true)}", title: 'Close merge request'
+ = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "hidden-xs hidden-sm btn btn-nr btn-grouped btn-reopen reopen-mr-link #{issue_button_visibility(@merge_request, false)}", title: 'Reopen merge request'
+ = link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "hidden-xs hidden-sm btn btn-nr btn-grouped issuable-edit", id: 'edit_merge_request' do
= icon('pencil-square-o')
Edit
- - if @merge_request.closed?
- = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'btn btn-nr btn-grouped btn-reopen reopen-mr-link', title: 'Reopen merge request'
diff --git a/app/views/projects/merge_requests/update_branches.html.haml b/app/views/projects/merge_requests/update_branches.html.haml
index 1b93188a10c..64482973a89 100644
--- a/app/views/projects/merge_requests/update_branches.html.haml
+++ b/app/views/projects/merge_requests/update_branches.html.haml
@@ -1,5 +1,3 @@
-%ul
- - @target_branches.each do |branch|
- %li
- %a{ href: "#", class: "#{("is-active" if "a" == branch)}", data: { id: branch } }
- = branch
+= render 'projects/merge_requests/dropdowns/branch',
+branches: @target_branches,
+selected: nil
diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml
index b2dae1c70ee..687222fa92f 100644
--- a/app/views/projects/milestones/_form.html.haml
+++ b/app/views/projects/milestones/_form.html.haml
@@ -1,6 +1,5 @@
-= form_for [@project.namespace.becomes(Namespace), @project, @milestone], html: {class: 'form-horizontal milestone-form gfm-form js-quick-submit js-requires-input'} do |f|
+= form_for [@project.namespace.becomes(Namespace), @project, @milestone], html: {class: 'form-horizontal milestone-form common-note-form js-quick-submit js-requires-input'} do |f|
= form_errors(@milestone)
-
.row
.col-md-6
.form-group
@@ -11,7 +10,7 @@
= f.label :description, "Description", class: "control-label"
.col-sm-10
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do
- = render 'projects/zen', f: f, attr: :description, classes: 'description form-control'
+ = render 'projects/zen', f: f, attr: :description, classes: 'note-textarea', placeholder: 'Write milestone description...'
= render 'projects/notes/hints'
.clearfix
.error-alert
diff --git a/app/views/projects/notes/_edit_form.html.haml b/app/views/projects/notes/_edit_form.html.haml
index 23e4f93eab5..c87a3fadf72 100644
--- a/app/views/projects/notes/_edit_form.html.haml
+++ b/app/views/projects/notes/_edit_form.html.haml
@@ -2,7 +2,7 @@
= form_for note, url: namespace_project_note_path(@project.namespace, @project, note), method: :put, remote: true, authenticity_token: true, html: { class: 'edit-note common-note-form js-quick-submit' } do |f|
= note_target_fields(note)
= render layout: 'projects/md_preview', locals: { preview_class: 'md-preview' } do
- = render 'projects/zen', f: f, attr: :note, classes: 'note-textarea js-note-text js-task-list-field'
+ = render 'projects/zen', f: f, attr: :note, classes: 'note-textarea js-note-text js-task-list-field', placeholder: "Write a comment or drag your files here..."
= render 'projects/notes/hints'
.note-form-actions.clearfix
diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml
index c446ecec2c3..d0ac380f216 100644
--- a/app/views/projects/notes/_form.html.haml
+++ b/app/views/projects/notes/_form.html.haml
@@ -1,4 +1,4 @@
-= form_for [@project.namespace.becomes(Namespace), @project, @note], remote: true, html: { :'data-type' => 'json', multipart: true, id: nil, class: "new-note js-new-note-form js-quick-submit common-note-form gfm-form" }, authenticity_token: true do |f|
+= form_for [@project.namespace.becomes(Namespace), @project, @note], remote: true, html: { :'data-type' => 'json', multipart: true, id: nil, class: "new-note js-new-note-form js-quick-submit common-note-form" }, authenticity_token: true do |f|
= hidden_field_tag :view, diff_view
= hidden_field_tag :line_type
= note_target_fields(@note)
@@ -8,7 +8,7 @@
= f.hidden_field :noteable_type
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do
- = render 'projects/zen', f: f, attr: :note, classes: 'note-textarea js-note-text'
+ = render 'projects/zen', f: f, attr: :note, classes: 'note-textarea js-note-text', placeholder: "Write a comment or drag your files here..."
= render 'projects/notes/hints'
.error-alert
diff --git a/app/views/projects/notes/discussions/_diff.html.haml b/app/views/projects/notes/discussions/_diff.html.haml
index 820e31ccd61..d46aab000c3 100644
--- a/app/views/projects/notes/discussions/_diff.html.haml
+++ b/app/views/projects/notes/discussions/_diff.html.haml
@@ -20,11 +20,9 @@
%td.new_line.diff-line-num= "..."
%td.line_content.match= line.text
- else
- %td.old_line.diff-line-num
- = raw(type == "new" ? "&nbsp;" : line.old_pos)
- %td.new_line.diff-line-num
- = raw(type == "old" ? "&nbsp;" : line.new_pos)
- %td.line_content{class: "noteable_line #{type} #{line_code}", line_code: line_code}= diff_line_content(line.text)
+ %td.old_line.diff-line-num{ data: { linenumber: type == "new" ? "&nbsp;".html_safe : line.old_pos } }
+ %td.new_line.diff-line-num{ data: { linenumber: type == "old" ? "&nbsp;".html_safe : line.new_pos } }
+ %td.line_content{ class: ['noteable_line', type, line_code], line_code: line_code }= diff_line_content(line.text, type)
- if line_code == note.line_code
= render "projects/notes/diff_notes_with_reply", notes: discussion_notes
diff --git a/app/views/projects/releases/edit.html.haml b/app/views/projects/releases/edit.html.haml
index c4a3f06ee06..6f0b32aa165 100644
--- a/app/views/projects/releases/edit.html.haml
+++ b/app/views/projects/releases/edit.html.haml
@@ -9,11 +9,11 @@
%strong #{@tag.name}
.prepend-top-default
- = form_for(@release, method: :put, url: namespace_project_tag_release_path(@project.namespace, @project, @tag.name), html: { class: 'form-horizontal gfm-form release-form js-quick-submit' }) do |f|
+ = form_for(@release, method: :put, url: namespace_project_tag_release_path(@project.namespace, @project, @tag.name), html: { class: 'form-horizontal common-note-form release-form js-quick-submit' }) do |f|
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do
- = render 'projects/zen', f: f, attr: :description, classes: 'description form-control'
+ = render 'projects/zen', f: f, attr: :description, classes: 'note-textarea', placeholder: "Write your release notes or drag files here..."
= render 'projects/notes/hints'
- .error-alert
- .form-actions.prepend-top-default
- = f.submit 'Save changes', class: 'btn btn-save'
- = link_to "Cancel", namespace_project_tag_path(@project.namespace, @project, @tag.name), class: "btn btn-default btn-cancel"
+ .error-alert
+ .form-actions.prepend-top-default
+ = f.submit 'Save changes', class: 'btn btn-save'
+ = link_to "Cancel", namespace_project_tag_path(@project.namespace, @project, @tag.name), class: "btn btn-default btn-cancel"
diff --git a/app/views/projects/tags/new.html.haml b/app/views/projects/tags/new.html.haml
index 77c7c4d23de..b40a6e5cb2d 100644
--- a/app/views/projects/tags/new.html.haml
+++ b/app/views/projects/tags/new.html.haml
@@ -10,7 +10,7 @@
New Tag
%hr
-= form_tag namespace_project_tags_path, method: :post, id: "new-tag-form", class: "form-horizontal gfm-form tag-form js-quick-submit js-requires-input" do
+= form_tag namespace_project_tags_path, method: :post, id: "new-tag-form", class: "form-horizontal common-note-form tag-form js-quick-submit js-requires-input" do
.form-group
= label_tag :tag_name, nil, class: 'control-label'
.col-sm-10
@@ -30,9 +30,9 @@
= label_tag :release_description, 'Release notes', class: 'control-label'
.col-sm-10
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do
- = render 'projects/zen', attr: :release_description, classes: 'description form-control'
+ = render 'projects/zen', attr: :release_description, classes: 'note-textarea', placeholder: "Write your release notes or drag files here..."
= render 'projects/notes/hints'
- .help-block Optionally, add release notes to the tag. They will be stored in the GitLab database and displayed on the tags page.
+ .help-block Optionally, add release notes to the tag. They will be stored in the GitLab database and displayed on the tags page.
.form-actions
= button_tag 'Create tag', class: 'btn btn-create', tabindex: 3
= link_to 'Cancel', namespace_project_tags_path(@project.namespace, @project), class: 'btn btn-cancel'
diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml
index 812876e2835..797a1a59e9f 100644
--- a/app/views/projects/wikis/_form.html.haml
+++ b/app/views/projects/wikis/_form.html.haml
@@ -1,4 +1,4 @@
-= form_for [@project.namespace.becomes(Namespace), @project, @page], method: @page.persisted? ? :put : :post, html: { class: 'form-horizontal wiki-form gfm-form prepend-top-default js-quick-submit' } do |f|
+= form_for [@project.namespace.becomes(Namespace), @project, @page], method: @page.persisted? ? :put : :post, html: { class: 'form-horizontal wiki-form common-note-form prepend-top-default js-quick-submit' } do |f|
= form_errors(@page)
= f.hidden_field :title, value: @page.title
@@ -11,7 +11,7 @@
= f.label :content, class: 'control-label'
.col-sm-10
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do
- = render 'projects/zen', f: f, attr: :content, classes: 'description form-control'
+ = render 'projects/zen', f: f, attr: :content, classes: 'note-textarea', placeholder: 'Write your content or drag files here...'
= render 'projects/notes/hints'
.clearfix
diff --git a/app/views/repository_check_mailer/notify.html.haml b/app/views/repository_check_mailer/notify.html.haml
new file mode 100644
index 00000000000..df16f503570
--- /dev/null
+++ b/app/views/repository_check_mailer/notify.html.haml
@@ -0,0 +1,5 @@
+%p
+ #{@message}.
+
+%p
+ = link_to "See the affected projects in the GitLab admin panel", admin_namespaces_projects_url(last_repository_check_failed: 1)
diff --git a/app/views/repository_check_mailer/notify.text.haml b/app/views/repository_check_mailer/notify.text.haml
new file mode 100644
index 00000000000..02f3f80288a
--- /dev/null
+++ b/app/views/repository_check_mailer/notify.text.haml
@@ -0,0 +1,3 @@
+#{@message}.
+\
+View details: #{admin_namespaces_projects_url(last_repository_check_failed: 1)}
diff --git a/app/views/search/results/_note.html.haml b/app/views/search/results/_note.html.haml
index 9544e3d3e17..d9400b1d9fa 100644
--- a/app/views/search/results/_note.html.haml
+++ b/app/views/search/results/_note.html.haml
@@ -1,5 +1,5 @@
- project = note.project
-- note_url = Gitlab::UrlBuilder.new(:note).build(note.id)
+- note_url = Gitlab::UrlBuilder.build(note)
- noteable_identifier = note.noteable.try(:iid) || note.noteable.id
.search-result-row
%h5.note-search-caption.str-truncated
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index 757a3812deb..bae15b7f844 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -4,7 +4,7 @@
= f.label :title, class: 'control-label'
.col-sm-10
= f.text_field :title, maxlength: 255, autofocus: true, autocomplete: 'off',
- class: 'form-control pad js-gfm-input', required: true
+ class: 'form-control pad', required: true
- if issuable.is_a?(MergeRequest)
%p.help-block
@@ -29,7 +29,8 @@
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do
= render 'projects/zen', f: f, attr: :description,
- classes: 'description form-control'
+ classes: 'note-textarea',
+ placeholder: "Write a comment or drag your files here..."
= render 'projects/notes/hints'
.clearfix
.error-alert
@@ -70,13 +71,13 @@
- if can? current_user, :admin_milestone, issuable.project
= link_to 'Create new milestone', new_namespace_project_milestone_path(issuable.project.namespace, issuable.project), target: :blank
.form-group
+ - has_labels = issuable.project.labels.any?
= f.label :label_ids, "Labels", class: 'control-label'
- .col-sm-10
- - if issuable.project.labels.any?
+ .col-sm-10{ class: ('issuable-form-padding-top' if !has_labels) }
+ - if has_labels
= f.collection_select :label_ids, issuable.project.labels.all, :id, :name,
{ selected: issuable.label_ids }, multiple: true, class: 'select2', data: { placeholder: "Select labels" }
- else
- .prepend-top-10
%span.light No labels yet.
&nbsp;
- if can? current_user, :admin_label, issuable.project
@@ -128,8 +129,6 @@
- else
.pull-right
- if current_user.can?(:"destroy_#{issuable.to_ability_name}", @project)
- = link_to polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), data: { confirm: "#{issuable.class.name.titleize} will be removed! Are you sure?" },
- method: :delete, class: 'btn btn-grouped' do
- = icon('trash-o')
- Delete
+ = link_to 'Delete', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), data: { confirm: "#{issuable.class.name.titleize} will be removed! Are you sure?" },
+ method: :delete, class: 'btn btn-danger btn-grouped'
= link_to 'Cancel', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), class: 'btn btn-grouped btn-cancel'
diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml
index fd5e58c1f1f..f722e61eeac 100644
--- a/app/views/shared/issuable/_label_dropdown.html.haml
+++ b/app/views/shared/issuable/_label_dropdown.html.haml
@@ -1,7 +1,7 @@
- if params[:label_name].present?
= hidden_field_tag(:label_name, params[:label_name])
.dropdown
- %button.dropdown-menu-toggle.js-label-select.js-filter-submit{type: "button", data: {toggle: "dropdown", field_name: "label_name", show_no: "true", show_any: "true", selected: params[:label_name], project_id: @project.try(:id), labels: labels_filter_path, default_label: "Label"}}
+ %button.dropdown-menu-toggle.js-label-select.js-filter-submit.js-extra-options{type: "button", data: {toggle: "dropdown", field_name: "label_name", show_no: "true", show_any: "true", selected: params[:label_name], project_id: @project.try(:id), labels: labels_filter_path, default_label: "Label"}}
%span.dropdown-toggle-text
= h(params[:label_name].presence || "Label")
= icon('chevron-down')
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index 94affa4b59a..08bfd93f4e6 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -49,7 +49,7 @@
.selectbox.hide-collapsed
= f.hidden_field 'assignee_id', value: issuable.assignee_id, id: 'issue_assignee_id'
- = dropdown_tag('Select assignee', options: { toggle_class: 'js-user-search js-author-search', title: 'Assign to', filter: true, dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author', placeholder: 'Search users', data: { first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), field_name: "#{issuable.to_ability_name}[assignee_id]", issue_update: issuable_json_path(issuable), ability_name: issuable.to_ability_name, null_user: true } })
+ = dropdown_tag('Select assignee', options: { toggle_class: 'js-user-search js-author-search', title: 'Assign to', filter: true, dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author', placeholder: 'Search users', data: { first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), author_id: issuable.author_id, field_name: "#{issuable.to_ability_name}[assignee_id]", issue_update: issuable_json_path(issuable), ability_name: issuable.to_ability_name, null_user: true } })
.block.milestone
.sidebar-collapsed-icon
@@ -128,7 +128,7 @@
.title.hide-collapsed
Notifications
- subscribtion_status = subscribed ? 'subscribed' : 'unsubscribed'
- %button.btn.btn-block.btn-gray.subscribe-button.hide-collapsed{:type => 'button'}
+ %button.btn.btn-block.btn-gray.js-subscribe-button.issuable-subscribe-button.hide-collapsed{ type: "button" }
%span= subscribed ? 'Unsubscribe' : 'Subscribe'
.subscription-status.hide-collapsed{data: {status: subscribtion_status}}
.unsubscribed{class: ( 'hidden' if subscribed )}
@@ -152,4 +152,4 @@
new LabelsSelect();
new IssuableContext('#{current_user.to_json(only: [:username, :id, :name])}');
new Subscription('.subscription')
- new Sidebar(); \ No newline at end of file
+ new Sidebar();
diff --git a/app/views/shared/milestones/_labels_tab.html.haml b/app/views/shared/milestones/_labels_tab.html.haml
index 868b2357003..b15e8ea73fe 100644
--- a/app/views/shared/milestones/_labels_tab.html.haml
+++ b/app/views/shared/milestones/_labels_tab.html.haml
@@ -4,15 +4,16 @@
%li
%span.label-row
- = link_to milestones_label_path(options) do
- - render_colored_label(label, tooltip: false)
- %span.prepend-left-10
+ %span.label-name
+ = link_to milestones_label_path(options) do
+ - render_colored_label(label, tooltip: false)
+ %span.prepend-description-left
= markdown(label.description, pipeline: :single_line)
- .pull-right
- %strong.issues-count
+ .pull-info-right
+ %span.append-right-20
= link_to milestones_label_path(options.merge(state: 'opened')) do
- pluralize milestone_issues_by_label_count(@milestone, label, state: :opened), 'open issue'
- %strong.issues-count
+ %span.append-right-20
= link_to milestones_label_path(options.merge(state: 'closed')) do
- pluralize milestone_issues_by_label_count(@milestone, label, state: :closed), 'closed issue'
diff --git a/app/workers/admin_email_worker.rb b/app/workers/admin_email_worker.rb
new file mode 100644
index 00000000000..667fff031dd
--- /dev/null
+++ b/app/workers/admin_email_worker.rb
@@ -0,0 +1,12 @@
+class AdminEmailWorker
+ include Sidekiq::Worker
+
+ sidekiq_options retry: false # this job auto-repeats via sidekiq-cron
+
+ def perform
+ repository_check_failed_count = Project.where(last_repository_check_failed: true).count
+ return if repository_check_failed_count.zero?
+
+ RepositoryCheckMailer.notify(repository_check_failed_count).deliver_now
+ end
+end
diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb
index 3cc232ef1ae..9e1215b21a6 100644
--- a/app/workers/post_receive.rb
+++ b/app/workers/post_receive.rb
@@ -40,7 +40,7 @@ class PostReceive
if Gitlab::Git.tag_ref?(ref)
GitTagPushService.new.execute(post_received.project, @user, oldrev, newrev, ref)
- else
+ elsif Gitlab::Git.branch_ref?(ref)
GitPushService.new(post_received.project, @user, oldrev: oldrev, newrev: newrev, ref: ref).execute
end
end
diff --git a/app/workers/repository_check/batch_worker.rb b/app/workers/repository_check/batch_worker.rb
new file mode 100644
index 00000000000..44b3145d50f
--- /dev/null
+++ b/app/workers/repository_check/batch_worker.rb
@@ -0,0 +1,63 @@
+module RepositoryCheck
+ class BatchWorker
+ include Sidekiq::Worker
+
+ RUN_TIME = 3600
+
+ sidekiq_options retry: false
+
+ def perform
+ start = Time.now
+
+ # This loop will break after a little more than one hour ('a little
+ # more' because `git fsck` may take a few minutes), or if it runs out of
+ # projects to check. By default sidekiq-cron will start a new
+ # RepositoryCheckWorker each hour so that as long as there are repositories to
+ # check, only one (or two) will be checked at a time.
+ project_ids.each do |project_id|
+ break if Time.now - start >= RUN_TIME
+ break unless current_settings.repository_checks_enabled
+
+ next unless try_obtain_lease(project_id)
+
+ SingleRepositoryWorker.new.perform(project_id)
+ end
+ end
+
+ private
+
+ # Project.find_each does not support WHERE clauses and
+ # Project.find_in_batches does not support ordering. So we just build an
+ # array of ID's. This is OK because we do it only once an hour, because
+ # getting ID's from Postgres is not terribly slow, and because no user
+ # has to sit and wait for this query to finish.
+ def project_ids
+ limit = 10_000
+ never_checked_projects = Project.where('last_repository_check_at IS NULL').limit(limit).
+ pluck(:id)
+ old_check_projects = Project.where('last_repository_check_at < ?', 1.month.ago).
+ reorder('last_repository_check_at ASC').limit(limit).pluck(:id)
+ never_checked_projects + old_check_projects
+ end
+
+ def try_obtain_lease(id)
+ # Use a 24-hour timeout because on servers/projects where 'git fsck' is
+ # super slow we definitely do not want to run it twice in parallel.
+ Gitlab::ExclusiveLease.new(
+ "project_repository_check:#{id}",
+ timeout: 24.hours
+ ).try_obtain
+ end
+
+ def current_settings
+ # No caching of the settings! If we cache them and an admin disables
+ # this feature, an active RepositoryCheckWorker would keep going for up
+ # to 1 hour after the feature was disabled.
+ if Rails.env.test?
+ Gitlab::CurrentSettings.fake_application_settings
+ else
+ ApplicationSetting.current
+ end
+ end
+ end
+end
diff --git a/app/workers/repository_check/clear_worker.rb b/app/workers/repository_check/clear_worker.rb
new file mode 100644
index 00000000000..b7202ddff34
--- /dev/null
+++ b/app/workers/repository_check/clear_worker.rb
@@ -0,0 +1,17 @@
+module RepositoryCheck
+ class ClearWorker
+ include Sidekiq::Worker
+
+ sidekiq_options retry: false
+
+ def perform
+ # Do small batched updates because these updates will be slow and locking
+ Project.select(:id).find_in_batches(batch_size: 100) do |batch|
+ Project.where(id: batch.map(&:id)).update_all(
+ last_repository_check_failed: nil,
+ last_repository_check_at: nil,
+ )
+ end
+ end
+ end
+end
diff --git a/app/workers/repository_check/single_repository_worker.rb b/app/workers/repository_check/single_repository_worker.rb
new file mode 100644
index 00000000000..e54ae86d06c
--- /dev/null
+++ b/app/workers/repository_check/single_repository_worker.rb
@@ -0,0 +1,36 @@
+module RepositoryCheck
+ class SingleRepositoryWorker
+ include Sidekiq::Worker
+
+ sidekiq_options retry: false
+
+ def perform(project_id)
+ project = Project.find(project_id)
+ project.update_columns(
+ last_repository_check_failed: !check(project),
+ last_repository_check_at: Time.now,
+ )
+ end
+
+ private
+
+ def check(project)
+ # Use 'map do', not 'all? do', to prevent short-circuiting
+ [project.repository, project.wiki.repository].map do |repository|
+ git_fsck(repository.path_to_repo)
+ end.all?
+ end
+
+ def git_fsck(path)
+ cmd = %W(nice git --git-dir=#{path} fsck)
+ output, status = Gitlab::Popen.popen(cmd)
+
+ if status.zero?
+ true
+ else
+ Gitlab::RepositoryCheckLogger.error("command failed: #{cmd.join(' ')}\n#{output}")
+ false
+ end
+ end
+ end
+end