summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorTimothy Andrew <mail@timothyandrew.net>2016-04-15 09:50:53 +0530
committerTimothy Andrew <mail@timothyandrew.net>2016-04-15 09:50:53 +0530
commit953bafff90f33d93d1b138501643a94beda11d53 (patch)
treeb1d77a923b50cbac451a76e2f4ba4a64fe21904a /app
parentf5ce601c2b052b18398d2207c64ce8828b628521 (diff)
parenta1497ba39c69cf57f0446a705448bef2aafcd7f0 (diff)
downloadgitlab-ce-953bafff90f33d93d1b138501643a94beda11d53.tar.gz
Merge remote-tracking branch 'origin/master' into 14566-confidential-issue-branches
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/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/stylesheets/framework/gfm.scss18
-rw-r--r--app/assets/stylesheets/framework/markdown_area.scss13
-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/diff.scss38
-rw-r--r--app/assets/stylesheets/pages/issuable.scss12
-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/groups_controller.rb1
-rw-r--r--app/controllers/oauth/applications_controller.rb2
-rw-r--r--app/controllers/projects/issues_controller.rb39
-rw-r--r--app/controllers/projects/repositories_controller.rb1
-rw-r--r--app/helpers/diff_helper.rb3
-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/issue.rb7
-rw-r--r--app/models/merge_request.rb6
-rw-r--r--app/models/oauth_access_token.rb19
-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.rb2
-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/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/layouts/header/_default.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/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/show.html.haml8
-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/_new_submit.html.haml2
-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.haml15
-rw-r--r--app/views/shared/issuable/_label_dropdown.html.haml2
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml4
-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
85 files changed, 695 insertions, 291 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/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/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/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/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/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..6bd90a23620 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;
@@ -322,3 +316,9 @@
color: #8c8c8c;
}
}
+
+.issuable-form-padding-top {
+ @media (min-width: $screen-sm-min) {
+ padding-top: 7px;
+ }
+}
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/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/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/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index a3842036982..f3056b21007 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
@@ -65,8 +63,6 @@ class Projects::IssuesController < Projects::ApplicationController
@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(current_user)
respond_to do |format|
format.html
@@ -118,15 +114,38 @@ 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(current_user)
+
+ 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/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/issue.rb b/app/models/issue.rb
index 252545d1147..530d2107596 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -105,11 +105,10 @@ class Issue < ActiveRecord::Base
end
# All branches containing the current issue's ID, except for
- # those with a merge request open (that the current user can see)
- # referencing the current issue.
+ # those with a merge request open referencing the current issue.
def related_branches(current_user)
branches_with_iid = project.repository.branch_names.select do |branch|
- branch.end_with?("-#{iid}")
+ branch =~ /\A#{iid}-(?!\d+-stable)/i
end
branches_with_merge_request = self.referenced_merge_requests(current_user).map(&:source_branch)
@@ -161,7 +160,7 @@ class Issue < ActiveRecord::Base
if self.confidential?
"issue-#{iid}"
else
- "#{title.parameterize}-#{iid}"
+ "#{iid}-#{title.parameterize}"
end
end
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_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..0b2289cfa39 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -253,6 +253,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)
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/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/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/_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/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/show.html.haml b/app/views/projects/issues/show.html.haml
index 6fa059cbe68..5fe5ddc0819 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -64,9 +64,11 @@
= @issue.description
= edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue_edited_ago')
- .merge-requests
- = render 'merge_requests'
- = render 'related_branches'
+ #merge-requests{'data-url' => referenced_merge_requests_namespace_project_issue_url(@project.namespace, @project, @issue)}
+ // This element is filled in using JavaScript.
+
+ #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'
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/_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/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..aed2622a6da 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -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..56c8eaa0597 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -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