diff options
author | Alfredo Sumaran <alfredo@gitlab.com> | 2016-04-21 16:03:33 -0500 |
---|---|---|
committer | Alfredo Sumaran <alfredo@gitlab.com> | 2016-04-21 16:03:33 -0500 |
commit | c9fe9f59d27bacb60c264678cce3cbe45d514a69 (patch) | |
tree | 0c7c497a5fbb96264d0c19a08aee17ad31f7fb05 /app | |
parent | 80ae3790b456b12314243c75632ed6024ac16424 (diff) | |
parent | c22c4048ce1a4233c0a6f9a9cd59a53efe49da53 (diff) | |
download | gitlab-ce-c9fe9f59d27bacb60c264678cce3cbe45d514a69.tar.gz |
Merge remote-tracking branch 'origin/master' into issue_3799
Diffstat (limited to 'app')
176 files changed, 1880 insertions, 980 deletions
diff --git a/app/assets/javascripts/api.js.coffee b/app/assets/javascripts/api.js.coffee index f3ed9a66715..dd1bbb37551 100644 --- a/app/assets/javascripts/api.js.coffee +++ b/app/assets/javascripts/api.js.coffee @@ -5,6 +5,7 @@ group_projects_path: "/api/:version/groups/:id/projects.json" projects_path: "/api/:version/projects.json" labels_path: "/api/:version/projects/:id/labels" + license_path: "/api/:version/licenses/:key" group: (group_id, callback) -> url = Api.buildUrl(Api.group_path) @@ -92,6 +93,16 @@ ).done (projects) -> callback(projects) + # Return text for a specific license + licenseText: (key, data, callback) -> + url = Api.buildUrl(Api.license_path).replace(':key', key) + + $.ajax( + url: url + data: data + ).done (license) -> + callback(license) + buildUrl: (url) -> url = gon.relative_url_root + url if gon.relative_url_root? return url.replace(':version', gon.api_version) diff --git a/app/assets/javascripts/blob/blob_license_selector.js.coffee b/app/assets/javascripts/blob/blob_license_selector.js.coffee new file mode 100644 index 00000000000..e17eaa75dc1 --- /dev/null +++ b/app/assets/javascripts/blob/blob_license_selector.js.coffee @@ -0,0 +1,30 @@ +class @BlobLicenseSelector + licenseRegex: /^(.+\/)?(licen[sc]e|copying)($|\.)/i + + constructor: (editor) -> + @$licenseSelector = $('.js-license-selector') + $fileNameInput = $('#file_name') + + initialFileNameValue = if $fileNameInput.length + $fileNameInput.val() + else if $('.editor-file-name').length + $('.editor-file-name').text().trim() + + @toggleLicenseSelector(initialFileNameValue) + + if $fileNameInput + $fileNameInput.on 'keyup blur', (e) => + @toggleLicenseSelector($(e.target).val()) + + $('select.license-select').on 'change', (e) -> + data = + project: $(this).data('project') + fullname: $(this).data('fullname') + Api.licenseText $(this).val(), data, (license) -> + editor.setValue(license.content, -1) + + toggleLicenseSelector: (fileName) => + if @licenseRegex.test(fileName) + @$licenseSelector.show() + else + @$licenseSelector.hide() diff --git a/app/assets/javascripts/blob/edit_blob.js.coffee b/app/assets/javascripts/blob/edit_blob.js.coffee index 390e41ed8d4..eea9aa972ee 100644 --- a/app/assets/javascripts/blob/edit_blob.js.coffee +++ b/app/assets/javascripts/blob/edit_blob.js.coffee @@ -1,44 +1,39 @@ class @EditBlob - constructor: (assets_path, mode)-> - ace.config.set "modePath", assets_path + '/ace' + constructor: (assets_path, ace_mode = null) -> + ace.config.set "modePath", "#{assets_path}/ace" ace.config.loadModule "ace/ext/searchbox" - if mode - ace_mode = mode - editor = ace.edit("editor") - editor.focus() - @editor = editor - - if ace_mode - editor.getSession().setMode "ace/mode/" + ace_mode + @editor = ace.edit("editor") + @editor.focus() + @editor.getSession().setMode "ace/mode/#{ace_mode}" if ace_mode # Before a form submission, move the content from the Ace editor into the # submitted textarea - $('form').submit -> - $("#file-content").val(editor.getValue()) + $('form').submit => + $("#file-content").val(@editor.getValue()) + + @initModePanesAndLinks() + new BlobLicenseSelector(@editor) - editModePanes = $(".js-edit-mode-pane") - editModeLinks = $(".js-edit-mode a") - editModeLinks.click (event) -> - event.preventDefault() - currentLink = $(this) - paneId = currentLink.attr("href") - currentPane = editModePanes.filter(paneId) - editModeLinks.parent().removeClass "active hover" - currentLink.parent().addClass "active hover" - editModePanes.hide() - if paneId is "#preview" - currentPane.fadeIn 200 - $.post currentLink.data("preview-url"), - content: editor.getValue() - , (response) -> - currentPane.empty().append response - currentPane.syntaxHighlight() - return + initModePanesAndLinks: -> + @$editModePanes = $(".js-edit-mode-pane") + @$editModeLinks = $(".js-edit-mode a") + @$editModeLinks.click @editModeLinkClickHandler - else - currentPane.fadeIn 200 - editor.focus() - return + editModeLinkClickHandler: (event) => + event.preventDefault() + currentLink = $(event.target) + paneId = currentLink.attr("href") + currentPane = @$editModePanes.filter(paneId) + @$editModeLinks.parent().removeClass "active hover" + currentLink.parent().addClass "active hover" + @$editModePanes.hide() + currentPane.fadeIn 200 + if paneId is "#preview" + $.post currentLink.data("preview-url"), + content: @editor.getValue() + , (response) -> + currentPane.empty().append response + currentPane.syntaxHighlight() - editor: -> - return @editor + else + @editor.focus() diff --git a/app/assets/javascripts/blob/new_blob.js.coffee b/app/assets/javascripts/blob/new_blob.js.coffee deleted file mode 100644 index 68c5e5195e3..00000000000 --- a/app/assets/javascripts/blob/new_blob.js.coffee +++ /dev/null @@ -1,20 +0,0 @@ -class @NewBlob - constructor: (assets_path, mode)-> - ace.config.set "modePath", assets_path + '/ace' - ace.config.loadModule "ace/ext/searchbox" - if mode - ace_mode = mode - editor = ace.edit("editor") - editor.focus() - @editor = editor - - if ace_mode - editor.getSession().setMode "ace/mode/" + ace_mode - - # Before a form submission, move the content from the Ace editor into the - # submitted textarea - $('form').submit -> - $("#file-content").val(editor.getValue()) - - editor: -> - return @editor diff --git a/app/assets/javascripts/commits.js.coffee b/app/assets/javascripts/commits.js.coffee index ffd3627b1b0..0acb4c1955e 100644 --- a/app/assets/javascripts/commits.js.coffee +++ b/app/assets/javascripts/commits.js.coffee @@ -1,7 +1,7 @@ class @CommitsList @timer = null - @init: (ref, limit) -> + @init: (limit) -> $("body").on "click", ".day-commits-table li.commit", (event) -> if event.target.nodeName != "A" location.href = $(this).attr("url") diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index 0b9110d35fa..2fdb7562515 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -17,6 +17,7 @@ class Dispatcher switch page when 'projects:issues:index' Issues.init() + Issuable.init() shortcut_handler = new ShortcutsNavigation() when 'projects:issues:show' new Issue() @@ -57,7 +58,7 @@ class Dispatcher new ZenMode() when 'projects:merge_requests:index' shortcut_handler = new ShortcutsNavigation() - MergeRequests.init() + Issuable.init() when 'dashboard:activity' new Activities() when 'dashboard:projects:starred' diff --git a/app/assets/javascripts/due_date_select.js.coffee b/app/assets/javascripts/due_date_select.js.coffee new file mode 100644 index 00000000000..a4304786cbb --- /dev/null +++ b/app/assets/javascripts/due_date_select.js.coffee @@ -0,0 +1,64 @@ +class @DueDateSelect + constructor: -> + $loading = $('.js-issuable-update .due_date') + .find('.block-loading') + .hide() + + $('.js-due-date-select').each (i, dropdown) -> + $dropdown = $(dropdown) + $dropdownParent = $dropdown.closest('.dropdown') + $datePicker = $dropdownParent.find('.js-due-date-calendar') + $block = $dropdown.closest('.block') + $selectbox = $dropdown.closest('.selectbox') + $value = $block.find('.value') + $sidebarValue = $('.js-due-date-sidebar-value', $block) + + fieldName = $dropdown.data('field-name') + abilityName = $dropdown.data('ability-name') + issueUpdateURL = $dropdown.data('issue-update') + + $dropdown.glDropdown( + hidden: -> + $selectbox.hide() + $value.removeAttr('style') + ) + + addDueDate = -> + # Create the post date + value = $("input[name='#{fieldName}']").val() + date = new Date value.replace(new RegExp('-', 'g'), ',') + mediumDate = $.datepicker.formatDate 'M d, yy', date + + data = {} + data[abilityName] = {} + data[abilityName].due_date = value + + $.ajax( + type: 'PUT' + url: issueUpdateURL + data: data + beforeSend: -> + $loading.fadeIn() + $dropdown.trigger('loading.gl.dropdown') + $selectbox.hide() + $value.removeAttr('style') + + $value.html(mediumDate) + $sidebarValue.html(mediumDate) + ).done (data) -> + $dropdown.trigger('loaded.gl.dropdown') + $dropdown.dropdown('toggle') + $loading.fadeOut() + + $datePicker.datepicker( + dateFormat: 'yy-mm-dd', + defaultDate: $("input[name='#{fieldName}']").val() + altField: "input[name='#{fieldName}']" + onSelect: -> + addDueDate() + ) + + $(document) + .off 'click', '.ui-datepicker-header a' + .on 'click', '.ui-datepicker-header a', (e) -> + e.stopImmediatePropagation() diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee index fac928ef202..29466e9f2ed 100644 --- a/app/assets/javascripts/gl_dropdown.js.coffee +++ b/app/assets/javascripts/gl_dropdown.js.coffee @@ -154,6 +154,9 @@ class GitLabDropdown @fullData = data @parseData @fullData + + if @options.filterable + @filterInput.trigger 'keyup' } # Init filterable @@ -218,6 +221,9 @@ class GitLabDropdown menu.toggleClass PAGE_TWO_CLASS + # Focus first visible input on active page + @dropdown.find('[class^="dropdown-page-"]:visible :text:visible:first').focus() + parseData: (data) -> @renderedData = data @@ -237,7 +243,8 @@ class GitLabDropdown shouldPropagate: (e) => if @options.multiSelect $target = $(e.target) - if not $target.hasClass('dropdown-menu-close') and not $target.hasClass('dropdown-menu-close-icon') + + if not $target.hasClass('dropdown-menu-close') and not $target.hasClass('dropdown-menu-close-icon') and not $target.data('is-link') e.stopPropagation() return false else @@ -372,7 +379,6 @@ class GitLabDropdown selectedObject = @renderedData[selectedIndex] value = if @options.id then @options.id(selectedObject, el) else selectedObject.id field = @dropdown.parent().find("input[name='#{fieldName}'][value='#{value}']") - if el.hasClass(ACTIVE_CLASS) el.removeClass(ACTIVE_CLASS) field.remove() @@ -383,13 +389,13 @@ class GitLabDropdown else selectedObject else - if !value? - field.remove() - - if not @options.multiSelect + if not @options.multiSelect or el.hasClass('dropdown-clear-active') @dropdown.find(".#{ACTIVE_CLASS}").removeClass ACTIVE_CLASS @dropdown.parent().find("input[name='#{fieldName}']").remove() + if !value? + field.remove() + # Toggle active class for the tick mark el.addClass ACTIVE_CLASS diff --git a/app/assets/javascripts/issuable.js.coffee b/app/assets/javascripts/issuable.js.coffee new file mode 100644 index 00000000000..afffed63ac5 --- /dev/null +++ b/app/assets/javascripts/issuable.js.coffee @@ -0,0 +1,84 @@ +@Issuable = + init: -> + Issuable.initTemplates() + Issuable.initSearch() + + initTemplates: -> + Issuable.labelRow = _.template( + '<% _.each(labels, function(label){ %> + <span class="label-row"> + <a href="#"><span class="label color-label has-tooltip" style="background-color: <%= label.color %>; color: <%= label.text_color %>" title="<%= _.escape(label.description) %>" data-container="body"><%= _.escape(label.title) %></span></a> + </span> + <% }); %>' + ) + + initSearch: -> + @timer = null + $('#issue_search') + .off 'keyup' + .on 'keyup', -> + clearTimeout(@timer) + @timer = setTimeout( -> + Issuable.filterResults $('#issue_search_form') + , 500) + + toggleLabelFilters: -> + $filteredLabels = $('.filtered-labels') + if $filteredLabels.find('.label-row').length > 0 + $filteredLabels.removeClass('hidden') + else + $filteredLabels.addClass('hidden') + + filterResults: (form) => + formData = form.serialize() + + $('.issues-holder, .merge-requests-holder').css('opacity', '0.5') + formAction = form.attr('action') + issuesUrl = formAction + issuesUrl += ("#{if formAction.indexOf('?') < 0 then '?' else '&'}") + issuesUrl += formData + $.ajax + type: 'GET' + url: formAction + data: formData + complete: -> + $('.issues-holder, .merge-requests-holder').css('opacity', '1.0') + success: (data) -> + $('.issues-holder, .merge-requests-holder').html(data.html) + # Change url so if user reload a page - search results are saved + history.replaceState {page: issuesUrl}, document.title, issuesUrl + Issuable.reload() + Issuable.updateStateFilters() + $filteredLabels = $('.filtered-labels') + + if typeof Issuable.labelRow is 'function' + $filteredLabels.html(Issuable.labelRow(data)) + + Issuable.toggleLabelFilters() + + dataType: "json" + + reload: -> + if Issues.created + Issues.initChecks() + + $('#filter_issue_search').val($('#issue_search').val()) + + updateStateFilters: -> + stateFilters = $('.issues-state-filters') + newParams = {} + paramKeys = ['author_id', 'milestone_title', 'assignee_id', 'issue_search'] + + for paramKey in paramKeys + newParams[paramKey] = gl.utils.getParameterValues(paramKey)[0] or '' + + if stateFilters.length + stateFilters.find('a').each -> + initialUrl = gl.utils.removeParamQueryString($(this).attr('href'), 'label_name[]') + labelNameValues = gl.utils.getParameterValues('label_name[]') + if labelNameValues + labelNameQueryString = ("label_name[]=#{value}" for value in labelNameValues).join('&') + newUrl = "#{gl.utils.mergeUrlParams(newParams, initialUrl)}&#{labelNameQueryString}" + else + newUrl = gl.utils.mergeUrlParams(newParams, initialUrl) + $(this).attr 'href', newUrl diff --git a/app/assets/javascripts/issuable_context.js.coffee b/app/assets/javascripts/issuable_context.js.coffee index 2f19513a831..3a439b94c59 100644 --- a/app/assets/javascripts/issuable_context.js.coffee +++ b/app/assets/javascripts/issuable_context.js.coffee @@ -9,21 +9,29 @@ class @IssuableContext $(".issuable-sidebar .inline-update").on "change", ".js-assignee", -> $(this).submit() - $(document).off("click", ".edit-link").on "click",".edit-link", (e) -> - $block = $(@).parents('.block') - $selectbox = $block.find('.selectbox') - if $selectbox.is(':visible') - $selectbox.hide() - $block.find('.value').show() - else - $selectbox.show() - $block.find('.value').hide() - - if $selectbox.is(':visible') - setTimeout (-> - $block.find('.dropdown-menu-toggle').trigger 'click' - ), 0 - + $(document) + .off 'click', '.dropdown-content a' + .on 'click', '.dropdown-content a', (e) -> + e.preventDefault() + + $(document) + .off 'click', '.edit-link' + .on 'click', '.edit-link', (e) -> + e.preventDefault() + + $block = $(@).parents('.block') + $selectbox = $block.find('.selectbox') + if $selectbox.is(':visible') + $selectbox.hide() + $block.find('.value').show() + else + $selectbox.show() + $block.find('.value').hide() + + if $selectbox.is(':visible') + setTimeout -> + $block.find('.dropdown-menu-toggle').trigger 'click' + , 0 $(".right-sidebar").niceScroll() diff --git a/app/assets/javascripts/issues.js.coffee b/app/assets/javascripts/issues.js.coffee index 0d9f2094c2a..3330e6c68ad 100644 --- a/app/assets/javascripts/issues.js.coffee +++ b/app/assets/javascripts/issues.js.coffee @@ -1,6 +1,6 @@ @Issues = init: -> - Issues.initSearch() + Issues.created = true Issues.initChecks() $("body").on "ajax:success", ".close_issue, .reopen_issue", -> @@ -15,10 +15,6 @@ else $(this).html totalIssues - 1 - reload: -> - Issues.initChecks() - $('#filter_issue_search').val($('#issue_search').val()) - initChecks: -> $(".check_all_issues").click -> $(".selected_issue").prop("checked", @checked) @@ -26,51 +22,6 @@ $(".selected_issue").bind "change", Issues.checkChanged - # Update state filters if present in page - updateStateFilters: -> - stateFilters = $('.issues-state-filters') - newParams = {} - paramKeys = ['author_id', 'label_name', 'milestone_title', 'assignee_id', 'issue_search'] - - for paramKey in paramKeys - newParams[paramKey] = gl.utils.getUrlParameter(paramKey) or '' - - if stateFilters.length - stateFilters.find('a').each -> - initialUrl = $(this).attr 'href' - $(this).attr 'href', gl.utils.mergeUrlParams(newParams, initialUrl) - - # Make sure we trigger ajax request only after user stop typing - initSearch: -> - @timer = null - $("#issue_search").keyup -> - clearTimeout(@timer) - @timer = setTimeout( -> - Issues.filterResults $("#issue_search_form") - , 500) - - filterResults: (form) => - $('.issues-holder, .merge-requests-holder').css("opacity", '0.5') - formAction = form.attr('action') - formData = form.serialize() - issuesUrl = formAction - issuesUrl += ("#{if formAction.indexOf("?") < 0 then '?' else '&'}") - issuesUrl += formData - - $.ajax - type: "GET" - url: formAction - data: formData - complete: -> - $('.issues-holder, .merge-requests-holder').css("opacity", '1.0') - success: (data) -> - $('.issues-holder, .merge-requests-holder').html(data.html) - # Change url so if user reload a page - search results are saved - history.replaceState {page: issuesUrl}, document.title, issuesUrl - Issues.reload() - Issues.updateStateFilters() - dataType: "json" - checkChanged: -> checked_issues = $(".selected_issue:checked") if checked_issues.length > 0 diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee index bc80980acb7..4ac54e13dba 100644 --- a/app/assets/javascripts/labels_select.js.coffee +++ b/app/assets/javascripts/labels_select.js.coffee @@ -6,7 +6,7 @@ class @LabelsSelect labelUrl = $dropdown.data('labels') issueUpdateURL = $dropdown.data('issueUpdate') selectedLabel = $dropdown.data('selected') - if selectedLabel? + if selectedLabel? and not $dropdown.hasClass 'js-multiselect' selectedLabel = selectedLabel.split(',') newLabelField = $('#new_label_name') newColorField = $('#new_label_color') @@ -16,33 +16,32 @@ class @LabelsSelect abilityName = $dropdown.data('ability-name') $selectbox = $dropdown.closest('.selectbox') $block = $selectbox.closest('.block') + $form = $dropdown.closest('form') $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon span') $value = $block.find('.value') - $loading = $block.find('.block-loading').fadeOut() - - if newLabelField.length - $newLabelCreateButton = $('.js-new-label-btn') - $colorPreview = $('.js-dropdown-label-color-preview') - $newLabelError = $dropdown.parent().find('.js-label-error') - $newLabelError.hide() + $newLabelError = $('.js-label-error') + $colorPreview = $('.js-dropdown-label-color-preview') + $newLabelCreateButton = $('.js-new-label-btn') - # Suggested colors in the dropdown to chose from pre-chosen colors - $('.suggest-colors-dropdown a').on 'click', (e) -> + $newLabelError.hide() + $loading = $block.find('.block-loading').fadeOut() issueURLSplit = issueUpdateURL.split('/') if issueUpdateURL? if issueUpdateURL labelHTMLTemplate = _.template( '<% _.each(labels, function(label){ %> - <a href="<%= ["",issueURLSplit[1], issueURLSplit[2],""].join("/") %>issues?label_name=<%= label.title %>"> - <span class="label has-tooltip color-label" title="<%= label.description %>" style="background-color: <%= label.color %>;"> - <%= label.title %> + <a href="<%= ["",issueURLSplit[1], issueURLSplit[2],""].join("/") %>issues?label_name=<%= _.escape(label.title) %>"> + <span class="label has-tooltip color-label" title="<%= _.escape(label.description) %>" style="background-color: <%= label.color %>;"> + <%= _.escape(label.title) %> </span> </a> <% }); %>' - ); + ) labelNoneHTMLTemplate = _.template('<div class="light">None</div>') - if newLabelField.length and $dropdown.hasClass 'js-extra-options' + if newLabelField.length + + # Suggested colors in the dropdown to chose from pre-chosen colors $('.suggest-colors-dropdown a').on "click", (e) -> e.preventDefault() e.stopPropagation() @@ -81,26 +80,25 @@ class @LabelsSelect enableLabelCreateButton = -> if newLabelField.val() isnt '' and newColorField.val() isnt '' $newLabelError.hide() - $('.js-new-label-btn').disable() - - # Create new label with API - Api.newLabel projectId, { - name: newLabelField.val() - color: newColorField.val() - }, (label) -> - $('.js-new-label-btn').enable() - - if label.message? - $newLabelError - .text label.message - .show() - else - $('.dropdown-menu-back', $dropdown.parent()).trigger 'click' - $newLabelCreateButton.enable() else $newLabelCreateButton.disable() + saveLabel = -> + # Create new label with API + Api.newLabel projectId, { + name: newLabelField.val() + color: newColorField.val() + }, (label) -> + $newLabelCreateButton.enable() + + if label.message? + $newLabelError + .text label.message + .show() + else + $('.dropdown-menu-back', $dropdown.parent()).trigger 'click' + newLabelField.on 'keyup change', enableLabelCreateButton newColorField.on 'keyup change', enableLabelCreateButton @@ -111,24 +109,7 @@ class @LabelsSelect .on 'click', (e) -> e.preventDefault() e.stopPropagation() - - if newLabelField.val() isnt '' and newColorField.val() isnt '' - $newLabelError.hide() - $('.js-new-label-btn').disable() - - # Create new label with API - Api.newLabel projectId, { - name: newLabelField.val() - color: newColorField.val() - }, (label) -> - $('.js-new-label-btn').enable() - - if label.message? - $newLabelError - .text label.message - .show() - else - $('.dropdown-menu-back', $dropdown.parent()).trigger 'click' + saveLabel() saveLabelData = -> selected = $dropdown @@ -171,7 +152,7 @@ class @LabelsSelect .find('a') .each((i) -> setTimeout(=> - glAnimate($(@), 'pulse') + gl.animate.animate($(@), 'pulse') ,200 * i ) ) @@ -200,18 +181,23 @@ class @LabelsSelect callback data renderRow: (label) -> - selectedClass = '' - if $selectbox.find("input[type='hidden']\ - [name='#{$dropdown.data('field-name')}']\ - [value='#{label.id}']").length - selectedClass = 'is-active' + removesAll = label.id is 0 or not label.id? + + selectedClass = [] + if $form.find("input[type='hidden']\ + [name='#{$dropdown.data('fieldName')}']\ + [value='#{this.id(label)}']").length + selectedClass.push 'is-active' + + if $dropdown.hasClass('js-multiselect') and removesAll + selectedClass.push 'dropdown-clear-active' color = if label.color? then "<span class='dropdown-label-box' style='background-color: #{label.color}'></span>" else "" "<li> - <a href='#' class='#{selectedClass}'> + <a href='#' class='#{selectedClass.join(' ')}'> #{color} - #{label.title} + #{_.escape(label.title)} </a> </li>" filterable: true @@ -219,37 +205,56 @@ class @LabelsSelect fields: ['title'] selectable: true - toggleLabel: (selected) -> + toggleLabel: (selected, el) -> + selected_labels = $('.js-label-select').siblings('.dropdown-menu-labels').find('.is-active') + if selected and selected.title? - selected.title + if selected_labels.length > 1 + "#{selected.title} +#{selected_labels.length - 1} more" + else + selected.title + else if not selected and selected_labels.length isnt 0 + if selected_labels.length > 1 + "#{$(selected_labels[0]).text()} +#{selected_labels.length - 1} more" + else if selected_labels.length is 1 + $(selected_labels).text() else defaultLabel fieldName: $dropdown.data('field-name') id: (label) -> - if label.isAny? - '' - else if $dropdown.hasClass "js-filter-submit" + if $dropdown.hasClass("js-filter-submit") and not label.isAny? label.title else label.id hidden: -> + page = $('body').data 'page' + isIssueIndex = page is 'projects:issues:index' + isMRIndex = page is 'projects:merge_requests:index' + $selectbox.hide() # display:block overrides the hide-collapse rule $value.removeAttr('style') if $dropdown.hasClass 'js-multiselect' - saveLabelData() + if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex) + selectedLabels = $dropdown + .closest('form') + .find("input:hidden[name='#{$dropdown.data('fieldName')}']") + Issuable.filterResults $dropdown.closest('form') + else if $dropdown.hasClass('js-filter-submit') + $dropdown.closest('form').submit() + else + saveLabelData() multiSelect: $dropdown.hasClass 'js-multiselect' clicked: (label) -> page = $('body').data 'page' isIssueIndex = page is 'projects:issues:index' - isMRIndex = page is page is 'projects:merge_requests:index' - + isMRIndex = page is 'projects:merge_requests:index' if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex) - selectedLabel = label.title - - Issues.filterResults $dropdown.closest('form') + if not $dropdown.hasClass 'js-multiselect' + selectedLabel = label.title + Issuable.filterResults $dropdown.closest('form') else if $dropdown.hasClass 'js-filter-submit' $dropdown.closest('form').submit() else diff --git a/app/assets/javascripts/lib/animate.js.coffee b/app/assets/javascripts/lib/animate.js.coffee index 8f892b5a2b9..ec3b44d6126 100644 --- a/app/assets/javascripts/lib/animate.js.coffee +++ b/app/assets/javascripts/lib/animate.js.coffee @@ -1,13 +1,39 @@ ((w) -> + if not w.gl? then w.gl = {} + if not gl.animate? then gl.animate = {} - w.glAnimate = ($el, animation, done) -> + gl.animate.animate = ($el, animation, options, done) -> + if options?.cssStart? + $el.css(options.cssStart) $el - .removeClass() + .removeClass(animation + ' animated') .addClass(animation + ' animated') .one 'webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', -> - $(this).removeClass() + $(this).removeClass(animation + ' animated') + if done? + done() + if options?.cssEnd? + $el.css(options.cssEnd) return return - return + gl.animate.animateEach = ($els, animation, time, options, done) -> + dfd = $.Deferred() + if not $els.length + dfd.resolve() + $els.each((i) -> + setTimeout(=> + $this = $(@) + gl.animate.animate($this, animation, options, => + if i is $els.length - 1 + dfd.resolve() + if done? + done() + ) + ,time * i + ) + return + ) + return dfd.promise() + return ) window
\ No newline at end of file diff --git a/app/assets/javascripts/lib/url_utility.js.coffee b/app/assets/javascripts/lib/url_utility.js.coffee index abd556e0b4e..6a00932c028 100644 --- a/app/assets/javascripts/lib/url_utility.js.coffee +++ b/app/assets/javascripts/lib/url_utility.js.coffee @@ -3,16 +3,20 @@ w.gl ?= {} w.gl.utils ?= {} - w.gl.utils.getUrlParameter = (sParam) -> + # Returns an array containing the value(s) of the + # of the key passed as an argument + w.gl.utils.getParameterValues = (sParam) -> sPageURL = decodeURIComponent(window.location.search.substring(1)) sURLVariables = sPageURL.split('&') sParameterName = undefined + values = [] i = 0 while i < sURLVariables.length sParameterName = sURLVariables[i].split('=') if sParameterName[0] is sParam - return if sParameterName[1] is undefined then true else sParameterName[1] + values.push(sParameterName[1]) i++ + values # # # @param {Object} params - url keys and value to merge @@ -28,4 +32,12 @@ newUrl = "#{newUrl}#{(if newUrl.indexOf('?') > 0 then '&' else '?')}#{paramName}=#{paramValue}" newUrl + # removes parameter query string from url. returns the modified url + w.gl.utils.removeParamQueryString = (url, param) -> + url = decodeURIComponent(url) + urlVariables = url.split('&') + ( + variables for variables in urlVariables when variables.indexOf(param) is -1 + ).join('&') + ) window diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee index e8613cab72b..372732d0aac 100644 --- a/app/assets/javascripts/merge_request_tabs.js.coffee +++ b/app/assets/javascripts/merge_request_tabs.js.coffee @@ -87,8 +87,8 @@ class @MergeRequestTabs if window.location.hash navBarHeight = $('.navbar-gitlab').outerHeight() - $el = $("#{container} #{window.location.hash}") - $.scrollTo("#{container} #{window.location.hash}", offset: -navBarHeight) if $el.length + $el = $("#{container} #{window.location.hash}:not(.match)") + $.scrollTo("#{container} #{window.location.hash}:not(.match)", offset: -navBarHeight) if $el.length # Activate a tab based on the current action activateTab: (action) -> @@ -176,12 +176,12 @@ class @MergeRequestTabs if locationHash isnt '' hashClassString = ".#{locationHash.replace('#', '')}" - $diffLine = $(locationHash) + $diffLine = $("#{locationHash}:not(.match)", $('#diffs')) - if $diffLine.is ':not(tr)' - $diffLine = $("td#{locationHash}, td#{hashClassString}") + if not $diffLine.is 'tr' + $diffLine = $('#diffs').find("td#{locationHash}, td#{hashClassString}") else - $diffLine = $('td', $diffLine) + $diffLine = $diffLine.find('td') if $diffLine.length $diffLine.addClass 'hll' diff --git a/app/assets/javascripts/merge_requests.js.coffee b/app/assets/javascripts/merge_requests.js.coffee deleted file mode 100644 index b3c73ffce5d..00000000000 --- a/app/assets/javascripts/merge_requests.js.coffee +++ /dev/null @@ -1,35 +0,0 @@ -# -# * Filter merge requests -# -@MergeRequests = - init: -> - MergeRequests.initSearch() - - # Make sure we trigger ajax request only after user stop typing - initSearch: -> - @timer = null - $("#issue_search").keyup -> - clearTimeout(@timer) - @timer = setTimeout(MergeRequests.filterResults, 500) - - filterResults: => - form = $("#issue_search_form") - search = $("#issue_search").val() - $('.merge-requests-holder').css("opacity", '0.5') - issues_url = form.attr('action') + '?' + form.serialize() - - $.ajax - type: "GET" - url: form.attr('action') - data: form.serialize() - complete: -> - $('.merge-requests-holder').css("opacity", '1.0') - success: (data) -> - $('.merge-requests-holder').html(data.html) - # Change url so if user reload a page - search results are saved - history.replaceState {page: issues_url}, document.title, issues_url - MergeRequests.reload() - dataType: "json" - - reload: -> - $('#filter_issue_search').val($('#issue_search').val()) diff --git a/app/assets/javascripts/milestone_select.js.coffee b/app/assets/javascripts/milestone_select.js.coffee index 6bd4e885a03..345a0e447af 100644 --- a/app/assets/javascripts/milestone_select.js.coffee +++ b/app/assets/javascripts/milestone_select.js.coffee @@ -24,7 +24,7 @@ class @MilestoneSelect if issueUpdateURL milestoneLinkTemplate = _.template( - '<a href="/<%= namespace %>/<%= path %>/milestones/<%= iid %>"><%= title %></a>' + '<a href="/<%= namespace %>/<%= path %>/milestones/<%= iid %>"><%= _.escape(title) %></a>' ) milestoneLinkNoneTemplate = '<div class="light">None</div>' @@ -71,7 +71,7 @@ class @MilestoneSelect defaultLabel fieldName: $dropdown.data('field-name') text: (milestone) -> - milestone.title + _.escape(milestone.title) id: (milestone) -> if !useId milestone.name @@ -97,7 +97,7 @@ class @MilestoneSelect selectedMilestone = selected.name else selectedMilestone = '' - Issues.filterResults $dropdown.closest('form') + Issuable.filterResults $dropdown.closest('form') else if $dropdown.hasClass('js-filter-submit') $dropdown.closest('form').submit() else diff --git a/app/assets/javascripts/todos.js.coffee b/app/assets/javascripts/todos.js.coffee index 00d2b641723..10e698d6a54 100644 --- a/app/assets/javascripts/todos.js.coffee +++ b/app/assets/javascripts/todos.js.coffee @@ -1,5 +1,11 @@ class @Todos - constructor: (@name) -> + constructor: (opts = {}) -> + { + @el = $('.js-todos-options') + } = opts + + @perPage = @el.data('perPage') + @clearListeners() @initBtnListeners() @@ -26,6 +32,7 @@ class @Todos dataType: 'json' data: '_method': 'delete' success: (data) => + @redirectIfNeeded data.count @clearDone $this.closest('li') @updateBadges data @@ -57,6 +64,40 @@ class @Todos $('.todos-pending .badge, .todos-pending-count').text data.count $('.todos-done .badge').text data.done_count + getTotalPages: -> + @el.data('totalPages') + + getCurrentPage: -> + @el.data('currentPage') + + getTodosPerPage: -> + @el.data('perPage') + + redirectIfNeeded: (total) -> + currPages = @getTotalPages() + currPage = @getCurrentPage() + + # Refresh if no remaining Todos + if not total + location.reload() + return + + # Do nothing if no pagination + return if not currPages + + newPages = Math.ceil(total / @getTodosPerPage()) + url = location.href # Includes query strings + + # If new total of pages is different than we have now + if newPages isnt currPages + # Redirect to previous page if there's one available + if currPages > 1 and currPage is currPages + pageParams = + page: currPages - 1 + url = gl.utils.mergeUrlParams(pageParams, url) + + Turbolinks.visit(url) + goToTodoUrl: (e)-> todoLink = $(this).data('url') return unless todoLink diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee index a7e934936e9..b80b1b861cc 100644 --- a/app/assets/javascripts/users_select.js.coffee +++ b/app/assets/javascripts/users_select.js.coffee @@ -158,7 +158,7 @@ class @UsersSelect if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex) selectedId = user.id - Issues.filterResults $dropdown.closest('form') + Issuable.filterResults $dropdown.closest('form') else if $dropdown.hasClass 'js-filter-submit' $dropdown.closest('form').submit() else diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index ba6c7930cdc..239eaf15cc1 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -248,7 +248,7 @@ .dropdown-title { position: relative; - padding: 0 25px 15px; + padding: 0 25px 10px; margin: 0 10px 10px; font-weight: 600; line-height: 1; @@ -278,7 +278,7 @@ right: 5px; width: 20px; height: 20px; - top: -1px; + top: -3px; } .dropdown-menu-back { @@ -320,7 +320,7 @@ } } -.dropdown-input-field { +.dropdown-input-field, .default-dropdown-input { width: 100%; padding: 0 7px; color: $dropdown-input-color; @@ -358,6 +358,13 @@ border-top: 1px solid $dropdown-divider-color; } +.dropdown-due-date-footer { + padding-top: 0; + margin-left: 10px; + margin-right: 10px; + border-top: 0; +} + .dropdown-footer-list { font-size: 14px; @@ -395,3 +402,122 @@ height: 15px; border-radius: $border-radius-base; } + +.dropdown-menu-due-date { + .dropdown-content { + max-height: 230px; + } + + .ui-widget { + table { + margin: 0; + } + + &.ui-datepicker-inline { + padding: 0 10px; + border: 0; + width: 100%; + } + + .ui-datepicker-header { + padding: 0 8px 10px; + border: 0; + + .ui-icon { + background: none; + font-size: 20px; + text-indent: 0; + + &:before { + display: block; + position: relative; + top: -2px; + color: $dropdown-title-btn-color; + font: normal normal normal 14px/1 FontAwesome; + font-size: inherit; + text-rendering: auto; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + } + } + } + + .ui-state-active, + .ui-state-hover { + color: $md-link-color; + background-color: $calendar-hover-bg; + } + + .ui-datepicker-prev, + .ui-datepicker-next { + top: 0; + height: 15px; + cursor: pointer; + + &:hover { + background-color: transparent; + border: 0; + + .ui-icon:before { + color: $md-link-color; + } + } + } + + .ui-datepicker-prev { + left: 0; + + .ui-icon:before { + content: '\f104'; + text-align: left; + } + } + + .ui-datepicker-next { + right: 0; + + .ui-icon:before { + content: '\f105'; + text-align: right; + } + } + + td { + padding: 0; + border: 1px solid $calendar-border-color; + + &:first-child { + border-left: 0; + } + + &:last-child { + border-right: 0; + } + + a { + line-height: 17px; + border: 0; + border-radius: 0; + } + } + + .ui-datepicker-title { + color: $gl-gray; + font-size: 15px; + line-height: 1; + font-weight: normal; + } + } + + th { + padding: 2px 0; + color: $calendar-header-color; + font-weight: normal; + text-transform: lowercase; + border-top: 1px solid $calendar-border-color; + } + + .ui-datepicker-unselectable { + background-color: $calendar-unselectable-bg; + } +} diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index 789df42fb66..148a7cab6e2 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -38,12 +38,14 @@ .filename { &.old { + display: inline-block; span.idiff { background-color: #f8cbcb; } } &.new { + display: inline-block; span.idiff { background-color: #a6f3a6; } diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss index 0f32d36d59c..704fa1ff800 100644 --- a/app/assets/stylesheets/framework/markdown_area.scss +++ b/app/assets/stylesheets/framework/markdown_area.scss @@ -90,3 +90,12 @@ box-shadow: none; width: 100%; } + +.md { + &.md-preview-holder { + code { + white-space: pre-wrap; + word-break: break-all; + } + } +} diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index f910cf61817..30ca27ab104 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -241,3 +241,8 @@ $note-form-border-color: #e5e5e5; $note-toolbar-color: #959494; $zen-control-hover-color: #111; + +$calendar-header-color: #b8b8b8; +$calendar-hover-bg: #ecf3fe; +$calendar-border-color: rgba(#000, .1); +$calendar-unselectable-bg: #faf9f9; diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss index 2c2ac903f29..751a5ab4d92 100644 --- a/app/assets/stylesheets/pages/detail_page.scss +++ b/app/assets/stylesheets/pages/detail_page.scss @@ -36,4 +36,10 @@ } } } + + .wiki { + code { + white-space: pre-wrap; + } + } } diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 183f22a1b24..e7c8198ba45 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -34,6 +34,7 @@ background: #fff; color: #333; border-radius: 0 0 3px 3px; + -webkit-overflow-scrolling: auto; .unfold { cursor: pointer; @@ -86,7 +87,7 @@ } span { - white-space: pre; + white-space: pre-wrap; } } } @@ -335,7 +336,7 @@ } .diff-file .line_content { - white-space: pre; + white-space: pre-wrap; } .diff-wrap-lines .line_content { diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss index 0f0592a0ab8..8981f070a20 100644 --- a/app/assets/stylesheets/pages/editor.scss +++ b/app/assets/stylesheets/pages/editor.scss @@ -26,6 +26,10 @@ line-height: 42px; padding-top: 7px; padding-bottom: 7px; + + .pull-right { + height: 20px; + } } .editor-ref { @@ -53,4 +57,9 @@ .select2 { float: right; } + + .encoding-selector, + .license-selector { + display: inline-block; + } } diff --git a/app/assets/stylesheets/pages/help.scss b/app/assets/stylesheets/pages/help.scss index 604f1700cf8..ee95bdf488e 100644 --- a/app/assets/stylesheets/pages/help.scss +++ b/app/assets/stylesheets/pages/help.scss @@ -59,8 +59,10 @@ position: relative; overflow-y: auto; padding: 15px; + .form-actions { margin: -$gl-padding+1; + margin-top: 15px; } } diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 5bf44c1cdb6..1cf3023ecc9 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -128,6 +128,7 @@ top: 58px; bottom: 0; right: 0; + z-index: 10; transition: width .3s; background: $gray-light; padding: 10px 20px; @@ -241,16 +242,20 @@ } } - .btn { + .issuable-pager { background: $gray-normal; border: 1px solid $border-gray-normal; &:hover { background: $gray-dark; border: 1px solid $border-gray-dark; } + + &.btn-primary { + @extend .btn-primary + } } - a:not(.btn) { + a:not(.issuable-pager) { &:hover { color: $md-link-color; text-decoration: none; diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index c2371d79989..7fa13e66b43 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -42,6 +42,7 @@ .note-textarea { display: block; padding: 10px 0; + color: $gl-gray; font-family: $regular_font; border: 0; @@ -83,18 +84,6 @@ border-color: $gl-success; } } - - p { - code { - white-space: normal; - } - - pre { - code { - white-space: pre; - } - } - } } } diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index ffcd89a3c9f..d54abe9bc02 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -81,16 +81,8 @@ ul.notes { @include md-typography; // On diffs code should wrap nicely and not overflow - p { - code { - white-space: normal; - } - - pre { - code { - white-space: pre; - } - } + code { + white-space: pre-wrap; } // Reset ul style types since we're nested inside a ul already @@ -137,7 +129,7 @@ ul.notes { margin-right: 10px; } .line_content { - white-space: pre; + white-space: pre-wrap; } } @@ -171,11 +163,6 @@ ul.notes { &.parallel { border-width: 1px; - - .code, - code { - white-space: pre-wrap; - } } .notes { @@ -196,6 +183,9 @@ ul.notes { } } + .author_link { + color: $gl-gray; + } } .note-headline-light, @@ -308,7 +298,7 @@ ul.notes { padding: 4px; font-size: 16px; color: $gl-link-color; - margin-left: -60px; + margin-left: -56px; position: absolute; z-index: 10; width: 32px; diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index b4a28b8dd3f..ec22548ddeb 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -75,6 +75,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController :admin_notification_email, :user_oauth_applications, :shared_runners_enabled, + :shared_runners_text, :max_artifacts_size, :metrics_enabled, :metrics_host, @@ -92,6 +93,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController :akismet_api_key, :email_author_in_body, :repository_checks_enabled, + :metrics_packet_size, restricted_visibility_levels: [], import_sources: [] ) diff --git a/app/controllers/admin/hooks_controller.rb b/app/controllers/admin/hooks_controller.rb index 0bd19c49d8f..93c4894ea0f 100644 --- a/app/controllers/admin/hooks_controller.rb +++ b/app/controllers/admin/hooks_controller.rb @@ -39,6 +39,6 @@ class Admin::HooksController < Admin::ApplicationController end def hook_params - params.require(:hook).permit(:url, :enable_ssl_verification) + params.require(:hook).permit(:url, :enable_ssl_verification, :push_events, :tag_push_events) end end diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb index f159e169f6d..b8b9e78427d 100644 --- a/app/controllers/projects/builds_controller.rb +++ b/app/controllers/projects/builds_controller.rb @@ -1,7 +1,7 @@ class Projects::BuildsController < Projects::ApplicationController before_action :build, except: [:index, :cancel_all] before_action :authorize_read_build!, except: [:cancel, :cancel_all, :retry] - before_action :authorize_update_build!, except: [:index, :show, :status] + before_action :authorize_update_build!, except: [:index, :show, :status, :raw] layout 'project' def index @@ -62,6 +62,14 @@ class Projects::BuildsController < Projects::ApplicationController notice: "Build has been sucessfully erased!" end + def raw + if @build.has_trace? + send_file @build.path_to_trace, type: 'text/plain; charset=utf-8', disposition: 'inline' + else + render_404 + end + end + private def build diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index 576fa3cedb2..a202cb38692 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -12,7 +12,7 @@ class Projects::CommitController < Projects::ApplicationController before_action :authorize_read_commit_status!, only: [:builds] before_action :commit before_action :define_show_vars, only: [:show, :builds] - before_action :authorize_edit_tree!, only: [:revert] + before_action :authorize_edit_tree!, only: [:revert, :cherry_pick] def show apply_diff_view_cookie! @@ -38,13 +38,13 @@ class Projects::CommitController < Projects::ApplicationController end def cancel_builds - ci_commit.builds.running_or_pending.each(&:cancel) + ci_builds.running_or_pending.each(&:cancel) redirect_back_or_default default: builds_namespace_project_commit_path(project.namespace, project, commit.sha) end def retry_builds - ci_commit.builds.latest.failed.each do |build| + ci_builds.latest.failed.each do |build| if build.retryable? Ci::Build.retry(build) end @@ -60,27 +60,32 @@ class Projects::CommitController < Projects::ApplicationController end def revert - assign_revert_commit_vars + assign_change_commit_vars(@commit.revert_branch_name) return render_404 if @target_branch.blank? - create_commit(Commits::RevertService, success_notice: "The #{revert_type_title} has been successfully reverted.", - success_path: successful_revert_path, failure_path: failed_revert_path) + create_commit(Commits::RevertService, success_notice: "The #{@commit.change_type_title} has been successfully reverted.", + success_path: successful_change_path, failure_path: failed_change_path) end + + def cherry_pick + assign_change_commit_vars(@commit.cherry_pick_branch_name) + + return render_404 if @target_branch.blank? - private - - def revert_type_title - @commit.merged_merge_request ? 'merge request' : 'commit' + create_commit(Commits::CherryPickService, success_notice: "The #{@commit.change_type_title} has been successfully cherry-picked.", + success_path: successful_change_path, failure_path: failed_change_path) end - def successful_revert_path + private + + def successful_change_path return referenced_merge_request_url if @commit.merged_merge_request namespace_project_commits_url(@project.namespace, @project, @target_branch) end - def failed_revert_path + def failed_change_path return referenced_merge_request_url if @commit.merged_merge_request namespace_project_commit_url(@project.namespace, @project, params[:id]) @@ -94,8 +99,12 @@ class Projects::CommitController < Projects::ApplicationController @commit ||= @project.commit(params[:id]) end - def ci_commit - @ci_commit ||= project.ci_commit(commit.sha) + def ci_commits + @ci_commits ||= project.ci_commits.where(sha: commit.sha) + end + + def ci_builds + @ci_builds ||= Ci::Build.where(commit: ci_commits) end def define_show_vars @@ -108,17 +117,17 @@ class Projects::CommitController < Projects::ApplicationController @diff_refs = [commit.parent || commit, commit] @notes_count = commit.notes.count - @statuses = ci_commit.statuses if ci_commit + @statuses = CommitStatus.where(commit: ci_commits) + @builds = Ci::Build.where(commit: ci_commits) end - def assign_revert_commit_vars + def assign_change_commit_vars(mr_source_branch) @commit = project.commit(params[:id]) @target_branch = params[:target_branch] - @mr_source_branch = @commit.revert_branch_name + @mr_source_branch = mr_source_branch @mr_target_branch = @target_branch @commit_params = { commit: @commit, - revert_type_title: revert_type_title, create_merge_request: params[:create_merge_request].present? || different_project? } end diff --git a/app/controllers/projects/group_links_controller.rb b/app/controllers/projects/group_links_controller.rb index 4159e53bfa9..606552fa853 100644 --- a/app/controllers/projects/group_links_controller.rb +++ b/app/controllers/projects/group_links_controller.rb @@ -7,10 +7,12 @@ class Projects::GroupLinksController < Projects::ApplicationController end def create - link = project.project_group_links.new - link.group_id = params[:link_group_id] - link.group_access = params[:link_group_access] - link.save + group = Group.find(params[:link_group_id]) + return render_404 unless can?(current_user, :read_group, group) + + project.project_group_links.create( + group: group, group_access: params[:link_group_access] + ) redirect_to namespace_project_group_links_path(project.namespace, project) end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 38214f04793..b96ab91c17d 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -33,14 +33,15 @@ class Projects::IssuesController < Projects::ApplicationController end @issues = @issues.page(params[:page]) - @label = @project.labels.find_by(title: params[:label_name]) + @labels = @project.labels.where(title: params[:label_name]) respond_to do |format| format.html format.atom { render layout: false } format.json do render json: { - html: view_to_html_string("projects/issues/_issues") + html: view_to_html_string("projects/issues/_issues"), + labels: @labels.as_json(methods: :text_color) } end end @@ -128,10 +129,7 @@ class Projects::IssuesController < Projects::ApplicationController end def related_branches - merge_requests = @issue.referenced_merge_requests(current_user) - - @related_branches = @issue.related_branches - - merge_requests.map(&:source_branch) + @related_branches = @issue.related_branches(current_user) respond_to do |format| format.json do @@ -194,7 +192,7 @@ class Projects::IssuesController < Projects::ApplicationController def issue_params params.require(:issue).permit( :title, :assignee_id, :position, :description, :confidential, - :milestone_id, :state_event, :task_num, label_ids: [] + :milestone_id, :due_date, :state_event, :task_num, label_ids: [] ) end diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 3e0cfc6aa65..81003c34f59 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -38,13 +38,14 @@ class Projects::MergeRequestsController < Projects::ApplicationController @merge_requests = @merge_requests.page(params[:page]) @merge_requests = @merge_requests.preload(:target_project) - @label = @project.labels.find_by(title: params[:label_name]) + @labels = @project.labels.where(title: params[:label_name]) respond_to do |format| format.html format.json do render json: { - html: view_to_html_string("projects/merge_requests/_merge_requests") + html: view_to_html_string("projects/merge_requests/_merge_requests"), + labels: @labels.as_json(methods: :text_color) } end end @@ -320,6 +321,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController def define_widget_vars @ci_commit = @merge_request.ci_commit + @ci_commits = [@ci_commit].compact closes_issues end diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index e457db2f0b7..33b2625c0ac 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -1,6 +1,6 @@ class Projects::ProjectMembersController < Projects::ApplicationController # Authorize - before_action :authorize_admin_project_member!, except: :leave + before_action :authorize_admin_project_member!, except: [:leave, :index] def index @project_members = @project.project_members diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb index 8b2577aebe1..739681f4085 100644 --- a/app/controllers/projects/services_controller.rb +++ b/app/controllers/projects/services_controller.rb @@ -6,7 +6,7 @@ class Projects::ServicesController < Projects::ApplicationController :description, :issues_url, :new_issue_url, :restrict_to_branch, :channel, :colorize_messages, :channels, :push_events, :issues_events, :merge_requests_events, :tag_push_events, - :note_events, :build_events, + :note_events, :build_events, :wiki_page_events, :notify_only_broken_builds, :add_pusher, :send_from_committer_email, :disable_diffs, :external_wiki_url, :notify, :color, diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb index 9f3a4a69721..c02bc28acef 100644 --- a/app/controllers/projects/wikis_controller.rb +++ b/app/controllers/projects/wikis_controller.rb @@ -44,7 +44,7 @@ class Projects::WikisController < Projects::ApplicationController return render('empty') unless can?(current_user, :create_wiki, @project) - if @page.update(content, format, message) + if @page = WikiPages::UpdateService.new(@project, current_user, wiki_params).execute(@page) redirect_to( namespace_project_wiki_path(@project.namespace, @project, @page), notice: 'Wiki was successfully updated.' @@ -55,9 +55,9 @@ class Projects::WikisController < Projects::ApplicationController end def create - @page = WikiPage.new(@project_wiki) + @page = WikiPages::CreateService.new(@project, current_user, wiki_params).execute - if @page.create(wiki_params) + if @page.persisted? redirect_to( namespace_project_wiki_path(@project.namespace, @project, @page), notice: 'Wiki was successfully updated.' @@ -122,15 +122,4 @@ class Projects::WikisController < Projects::ApplicationController params[:wiki].slice(:title, :content, :format, :message) end - def content - params[:wiki][:content] - end - - def format - params[:wiki][:format] - end - - def message - params[:wiki][:message] - end end diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index f1df6832bf6..93aa30b3255 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -39,6 +39,7 @@ class IssuableFinder items = by_assignee(items) items = by_author(items) items = by_label(items) + items = by_due_date(items) sort(items) end @@ -117,7 +118,7 @@ class IssuableFinder end def filter_by_no_label? - labels? && params[:label_name] == Label::None.title + labels? && params[:label_name].include?(Label::None.title) end def labels @@ -271,18 +272,55 @@ class IssuableFinder items = items.without_label else items = items.with_label(label_names) - if projects items = items.where(labels: { project_id: projects }) end end end + # When filtering by multiple labels we may end up duplicating issues (if one + # has multiple labels). This ensures we only return unique issues. + items.distinct + end + + def by_due_date(items) + if due_date? + if filter_by_no_due_date? + items = items.without_due_date + elsif filter_by_overdue? + items = items.due_before(Date.today) + elsif filter_by_due_this_week? + items = items.due_between(Date.today.beginning_of_week, Date.today.end_of_week) + elsif filter_by_due_this_month? + items = items.due_between(Date.today.beginning_of_month, Date.today.end_of_month) + end + end + items end + def filter_by_no_due_date? + due_date? && params[:due_date] == Issue::NoDueDate.name + end + + def filter_by_overdue? + due_date? && params[:due_date] == Issue::Overdue.name + end + + def filter_by_due_this_week? + due_date? && params[:due_date] == Issue::DueThisWeek.name + end + + def filter_by_due_this_month? + due_date? && params[:due_date] == Issue::DueThisMonth.name + end + + def due_date? + params[:due_date].present? && klass.column_names.include?('due_date') + end + def label_names - params[:label_name].split(',') + params[:label_name].is_a?(String) ? params[:label_name].split(',') : params[:label_name] end def current_user_related? diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 16e5b8ac223..3e0074da394 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -254,11 +254,11 @@ module ApplicationHelper def page_filter_path(options = {}) without = options.delete(:without) + add_label = options.delete(:label) exist_opts = { state: params[:state], scope: params[:scope], - label_name: params[:label_name], milestone_title: params[:milestone_title], assignee_id: params[:assignee_id], author_id: params[:author_id], @@ -275,6 +275,13 @@ module ApplicationHelper path = request.path path << "?#{options.to_param}" + if add_label + if params[:label_name].present? and params[:label_name].respond_to?('any?') + params[:label_name].each do |label| + path << "&label_name[]=#{label}" + end + end + end path end diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 60a0ff32c9c..914b0ef6042 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -15,6 +15,10 @@ module ApplicationSettingsHelper current_application_settings.sign_in_text end + def shared_runners_text + current_application_settings.shared_runners_text + end + def user_oauth_applications? current_application_settings.user_oauth_applications end diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index 9e59a295fc4..a4d7c425d0f 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -173,4 +173,15 @@ module BlobHelper response.etag = @blob.id !stale end + + def licenses_for_select + return @licenses_for_select if defined?(@licenses_for_select) + + licenses = Licensee::License.all + + @licenses_for_select = { + Popular: licenses.select(&:featured).map { |license| [license.name, license.key] }, + Other: licenses.reject(&:featured).map { |license| [license.name, license.key] } + } + end end diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb index 8b1575d5e0c..417050b4132 100644 --- a/app/helpers/ci_status_helper.rb +++ b/app/helpers/ci_status_helper.rb @@ -4,14 +4,6 @@ module CiStatusHelper builds_namespace_project_commit_path(project.namespace, project, ci_commit.sha) end - def ci_status_icon(ci_commit) - ci_icon_for_status(ci_commit.status) - end - - def ci_status_label(ci_commit) - ci_label_for_status(ci_commit.status) - end - def ci_status_with_icon(status, target = nil) content = ci_icon_for_status(status) + ' '.html_safe + ci_label_for_status(status) klass = "ci-status ci-#{status}" @@ -47,10 +39,13 @@ module CiStatusHelper end def render_ci_status(ci_commit, tooltip_placement: 'auto left') - link_to ci_status_icon(ci_commit), + # TODO: split this method into + # - render_commit_status + # - render_pipeline_status + link_to ci_icon_for_status(ci_commit.status), ci_status_path(ci_commit), class: "ci-status-link ci-status-icon-#{ci_commit.status.dasherize}", - title: "Build #{ci_status_label(ci_commit)}", + title: "Build #{ci_label_for_status(ci_commit.status)}", data: { toggle: 'tooltip', placement: tooltip_placement } end diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb index 35ba543cef1..b59c3982edd 100644 --- a/app/helpers/commits_helper.rb +++ b/app/helpers/commits_helper.rb @@ -126,12 +126,10 @@ module CommitsHelper def revert_commit_link(commit, continue_to_path, btn_class: nil) return unless current_user - tooltip = "Revert this #{revert_commit_type(commit)} in a new merge request" + tooltip = "Revert this #{commit.change_type_title} in a new merge request" if can_collaborate_with_project? - content_tag :span, 'data-toggle' => 'modal', 'data-target' => '#modal-revert-commit' do - link_to 'Revert', '#modal-revert-commit', 'data-toggle' => 'tooltip', 'data-container' => 'body', title: tooltip, class: "btn btn-default btn-grouped btn-#{btn_class}" - end + link_to 'Revert', '#modal-revert-commit', 'data-toggle' => 'modal', 'data-container' => 'body', title: tooltip, class: "btn btn-default btn-grouped btn-#{btn_class} has-tooltip" elsif can?(current_user, :fork_project, @project) continue_params = { to: continue_to_path, @@ -146,11 +144,24 @@ module CommitsHelper end end - def revert_commit_type(commit) - if commit.merged_merge_request - 'merge request' - else - 'commit' + def cherry_pick_commit_link(commit, continue_to_path, btn_class: nil) + return unless current_user + + tooltip = "Cherry-pick this #{commit.change_type_title} in a new merge request" + + if can_collaborate_with_project? + link_to 'Cherry-pick', '#modal-cherry-pick-commit', 'data-toggle' => 'modal', 'data-container' => 'body', title: tooltip, class: "btn btn-default btn-grouped btn-#{btn_class} has-tooltip" + elsif can?(current_user, :fork_project, @project) + continue_params = { + to: continue_to_path, + notice: edit_in_new_fork_notice + ' Try to cherry-pick this commit again.', + notice_now: edit_in_new_fork_notice_now + } + fork_path = namespace_project_forks_path(@project.namespace, @project, + namespace_key: current_user.namespace.id, + continue: continue_params) + + link_to 'Cherry-pick', fork_path, class: 'btn btn-grouped btn-close', method: :post, 'data-toggle' => 'tooltip', 'data-container' => 'body', title: tooltip end end @@ -183,7 +194,7 @@ module CommitsHelper options = { class: "commit-#{options[:source]}-link has-tooltip", - data: { 'original-title'.to_sym => sanitize(source_email) } + title: source_email } if user.nil? diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb index f3fddef01cb..f07eff3fb57 100644 --- a/app/helpers/gitlab_routing_helper.rb +++ b/app/helpers/gitlab_routing_helper.rb @@ -25,6 +25,10 @@ module GitlabRoutingHelper namespace_project_commits_path(project.namespace, project, @ref || project.repository.root_ref) end + def project_pipelines_path(project, *args) + namespace_project_pipelines_path(project.namespace, project, *args) + end + def project_builds_path(project, *args) namespace_project_builds_path(project.namespace, project, *args) end diff --git a/app/helpers/import_helper.rb b/app/helpers/import_helper.rb new file mode 100644 index 00000000000..109bc1a02d1 --- /dev/null +++ b/app/helpers/import_helper.rb @@ -0,0 +1,18 @@ +module ImportHelper + def github_project_link(path_with_namespace) + link_to path_with_namespace, github_project_url(path_with_namespace), target: '_blank' + end + + private + + def github_project_url(path_with_namespace) + "#{github_root_url}/#{path_with_namespace}" + end + + def github_root_url + return @github_url if defined?(@github_url) + + provider = Gitlab.config.omniauth.providers.find { |p| p.name == 'github' } + @github_url = provider.fetch('url', 'https://github.com') if provider + end +end diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 49b6b79ce35..39474217286 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -16,6 +16,25 @@ module IssuablesHelper base_issuable_scope(issuable).where('iid > ?', issuable.iid).last end + def multi_label_name(current_labels, default_label) + # current_labels may be a string from before + if current_labels.is_a?(Array) + if current_labels.count > 1 + "#{current_labels[0]} +#{current_labels.count - 1} more" + else + current_labels[0] + end + elsif current_labels.is_a?(String) + if current_labels.nil? || current_labels.empty? + default_label + else + current_labels + end + else + default_label + end + end + def issuable_json_path(issuable) project = issuable.project diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 4cb8adcebad..afe1e11a0da 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -131,7 +131,7 @@ module IssuesHelper class: "icon emoji-icon emoji-#{unicode}", title: name, data: data - else + else # Emoji icons displayed separately, used for the awards already given # to an issue or merge request. content_tag :img, "", @@ -172,6 +172,18 @@ module IssuesHelper end.to_h end + def due_date_options + options = [ + Issue::AnyDueDate, + Issue::NoDueDate, + Issue::DueThisWeek, + Issue::DueThisMonth, + Issue::Overdue + ] + + options_from_collection_for_select(options, 'name', 'title', params[:due_date]) + end + # Required for Banzai::Filter::IssueReferenceFilter module_function :url_for_issue end diff --git a/app/helpers/page_layout_helper.rb b/app/helpers/page_layout_helper.rb index 82f805fa444..e4e8b934bc8 100644 --- a/app/helpers/page_layout_helper.rb +++ b/app/helpers/page_layout_helper.rb @@ -84,6 +84,14 @@ module PageLayoutHelper end end + def nav(name = nil) + if name + @nav = name + else + @nav + end + end + def fluid_layout(enabled = false) if @fluid_layout.nil? @fluid_layout = (current_user && current_user.layout == "fluid") || enabled diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 7e00aacceaa..ab85694da3f 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -52,7 +52,7 @@ module ProjectsHelper link_to(author_html, user_path(author), class: "author_link #{"#{opts[:mobile_classes]}" if opts[:mobile_classes]}").html_safe else title = opts[:title].sub(":name", sanitize(author.name)) - link_to(author_html, user_path(author), class: "author_link has-tooltip", data: { 'original-title'.to_sym => title, container: 'body' } ).html_safe + link_to(author_html, user_path(author), class: "author_link has-tooltip", title: title, data: { container: 'body' } ).html_safe end end @@ -144,6 +144,10 @@ module ProjectsHelper nav_tabs << :settings end + if can?(current_user, :read_project_member, project) + nav_tabs << :team + end + if can?(current_user, :read_issue, project) nav_tabs << :issues end @@ -216,40 +220,14 @@ module ProjectsHelper end end - def add_contribution_guide_path(project) - if project && !project.repository.contribution_guide - namespace_project_new_blob_path( - project.namespace, - project, - project.default_branch, - file_name: "CONTRIBUTING.md", - commit_message: "Add contribution guide" - ) - end - end - - def add_changelog_path(project) - if project && !project.repository.changelog - namespace_project_new_blob_path( - project.namespace, - project, - project.default_branch, - file_name: "CHANGELOG", - commit_message: "Add changelog" - ) - end - end - - def add_license_path(project) - if project && !project.repository.license - namespace_project_new_blob_path( - project.namespace, - project, - project.default_branch, - file_name: "LICENSE", - commit_message: "Add license" - ) - end + def add_special_file_path(project, file_name:, commit_message: nil) + namespace_project_new_blob_path( + project.namespace, + project, + project.default_branch || 'master', + file_name: file_name, + commit_message: commit_message || "Add #{file_name.downcase}" + ) end def contribution_guide_path(project) @@ -272,7 +250,7 @@ module ProjectsHelper end def license_path(project) - filename_path(project, :license) + filename_path(project, :license_blob) end def version_path(project) @@ -306,6 +284,13 @@ module ProjectsHelper namespace_project_new_blob_path(@project.namespace, @project, tree_join(ref), file_name: 'README.md') end + def new_license_path + ref = @repository.root_ref if @repository + ref ||= 'master' + + namespace_project_new_blob_path(@project.namespace, @project, tree_join(ref), file_name: 'LICENSE') + end + def last_push_event if current_user current_user.recent_push(@project.id) @@ -335,6 +320,12 @@ module ProjectsHelper @ref || @repository.try(:root_ref) end + def license_short_name(project) + license = Licensee::License.new(project.repository.license_key) + + license.nickname || license.name + end + private def filename_path(project, filename) diff --git a/app/helpers/selects_helper.rb b/app/helpers/selects_helper.rb index 4fc6de59a8b..e951a87a212 100644 --- a/app/helpers/selects_helper.rb +++ b/app/helpers/selects_helper.rb @@ -2,32 +2,29 @@ module SelectsHelper def users_select_tag(id, opts = {}) css_class = "ajax-users-select " css_class << "multiselect " if opts[:multiple] + css_class << "skip_ldap " if opts[:skip_ldap] css_class << (opts[:class] || '') 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 - author_id = opts[:author_id] || '' - project = opts[:project] || @project + first_user = opts[:first_user] && current_user ? current_user.username : false html = { class: css_class, data: { - placeholder: placeholder, - null_user: null_user, - any_user: any_user, - email_user: email_user, + 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: first_user, - current_user: current_user, - author_id: author_id + current_user: opts[:current_user] || false, + "push-code-to-protected-branches" => opts[:push_code_to_protected_branches], + author_id: opts[:author_id] || '' } } unless opts[:scope] == :all + project = opts[:project] || @project + if project html['data-project-id'] = project.id elsif @group diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb index 2f2d2721d6d..630e10ea892 100644 --- a/app/helpers/sorting_helper.rb +++ b/app/helpers/sorting_helper.rb @@ -8,6 +8,8 @@ module SortingHelper sort_value_oldest_created => sort_title_oldest_created, sort_value_milestone_soon => sort_title_milestone_soon, sort_value_milestone_later => sort_title_milestone_later, + sort_value_due_date_soon => sort_title_due_date_soon, + sort_value_due_date_later => sort_title_due_date_later, sort_value_largest_repo => sort_title_largest_repo, sort_value_recently_signin => sort_title_recently_signin, sort_value_oldest_signin => sort_title_oldest_signin, @@ -50,6 +52,14 @@ module SortingHelper 'Milestone due later' end + def sort_title_due_date_soon + 'Due soon' + end + + def sort_title_due_date_later + 'Due later' + end + def sort_title_name 'Name' end @@ -98,6 +108,14 @@ module SortingHelper 'milestone_due_desc' end + def sort_value_due_date_soon + 'due_date_asc' + end + + def sort_value_due_date_later + 'due_date_desc' + end + def sort_value_name 'name_asc' end diff --git a/app/helpers/tab_helper.rb b/app/helpers/tab_helper.rb index 04e53fe7c61..96a83671009 100644 --- a/app/helpers/tab_helper.rb +++ b/app/helpers/tab_helper.rb @@ -110,4 +110,12 @@ module TabHelper 'active' end end + + def profile_tab_class + if controller.controller_path =~ /\Aprofiles/ + return 'active' + end + + 'active' if current_controller?('oauth/applications') + end end diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb index 4920ca5af6e..dbedf417fa5 100644 --- a/app/helpers/tree_helper.rb +++ b/app/helpers/tree_helper.rb @@ -66,7 +66,7 @@ module TreeHelper ref else project = tree_edit_project(project) - project.repository.next_patch_branch + project.repository.next_branch('patch') end end diff --git a/app/mailers/emails/merge_requests.rb b/app/mailers/emails/merge_requests.rb index 55bb4f65270..9dd11d20ea6 100644 --- a/app/mailers/emails/merge_requests.rb +++ b/app/mailers/emails/merge_requests.rb @@ -56,7 +56,7 @@ module Emails { from: sender(sender_id), to: recipient(recipient_id), - subject: subject("#{@merge_request.title} (##{@merge_request.iid})") + subject: subject("#{@merge_request.title} (#{@merge_request.to_reference})") } end end diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb index f9650df9a74..cdc40b81ee1 100644 --- a/app/mailers/emails/notes.rb +++ b/app/mailers/emails/notes.rb @@ -38,7 +38,7 @@ module Emails { from: sender(@note.author_id), to: recipient(recipient_id), - subject: subject("#{@note.noteable.title} (##{@note.noteable.iid})") + subject: subject("#{@note.noteable.title} (#{@note.noteable.to_reference})") } end diff --git a/app/mailers/repository_check_mailer.rb b/app/mailers/repository_check_mailer.rb index 2bff5b63cc4..21db2fe04a0 100644 --- a/app/mailers/repository_check_mailer.rb +++ b/app/mailers/repository_check_mailer.rb @@ -8,7 +8,7 @@ class RepositoryCheckMailer < BaseMailer mail( to: User.admins.pluck(:email), - subject: @message + subject: "GitLab Admin | #{@message}" ) end end diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 85ef0523b31..553cd447971 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -37,8 +37,6 @@ module Ci class Build < CommitStatus - LAZY_ATTRIBUTES = ['trace'] - belongs_to :runner, class_name: 'Ci::Runner' belongs_to :trigger_request, class_name: 'Ci::TriggerRequest' belongs_to :erased_by, class_name: 'User' @@ -50,25 +48,17 @@ module Ci scope :unstarted, ->() { where(runner_id: nil) } scope :ignore_failures, ->() { where(allow_failure: false) } - scope :similar, ->(build) { where(ref: build.ref, tag: build.tag, trigger_request_id: build.trigger_request_id) } mount_uploader :artifacts_file, ArtifactUploader mount_uploader :artifacts_metadata, ArtifactUploader acts_as_taggable - # To prevent db load megabytes of data from trace - default_scope -> { select(Ci::Build.columns_without_lazy) } - before_destroy { project } - class << self - def columns_without_lazy - (column_names - LAZY_ATTRIBUTES).map do |column_name| - "#{table_name}.#{column_name}" - end - end + after_create :execute_hooks + class << self def last_month where('created_at > ?', Date.today - 1.month) end @@ -126,12 +116,16 @@ module Ci end def retried? - !self.commit.latest_statuses_for_ref(self.ref).include?(self) + !self.commit.statuses.latest.include?(self) + end + + def retry + Ci::Build.retry(self) end def depends_on_builds # Get builds of the same type - latest_builds = self.commit.builds.similar(self).latest + latest_builds = self.commit.builds.latest # Return builds from previous stages latest_builds.where('stage_idx < ?', stage_idx) @@ -230,12 +224,33 @@ module Ci end end + def trace_length + if raw_trace + raw_trace.length + else + 0 + end + end + def trace=(trace) + recreate_trace_dir + File.write(path_to_trace, trace) + end + + def recreate_trace_dir unless Dir.exists?(dir_to_trace) FileUtils.mkdir_p(dir_to_trace) end + end + private :recreate_trace_dir - File.write(path_to_trace, trace) + def append_trace(trace_part, offset) + recreate_trace_dir + + File.truncate(path_to_trace, offset) if File.exist?(path_to_trace) + File.open(path_to_trace, 'a') do |f| + f.write(trace_part) + end end def dir_to_trace diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb index f4cf7034b14..f2667e5476b 100644 --- a/app/models/ci/commit.rb +++ b/app/models/ci/commit.rb @@ -19,21 +19,28 @@ module Ci class Commit < ActiveRecord::Base extend Ci::Model + include Statuseable belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id has_many :statuses, class_name: 'CommitStatus' has_many :builds, class_name: 'Ci::Build' has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest' + delegate :stages, to: :statuses + validates_presence_of :sha + validates_presence_of :status validate :valid_commit_sha + # Invalidate object and save if when touched + after_touch :update_state + def self.truncate_sha(sha) sha[0...8] end - def to_param - sha + def self.stages + CommitStatus.where(commit: all).stages end def project_id @@ -68,15 +75,20 @@ module Ci nil end - def stage - running_or_pending = statuses.latest.running_or_pending.ordered - running_or_pending.first.try(:stage) + def branch? + !tag? + end + + def retryable? + builds.latest.any? do |build| + build.failed? && build.retryable? + end end - def create_builds(ref, tag, user, trigger_request = nil) + def create_builds(user, trigger_request = nil) return unless config_processor config_processor.stages.any? do |stage| - CreateBuildsService.new.execute(self, stage, ref, tag, user, trigger_request, 'success').present? + CreateBuildsService.new(self).execute(stage, user, 'success', trigger_request).present? end end @@ -84,7 +96,7 @@ module Ci return unless config_processor # don't create other builds if this one is retried - latest_builds = builds.similar(build).latest + latest_builds = builds.latest return unless latest_builds.exists?(build.id) # get list of stages after this build @@ -92,88 +104,21 @@ module Ci next_stages.delete(build.stage) # get status for all prior builds - prior_builds = latest_builds.reject { |other_build| next_stages.include?(other_build.stage) } - status = Ci::Status.get_status(prior_builds) + prior_builds = latest_builds.where.not(stage: next_stages) + prior_status = prior_builds.status # create builds for next stages based next_stages.any? do |stage| - CreateBuildsService.new.execute(self, stage, build.ref, build.tag, build.user, build.trigger_request, status).present? + CreateBuildsService.new(self).execute(stage, build.user, prior_status, build.trigger_request).present? end end - def refs - statuses.order(:ref).pluck(:ref).uniq - end - - def latest_statuses - @latest_statuses ||= statuses.latest.to_a - end - - def latest_statuses_for_ref(ref) - latest_statuses.select { |status| status.ref == ref } - end - - def matrix_builds(build = nil) - matrix_builds = builds.latest.ordered - matrix_builds = matrix_builds.similar(build) if build - matrix_builds.to_a - end - def retried @retried ||= (statuses.order(id: :desc) - statuses.latest) end - def status - if yaml_errors.present? - return 'failed' - end - - @status ||= Ci::Status.get_status(latest_statuses) - end - - def pending? - status == 'pending' - end - - def running? - status == 'running' - end - - def success? - status == 'success' - end - - def failed? - status == 'failed' - end - - def canceled? - status == 'canceled' - end - - def active? - running? || pending? - end - - def complete? - canceled? || success? || failed? - end - - def duration - duration_array = statuses.map(&:duration).compact - duration_array.reduce(:+).to_i - end - - def started_at - @started_at ||= statuses.order('started_at ASC').first.try(:started_at) - end - - def finished_at - @finished_at ||= statuses.order('finished_at DESC').first.try(:finished_at) - end - def coverage - coverage_array = latest_statuses.map(&:coverage).compact + coverage_array = statuses.latest.map(&:coverage).compact if coverage_array.size >= 1 '%.2f' % (coverage_array.reduce(:+) / coverage_array.size) end @@ -181,23 +126,29 @@ module Ci def config_processor return nil unless ci_yaml_file - @config_processor ||= Ci::GitlabCiYamlProcessor.new(ci_yaml_file, project.path_with_namespace) - rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e - save_yaml_error(e.message) - nil - rescue - save_yaml_error("Undefined error") - nil + return @config_processor if defined?(@config_processor) + + @config_processor ||= begin + Ci::GitlabCiYamlProcessor.new(ci_yaml_file, project.path_with_namespace) + rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e + save_yaml_error(e.message) + nil + rescue + save_yaml_error("Undefined error") + nil + end end def ci_yaml_file + return @ci_yaml_file if defined?(@ci_yaml_file) + @ci_yaml_file ||= begin blob = project.repository.blob_at(sha, '.gitlab-ci.yml') blob.load_all_data!(project.repository) blob.data + rescue + nil end - rescue - nil end def skip_ci? @@ -206,10 +157,23 @@ module Ci private + def update_state + statuses.reload + self.status = if yaml_errors.blank? + statuses.latest.status || 'skipped' + else + 'failed' + end + self.started_at = statuses.started_at + self.finished_at = statuses.finished_at + self.duration = statuses.latest.duration + save + end + def save_yaml_error(error) return if self.yaml_errors? self.yaml_errors = error - save + update_state end end end diff --git a/app/models/commit.rb b/app/models/commit.rb index d1f07ccd55c..562c3ed15b2 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -15,8 +15,8 @@ class Commit DIFF_SAFE_LINES = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_lines] # Commits above this size will not be rendered in HTML - DIFF_HARD_LIMIT_FILES = 1000 unless defined?(DIFF_HARD_LIMIT_FILES) - DIFF_HARD_LIMIT_LINES = 50000 unless defined?(DIFF_HARD_LIMIT_LINES) + DIFF_HARD_LIMIT_FILES = 1000 + DIFF_HARD_LIMIT_LINES = 50000 class << self def decorate(commits, project) @@ -207,17 +207,22 @@ class Commit @raw.short_id(7) end - def ci_commit - project.ci_commit(sha) + def ci_commits + @ci_commits ||= project.ci_commits.where(sha: sha) end def status - ci_commit.try(:status) || :not_found + return @status if defined?(@status) + @status ||= ci_commits.status end def revert_branch_name "revert-#{short_id}" end + + def cherry_pick_branch_name + project.repository.next_branch("cherry-pick-#{short_id}", mild: true) + end def revert_description if merged_merge_request @@ -253,6 +258,10 @@ class Commit end.any? { |commit_ref| commit_ref.reverts_commit?(self) } end + def change_type_title + merged_merge_request ? 'merge request' : 'commit' + end + private def repo_changes diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 3377a85a55a..aa56314aa16 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -33,30 +33,23 @@ # class CommitStatus < ActiveRecord::Base + include Statuseable + self.table_name = 'ci_builds' belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id - belongs_to :commit, class_name: 'Ci::Commit' + belongs_to :commit, class_name: 'Ci::Commit', touch: true belongs_to :user validates :commit, presence: true - validates :status, inclusion: { in: %w(pending running failed success canceled) } validates_presence_of :name alias_attribute :author, :user - scope :running, -> { where(status: 'running') } - scope :pending, -> { where(status: 'pending') } - scope :success, -> { where(status: 'success') } - scope :failed, -> { where(status: 'failed') } - scope :running_or_pending, -> { where(status: [:running, :pending]) } - scope :finished, -> { where(status: [:success, :failed, :canceled]) } - scope :latest, -> { where(id: unscope(:select).select('max(id)').group(:name, :ref)) } + scope :latest, -> { where(id: unscope(:select).select('max(id)').group(:name, :commit_id)) } scope :ordered, -> { order(:ref, :stage_idx, :name) } - scope :for_ref, ->(ref) { where(ref: ref) } - - AVAILABLE_STATUSES = ['pending', 'running', 'success', 'failed', 'canceled'] + scope :ignored, -> { where(allow_failure: true, status: [:failed, :canceled]) } state_machine :status, initial: :pending do event :run do @@ -86,31 +79,24 @@ class CommitStatus < ActiveRecord::Base after_transition [:pending, :running] => :success do |commit_status| MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.commit.project, nil).trigger(commit_status) end - - state :pending, value: 'pending' - state :running, value: 'running' - state :failed, value: 'failed' - state :success, value: 'success' - state :canceled, value: 'canceled' end - delegate :sha, :short_sha, to: :commit, prefix: false + delegate :sha, :short_sha, to: :commit - # TODO: this should be removed with all references def before_sha - Gitlab::Git::BLANK_SHA + commit.before_sha || Gitlab::Git::BLANK_SHA end - def started? - !pending? && !canceled? && started_at + def self.stages + order_by = 'max(stage_idx)' + group('stage').order(order_by).pluck(:stage, order_by).map(&:first).compact end - def active? - running? || pending? - end - - def complete? - canceled? || success? || failed? + def self.stages_status + all.stages.inject({}) do |h, stage| + h[stage] = all.where(stage: stage).status + h + end end def ignored? @@ -118,11 +104,13 @@ class CommitStatus < ActiveRecord::Base end def duration - if started_at && finished_at - finished_at - started_at - elsif started_at - Time.now - started_at - end + duration = + if started_at && finished_at + finished_at - started_at + elsif started_at + Time.now - started_at + end + duration end def stuck? diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index afa2ca039ae..d5166e81474 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -37,7 +37,6 @@ module Issuable scope :closed, -> { with_state(:closed) } scope :order_milestone_due_desc, -> { joins(:milestone).reorder('milestones.due_date DESC, milestones.id DESC') } scope :order_milestone_due_asc, -> { joins(:milestone).reorder('milestones.due_date ASC, milestones.id ASC') } - scope :with_label, ->(title) { joins(:labels).where(labels: { title: title }) } scope :without_label, -> { joins("LEFT OUTER JOIN label_links ON label_links.target_type = '#{name}' AND label_links.target_id = #{table_name}.id").where(label_links: { id: nil }) } scope :join_project, -> { joins(:project) } @@ -122,6 +121,14 @@ module Issuable joins(join_clause).group(issuable_table[:id]).reorder("COUNT(notes.id) DESC") end + + def with_label(title) + if title.is_a?(Array) && title.count > 1 + joins(:labels).where(labels: { title: title }).group('issues.id').having("count(distinct labels.title) = #{title.count}") + else + joins(:labels).where(labels: { title: title }) + end + end end def today? diff --git a/app/models/concerns/statuseable.rb b/app/models/concerns/statuseable.rb new file mode 100644 index 00000000000..8a293b7b76e --- /dev/null +++ b/app/models/concerns/statuseable.rb @@ -0,0 +1,81 @@ +module Statuseable + extend ActiveSupport::Concern + + AVAILABLE_STATUSES = %w(pending running success failed canceled skipped) + + class_methods do + def status_sql + builds = all.select('count(*)').to_sql + success = all.success.select('count(*)').to_sql + ignored = all.ignored.select('count(*)').to_sql if all.respond_to?(:ignored) + ignored ||= '0' + pending = all.pending.select('count(*)').to_sql + running = all.running.select('count(*)').to_sql + canceled = all.canceled.select('count(*)').to_sql + skipped = all.skipped.select('count(*)').to_sql + + deduce_status = "(CASE + WHEN (#{builds})=0 THEN NULL + WHEN (#{builds})=(#{success})+(#{ignored}) THEN 'success' + WHEN (#{builds})=(#{pending}) THEN 'pending' + WHEN (#{builds})=(#{canceled}) THEN 'canceled' + WHEN (#{builds})=(#{skipped}) THEN 'skipped' + WHEN (#{running})+(#{pending})>0 THEN 'running' + ELSE 'failed' + END)" + + deduce_status + end + + def status + all.pluck(self.status_sql).first + end + + def duration + duration_array = all.map(&:duration).compact + duration_array.reduce(:+) + end + + def started_at + all.minimum(:started_at) + end + + def finished_at + all.maximum(:finished_at) + end + end + + included do + validates :status, inclusion: { in: AVAILABLE_STATUSES } + + state_machine :status, initial: :pending do + state :pending, value: 'pending' + state :running, value: 'running' + state :failed, value: 'failed' + state :success, value: 'success' + state :canceled, value: 'canceled' + state :skipped, value: 'skipped' + end + + scope :running, -> { where(status: 'running') } + scope :pending, -> { where(status: 'pending') } + scope :success, -> { where(status: 'success') } + scope :failed, -> { where(status: 'failed') } + scope :canceled, -> { where(status: 'canceled') } + scope :skipped, -> { where(status: 'skipped') } + scope :running_or_pending, -> { where(status: [:running, :pending]) } + scope :finished, -> { where(status: [:success, :failed, :canceled]) } + end + + def started? + !pending? && !canceled? && started_at + end + + def active? + running? || pending? + end + + def complete? + canceled? || success? || failed? + end +end diff --git a/app/models/group.rb b/app/models/group.rb index 9a04ac70d35..1f8432e3320 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -15,7 +15,6 @@ # require 'carrierwave/orm/activerecord' -require 'file_size_validator' class Group < Namespace include Gitlab::ConfigHelper diff --git a/app/models/hooks/project_hook.rb b/app/models/hooks/project_hook.rb index fe923fafbe0..bc6e0f98c3c 100644 --- a/app/models/hooks/project_hook.rb +++ b/app/models/hooks/project_hook.rb @@ -21,10 +21,9 @@ class ProjectHook < WebHook belongs_to :project - scope :push_hooks, -> { where(push_events: true) } - scope :tag_push_hooks, -> { where(tag_push_events: true) } scope :issue_hooks, -> { where(issues_events: true) } scope :note_hooks, -> { where(note_events: true) } scope :merge_request_hooks, -> { where(merge_requests_events: true) } scope :build_hooks, -> { where(build_events: true) } + scope :wiki_page_hooks, -> { where(wiki_page_events: true) } end diff --git a/app/models/hooks/system_hook.rb b/app/models/hooks/system_hook.rb index c147d8762a9..15dddcc2447 100644 --- a/app/models/hooks/system_hook.rb +++ b/app/models/hooks/system_hook.rb @@ -19,4 +19,7 @@ # class SystemHook < WebHook + def async_execute(data, hook_name) + Sidekiq::Client.enqueue(SystemHookWorker, id, data, hook_name) + end end diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb index 7a13c3f0a39..3a2e4f546f7 100644 --- a/app/models/hooks/web_hook.rb +++ b/app/models/hooks/web_hook.rb @@ -30,6 +30,9 @@ class WebHook < ActiveRecord::Base default_value_for :build_events, false default_value_for :enable_ssl_verification, true + scope :push_hooks, -> { where(push_events: true) } + scope :tag_push_hooks, -> { where(tag_push_events: true) } + # HTTParty timeout default_timeout Gitlab.config.gitlab.webhook_timeout diff --git a/app/models/issue.rb b/app/models/issue.rb index 3f188e04770..ea1bfb776ee 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -20,7 +20,6 @@ # require 'carrierwave/orm/activerecord' -require 'file_size_validator' class Issue < ActiveRecord::Base include InternalId @@ -29,6 +28,13 @@ class Issue < ActiveRecord::Base include Sortable include Taskable + DueDateStruct = Struct.new(:title, :name).freeze + NoDueDate = DueDateStruct.new('No Due Date', '0').freeze + AnyDueDate = DueDateStruct.new('Any Due Date', '').freeze + Overdue = DueDateStruct.new('Overdue', 'overdue').freeze + DueThisWeek = DueDateStruct.new('Due This Week', 'week').freeze + DueThisMonth = DueDateStruct.new('Due This Month', 'month').freeze + ActsAsTaggableOn.strict_case_match = true belongs_to :project @@ -40,6 +46,13 @@ class Issue < ActiveRecord::Base scope :open_for, ->(user) { opened.assigned_to(user) } scope :in_projects, ->(project_ids) { where(project_id: project_ids) } + scope :without_due_date, -> { where(due_date: nil) } + scope :due_before, ->(date) { where('issues.due_date < ?', date) } + scope :due_between, ->(from_date, to_date) { where('issues.due_date >= ?', from_date).where('issues.due_date <= ?', to_date) } + + scope :order_due_date_asc, -> { reorder('issues.due_date IS NULL, issues.due_date ASC') } + scope :order_due_date_desc, -> { reorder('issues.due_date IS NULL, issues.due_date DESC') } + state_machine :state, initial: :opened do event :close do transition [:reopened, :opened] => :closed @@ -83,6 +96,15 @@ class Issue < ActiveRecord::Base @link_reference_pattern ||= super("issues", /(?<issue>\d+)/) end + def self.sort(method) + case method.to_s + when 'due_date_asc' then order_due_date_asc + when 'due_date_desc' then order_due_date_desc + else + super + end + end + def to_reference(from_project = nil) reference = "#{self.class.reference_prefix}#{iid}" @@ -104,10 +126,16 @@ class Issue < ActiveRecord::Base end end - def related_branches - project.repository.branch_names.select do |branch| + # All branches containing the current issue's ID, except for + # 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 =~ /\A#{iid}-(?!\d+-stable)/i end + + branches_with_merge_request = self.referenced_merge_requests(current_user).map(&:source_branch) + + branches_with_iid - branches_with_merge_request end # Reset issue events cache @@ -151,13 +179,21 @@ class Issue < ActiveRecord::Base end def to_branch_name - "#{iid}-#{title.parameterize}" + if self.confidential? + "#{iid}-confidential-issue" + else + "#{iid}-#{title.parameterize}" + end end def can_be_worked_on?(current_user) !self.closed? && !self.project.forked? && - self.related_branches.empty? && + self.related_branches(current_user).empty? && self.closed_by_merge_requests(current_user).empty? end + + def overdue? + due_date.try(:past?) || false + end end diff --git a/app/models/label.rb b/app/models/label.rb index 55c01cae762..60bdce32952 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -113,6 +113,10 @@ class Label < ActiveRecord::Base template end + def text_color + LabelsHelper::text_color_for_bg(self.color) + end + private def label_format_reference(format = :id) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index e410febdfff..d00919c3b0c 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -27,9 +27,6 @@ # merge_commit_sha :string # -require Rails.root.join("app/models/commit") -require Rails.root.join("lib/static_model") - class MergeRequest < ActiveRecord::Base include InternalId include Issuable @@ -589,7 +586,7 @@ class MergeRequest < ActiveRecord::Base end def ci_commit - @ci_commit ||= source_project.ci_commit(last_commit.id) if last_commit && source_project + @ci_commit ||= source_project.ci_commit(last_commit.id, source_branch) if last_commit && source_project end def diff_refs @@ -605,4 +602,8 @@ class MergeRequest < ActiveRecord::Base def can_be_reverted?(current_user = nil) merge_commit && !merge_commit.has_been_reverted?(current_user, self) end + + def can_be_cherry_picked? + merge_commit + end end diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 33884118595..0580cafdd1b 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -11,8 +11,6 @@ # updated_at :datetime # -require Rails.root.join("app/models/commit") - class MergeRequestDiff < ActiveRecord::Base include Sortable diff --git a/app/models/note.rb b/app/models/note.rb index 87ced65c650..71b4293d57a 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -20,7 +20,6 @@ # require 'carrierwave/orm/activerecord' -require 'file_size_validator' class Note < ActiveRecord::Base include Gitlab::CurrentSettings diff --git a/app/models/project.rb b/app/models/project.rb index 8f20922e3c5..7aa21b19e67 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -40,7 +40,6 @@ # require 'carrierwave/orm/activerecord' -require 'file_size_validator' class Project < ActiveRecord::Base include Gitlab::ConfigHelper @@ -831,8 +830,8 @@ class Project < ActiveRecord::Base end end - def hook_attrs - { + def hook_attrs(backward: true) + attrs = { name: name, description: description, web_url: web_url, @@ -843,12 +842,19 @@ class Project < ActiveRecord::Base visibility_level: visibility_level, path_with_namespace: path_with_namespace, default_branch: default_branch, - # Backward compatibility - homepage: web_url, - url: url_to_repo, - ssh_url: ssh_url_to_repo, - http_url: http_url_to_repo } + + # Backward compatibility + if backward + attrs.merge!({ + homepage: web_url, + url: url_to_repo, + ssh_url: ssh_url_to_repo, + http_url: http_url_to_repo + }) + end + + attrs end # Reset events cache related to this project @@ -957,12 +963,12 @@ class Project < ActiveRecord::Base !namespace.share_with_group_lock end - def ci_commit(sha) - ci_commits.find_by(sha: sha) + def ci_commit(sha, ref) + ci_commits.order(id: :desc).find_by(sha: sha, ref: ref) end - def ensure_ci_commit(sha) - ci_commit(sha) || ci_commits.create(sha: sha) + def ensure_ci_commit(sha, ref) + ci_commit(sha, ref) || ci_commits.create(sha: sha, ref: ref) end def enable_ci diff --git a/app/models/project_import_data.rb b/app/models/project_import_data.rb index 79efb403058..2c0ae312f1b 100644 --- a/app/models/project_import_data.rb +++ b/app/models/project_import_data.rb @@ -8,7 +8,6 @@ # require 'carrierwave/orm/activerecord' -require 'file_size_validator' class ProjectImportData < ActiveRecord::Base belongs_to :project diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb index 0e3fa4a40fe..064ef8e8674 100644 --- a/app/models/project_services/hipchat_service.rb +++ b/app/models/project_services/hipchat_service.rb @@ -183,7 +183,7 @@ class HipchatService < Service title = obj_attr[:title] merge_request_url = "#{project_url}/merge_requests/#{merge_request_id}" - merge_request_link = "<a href=\"#{merge_request_url}\">merge request ##{merge_request_id}</a>" + merge_request_link = "<a href=\"#{merge_request_url}\">merge request !#{merge_request_id}</a>" message = "#{user_name} #{state} #{merge_request_link} in " \ "#{project_link}: <b>#{title}</b>" @@ -224,7 +224,7 @@ class HipchatService < Service when "MergeRequest" subj_attr = HashWithIndifferentAccess.new(data[:merge_request]) subject_id = subj_attr[:iid] - subject_desc = "##{subject_id}" + subject_desc = "!#{subject_id}" subject_type = "merge request" title = format_title(subj_attr[:title]) when "Snippet" diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb index d89cf6d17b2..fd65027f084 100644 --- a/app/models/project_services/slack_service.rb +++ b/app/models/project_services/slack_service.rb @@ -60,7 +60,7 @@ class SlackService < Service end def supported_events - %w(push issue merge_request note tag_push build) + %w(push issue merge_request note tag_push build wiki_page) end def execute(data) @@ -90,6 +90,8 @@ class SlackService < Service NoteMessage.new(data) when "build" BuildMessage.new(data) if should_build_be_notified?(data) + when "wiki_page" + WikiPageMessage.new(data) end opt = {} @@ -133,3 +135,4 @@ require "slack_service/push_message" require "slack_service/merge_message" require "slack_service/note_message" require "slack_service/build_message" +require "slack_service/wiki_page_message" diff --git a/app/models/project_services/slack_service/merge_message.rb b/app/models/project_services/slack_service/merge_message.rb index e792c258f73..11fc691022b 100644 --- a/app/models/project_services/slack_service/merge_message.rb +++ b/app/models/project_services/slack_service/merge_message.rb @@ -50,7 +50,7 @@ class SlackService end def merge_request_link - "[merge request ##{merge_request_id}](#{merge_request_url})" + "[merge request !#{merge_request_id}](#{merge_request_url})" end def merge_request_url diff --git a/app/models/project_services/slack_service/note_message.rb b/app/models/project_services/slack_service/note_message.rb index b15d9a14677..89ba51cb662 100644 --- a/app/models/project_services/slack_service/note_message.rb +++ b/app/models/project_services/slack_service/note_message.rb @@ -58,7 +58,7 @@ class SlackService def create_merge_note(merge_request) commented_on_message( - "[merge request ##{merge_request[:iid]}](#{@note_url})", + "[merge request !#{merge_request[:iid]}](#{@note_url})", format_title(merge_request[:title])) end diff --git a/app/models/project_services/slack_service/wiki_page_message.rb b/app/models/project_services/slack_service/wiki_page_message.rb new file mode 100644 index 00000000000..f336d9e7691 --- /dev/null +++ b/app/models/project_services/slack_service/wiki_page_message.rb @@ -0,0 +1,53 @@ +class SlackService + class WikiPageMessage < BaseMessage + attr_reader :user_name + attr_reader :title + attr_reader :project_name + attr_reader :project_url + attr_reader :wiki_page_url + attr_reader :action + attr_reader :description + + def initialize(params) + @user_name = params[:user][:name] + @project_name = params[:project_name] + @project_url = params[:project_url] + + obj_attr = params[:object_attributes] + obj_attr = HashWithIndifferentAccess.new(obj_attr) + @title = obj_attr[:title] + @wiki_page_url = obj_attr[:url] + @description = obj_attr[:content] + + @action = + case obj_attr[:action] + when "create" + "created" + when "update" + "edited" + end + end + + def attachments + description_message + end + + private + + def message + "#{user_name} #{action} #{wiki_page_link} in #{project_link}: *#{title}*" + end + + def description_message + [{ text: format(@description), color: attachment_color }] + end + + def project_link + "[#{project_name}](#{project_url})" + end + + def wiki_page_link + "[wiki page](#{wiki_page_url})" + end + end +end diff --git a/app/models/repository.rb b/app/models/repository.rb index 589756f8531..da751591103 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -228,7 +228,8 @@ class Repository def cache_keys %i(size branch_names tag_names commit_count - readme version contribution_guide changelog license) + readme version contribution_guide changelog + license_blob license_key) end def build_cache @@ -461,27 +462,21 @@ class Repository end end - def license - cache.fetch(:license) do - licenses = tree(:head).blobs.find_all do |file| - file.name =~ /\A(copying|license|licence)/i - end - - preferences = [ - /\Alicen[sc]e\z/i, # LICENSE, LICENCE - /\Alicen[sc]e\./i, # LICENSE.md, LICENSE.txt - /\Acopying\z/i, # COPYING - /\Acopying\.(?!lesser)/i, # COPYING.txt - /Acopying.lesser/i # COPYING.LESSER - ] + def license_blob + return nil if !exists? || empty? - license = nil - preferences.each do |r| - license = licenses.find { |l| l.name =~ r } - break if license + cache.fetch(:license_blob) do + if licensee_project.license + blob_at_branch(root_ref, licensee_project.matched_file.filename) end + end + end - license + def license_key + return nil if !exists? || empty? + + cache.fetch(:license_key) do + licensee_project.license.try(:key) || 'no-license' end end @@ -549,15 +544,18 @@ class Repository commit(sha) end - def next_patch_branch - patch_branch_ids = self.branch_names.map do |n| - result = n.match(/\Apatch-([0-9]+)\z/) + def next_branch(name, opts={}) + branch_ids = self.branch_names.map do |n| + next 1 if n == name + result = n.match(/\A#{name}-([0-9]+)\z/) result[1].to_i if result end.compact - highest_patch_branch_id = patch_branch_ids.max || 0 + highest_branch_id = branch_ids.max || 0 - "patch-#{highest_patch_branch_id + 1}" + return name if opts[:mild] && 0 == highest_branch_id + + "#{name}-#{highest_branch_id + 1}" end # Remove archives older than 2 hours @@ -760,6 +758,28 @@ class Repository end end + def cherry_pick(user, commit, base_branch, cherry_pick_tree_id = nil) + source_sha = find_branch(base_branch).target + cherry_pick_tree_id ||= check_cherry_pick_content(commit, base_branch) + + return false unless cherry_pick_tree_id + + commit_with_hooks(user, base_branch) do |ref| + committer = user_to_committer(user) + source_sha = Rugged::Commit.create(rugged, + message: commit.message, + author: { + email: commit.author_email, + name: commit.author_name, + time: commit.authored_date + }, + committer: committer, + tree: cherry_pick_tree_id, + parents: [rugged.lookup(source_sha)], + update_ref: ref) + end + end + def check_revert_content(commit, base_branch) source_sha = find_branch(base_branch).target args = [commit.id, source_sha] @@ -774,6 +794,20 @@ class Repository tree_id end + def check_cherry_pick_content(commit, base_branch) + source_sha = find_branch(base_branch).target + args = [commit.id, source_sha] + args << 1 if commit.merge_commit? + + cherry_pick_index = rugged.cherrypick_commit(*args) + return false if cherry_pick_index.conflicts? + + tree_id = cherry_pick_index.write_tree(rugged) + return false unless diff_exists?(source_sha, tree_id) + + tree_id + end + def diff_exists?(sha1, sha2) rugged.diff(sha1, sha2).size > 0 end @@ -925,4 +959,8 @@ class Repository def cache @cache ||= RepositoryCache.new(path_with_namespace) end + + def licensee_project + @licensee_project ||= Licensee.project(path) + end end diff --git a/app/models/service.rb b/app/models/service.rb index 721273250ea..2645b8321d7 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -32,6 +32,7 @@ class Service < ActiveRecord::Base default_value_for :tag_push_events, true default_value_for :note_events, true default_value_for :build_events, true + default_value_for :wiki_page_events, true after_initialize :initialize_properties @@ -53,6 +54,7 @@ class Service < ActiveRecord::Base scope :merge_request_hooks, -> { where(merge_requests_events: true, active: true) } scope :note_hooks, -> { where(note_events: true, active: true) } scope :build_hooks, -> { where(build_events: true, active: true) } + scope :wiki_page_hooks, -> { where(wiki_page_events: true, active: true) } default_value_for :category, 'common' @@ -94,7 +96,7 @@ class Service < ActiveRecord::Base end def supported_events - %w(push tag_push issue merge_request) + %w(push tag_push issue merge_request wiki_page) end def execute(data) diff --git a/app/models/user.rb b/app/models/user.rb index 031315debd7..ab48f8f1960 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -63,7 +63,6 @@ # require 'carrierwave/orm/activerecord' -require 'file_size_validator' class User < ActiveRecord::Base extend Gitlab::ConfigHelper diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb index 526760779a4..3d5fd9d3ee9 100644 --- a/app/models/wiki_page.rb +++ b/app/models/wiki_page.rb @@ -29,6 +29,10 @@ class WikiPage # new Page values before writing to the Gollum repository. attr_accessor :attributes + def hook_attrs + attributes + end + def initialize(wiki, page = nil, persisted = false) @wiki = wiki @page = page diff --git a/app/services/ci/create_builds_service.rb b/app/services/ci/create_builds_service.rb index 2cd51a7610f..18274ce24e2 100644 --- a/app/services/ci/create_builds_service.rb +++ b/app/services/ci/create_builds_service.rb @@ -1,7 +1,11 @@ module Ci class CreateBuildsService - def execute(commit, stage, ref, tag, user, trigger_request, status) - builds_attrs = commit.config_processor.builds_for_stage_and_ref(stage, ref, tag, trigger_request) + def initialize(commit) + @commit = commit + end + + def execute(stage, user, status, trigger_request = nil) + builds_attrs = config_processor.builds_for_stage_and_ref(stage, @commit.ref, @commit.tag, trigger_request) # check when to create next build builds_attrs = builds_attrs.select do |build_attrs| @@ -17,7 +21,8 @@ module Ci builds_attrs.map do |build_attrs| # don't create the same build twice - unless commit.builds.find_by(ref: ref, tag: tag, trigger_request: trigger_request, name: build_attrs[:name]) + unless @commit.builds.find_by(ref: @commit.ref, tag: @commit.tag, + trigger_request: trigger_request, name: build_attrs[:name]) build_attrs.slice!(:name, :commands, :tag_list, @@ -26,17 +31,21 @@ module Ci :stage, :stage_idx) - build_attrs.merge!(ref: ref, - tag: tag, + build_attrs.merge!(ref: @commit.ref, + tag: @commit.tag, trigger_request: trigger_request, user: user, - project: commit.project) + project: @commit.project) - build = commit.builds.create!(build_attrs) - build.execute_hooks - build + @commit.builds.create!(build_attrs) end end end + + private + + def config_processor + @config_processor ||= @commit.config_processor + end end end diff --git a/app/services/ci/create_trigger_request_service.rb b/app/services/ci/create_trigger_request_service.rb index b3dfc707221..993acf11db9 100644 --- a/app/services/ci/create_trigger_request_service.rb +++ b/app/services/ci/create_trigger_request_service.rb @@ -7,14 +7,14 @@ module Ci # check if ref is tag tag = project.repository.find_tag(ref).present? - ci_commit = project.ensure_ci_commit(commit.sha) + ci_commit = project.ci_commits.create(sha: commit.sha, ref: ref, tag: tag) trigger_request = trigger.trigger_requests.create!( variables: variables, commit: ci_commit, ) - if ci_commit.create_builds(ref, tag, nil, trigger_request) + if ci_commit.create_builds(nil, trigger_request) trigger_request end end diff --git a/app/services/ci/image_for_build_service.rb b/app/services/ci/image_for_build_service.rb index 50c95ced8a7..3018f27ec05 100644 --- a/app/services/ci/image_for_build_service.rb +++ b/app/services/ci/image_for_build_service.rb @@ -3,8 +3,9 @@ module Ci def execute(project, opts) sha = opts[:sha] || ref_sha(project, opts[:ref]) - commit = project.ci_commits.find_by(sha: sha) - image_name = image_for_commit(commit) + ci_commits = project.ci_commits.where(sha: sha) + ci_commits = ci_commits.where(ref: opts[:ref]) if opts[:ref] + image_name = image_for_status(ci_commits.status) image_path = Rails.root.join('public/ci', image_name) OpenStruct.new(path: image_path, name: image_name) @@ -16,9 +17,9 @@ module Ci project.commit(ref).try(:sha) if ref end - def image_for_commit(commit) - return 'build-unknown.svg' unless commit - 'build-' + commit.status + ".svg" + def image_for_status(status) + status ||= 'unknown' + 'build-' + status + ".svg" end end end diff --git a/app/services/commits/change_service.rb b/app/services/commits/change_service.rb new file mode 100644 index 00000000000..6b69cb53b2c --- /dev/null +++ b/app/services/commits/change_service.rb @@ -0,0 +1,47 @@ +module Commits + class ChangeService < ::BaseService + class ValidationError < StandardError; end + class ChangeError < StandardError; end + + def execute + @source_project = params[:source_project] || @project + @target_branch = params[:target_branch] + @commit = params[:commit] + @create_merge_request = params[:create_merge_request].present? + + check_push_permissions unless @create_merge_request + commit + rescue Repository::CommitError, Gitlab::Git::Repository::InvalidBlobName, GitHooksService::PreReceiveError, + ValidationError, ChangeError => ex + error(ex.message) + end + + def commit + raise NotImplementedError + end + + private + + def check_push_permissions + allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(@target_branch) + + unless allowed + raise ValidationError.new('You are not allowed to push into this branch') + end + + true + end + + def create_target_branch(new_branch) + # Temporary branch exists and contains the change commit + return success if repository.find_branch(new_branch) + + result = CreateBranchService.new(@project, current_user) + .execute(new_branch, @target_branch, source_project: @source_project) + + if result[:status] == :error + raise ChangeError, "There was an error creating the source branch: #{result[:message]}" + end + end + end +end diff --git a/app/services/commits/cherry_pick_service.rb b/app/services/commits/cherry_pick_service.rb new file mode 100644 index 00000000000..f9a4efa7182 --- /dev/null +++ b/app/services/commits/cherry_pick_service.rb @@ -0,0 +1,19 @@ +module Commits + class CherryPickService < ChangeService + def commit + cherry_pick_into = @create_merge_request ? @commit.cherry_pick_branch_name : @target_branch + cherry_pick_tree_id = repository.check_cherry_pick_content(@commit, @target_branch) + + if cherry_pick_tree_id + create_target_branch(cherry_pick_into) if @create_merge_request + + repository.cherry_pick(current_user, @commit, cherry_pick_into, cherry_pick_tree_id) + success + else + error_msg = "Sorry, we cannot cherry-pick this #{@commit.change_type_title} automatically. + It may have already been cherry-picked, or a more recent commit may have updated some of its content." + raise ChangeError, error_msg + end + end + end +end diff --git a/app/services/commits/revert_service.rb b/app/services/commits/revert_service.rb index a3c950ede1f..c7de9f6f35e 100644 --- a/app/services/commits/revert_service.rb +++ b/app/services/commits/revert_service.rb @@ -1,21 +1,5 @@ module Commits - class RevertService < ::BaseService - class ValidationError < StandardError; end - class ReversionError < StandardError; end - - def execute - @source_project = params[:source_project] || @project - @target_branch = params[:target_branch] - @commit = params[:commit] - @create_merge_request = params[:create_merge_request].present? - - check_push_permissions unless @create_merge_request - commit - rescue Repository::CommitError, Gitlab::Git::Repository::InvalidBlobName, GitHooksService::PreReceiveError, - ValidationError, ReversionError => ex - error(ex.message) - end - + class RevertService < ChangeService def commit revert_into = @create_merge_request ? @commit.revert_branch_name : @target_branch revert_tree_id = repository.check_revert_content(@commit, @target_branch) @@ -26,34 +10,10 @@ module Commits repository.revert(current_user, @commit, revert_into, revert_tree_id) success else - error_msg = "Sorry, we cannot revert this #{params[:revert_type_title]} automatically. + error_msg = "Sorry, we cannot revert this #{@commit.change_type_title} automatically. It may have already been reverted, or a more recent commit may have updated some of its content." - raise ReversionError, error_msg + raise ChangeError, error_msg end end - - private - - def create_target_branch(new_branch) - # Temporary branch exists and contains the revert commit - return success if repository.find_branch(new_branch) - - result = CreateBranchService.new(@project, current_user) - .execute(new_branch, @target_branch, source_project: @source_project) - - if result[:status] == :error - raise ReversionError, "There was an error creating the source branch: #{result[:message]}" - end - end - - def check_push_permissions - allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(@target_branch) - - unless allowed - raise ValidationError.new('You are not allowed to push into this branch') - end - - true - end end end diff --git a/app/services/create_commit_builds_service.rb b/app/services/create_commit_builds_service.rb index 69d5c42a877..0d2aa1ff03d 100644 --- a/app/services/create_commit_builds_service.rb +++ b/app/services/create_commit_builds_service.rb @@ -2,6 +2,7 @@ class CreateCommitBuildsService def execute(project, user, params) return false unless project.builds_enabled? + before_sha = params[:checkout_sha] || params[:before] sha = params[:checkout_sha] || params[:after] origin_ref = params[:ref] @@ -10,15 +11,16 @@ class CreateCommitBuildsService end ref = Gitlab::Git.ref_name(origin_ref) + tag = Gitlab::Git.tag_ref?(origin_ref) # Skip branch removal if sha == Gitlab::Git::BLANK_SHA return false end - commit = project.ci_commit(sha) + commit = project.ci_commit(sha, ref) unless commit - commit = project.ci_commits.new(sha: sha) + commit = project.ci_commits.new(sha: sha, ref: ref, before_sha: before_sha, tag: tag) # Skip creating ci_commit when no gitlab-ci.yml is found unless commit.ci_yaml_file @@ -32,10 +34,10 @@ class CreateCommitBuildsService # Skip creating builds for commits that have [ci skip] unless commit.skip_ci? # Create builds for commit - tag = Gitlab::Git.tag_ref?(origin_ref) - commit.create_builds(ref, tag, user) + commit.create_builds(user) end + commit.touch commit end end diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb index dc74c02760b..1e1be8cd04b 100644 --- a/app/services/git_push_service.rb +++ b/app/services/git_push_service.rb @@ -73,6 +73,7 @@ class GitPushService < BaseService @project.update_merge_requests(params[:oldrev], params[:newrev], params[:ref], current_user) EventCreateService.new.push(@project, current_user, build_push_data) + SystemHooksService.new.execute_hooks(build_push_data_system_hook.dup, :push_hooks) @project.execute_hooks(build_push_data.dup, :push_hooks) @project.execute_services(build_push_data.dup, :push_hooks) CreateCommitBuildsService.new.execute(@project, current_user, build_push_data) @@ -138,6 +139,11 @@ class GitPushService < BaseService build(@project, current_user, params[:oldrev], params[:newrev], params[:ref], push_commits) end + def build_push_data_system_hook + @push_data_system ||= Gitlab::PushDataBuilder. + build(@project, current_user, params[:oldrev], params[:newrev], params[:ref], []) + end + def push_to_existing_branch? # Return if this is not a push to a branch (e.g. new commits) Gitlab::Git.branch_ref?(params[:ref]) && !Gitlab::Git.blank_ref?(params[:oldrev]) diff --git a/app/services/git_tag_push_service.rb b/app/services/git_tag_push_service.rb index c88c7672805..64271d8bc5c 100644 --- a/app/services/git_tag_push_service.rb +++ b/app/services/git_tag_push_service.rb @@ -1,16 +1,16 @@ -class GitTagPushService - attr_accessor :project, :user, :push_data +class GitTagPushService < BaseService + attr_accessor :push_data - def execute(project, user, oldrev, newrev, ref) + def execute project.repository.before_push_tag - @project, @user = project, user - @push_data = build_push_data(oldrev, newrev, ref) + @push_data = build_push_data - EventCreateService.new.push(project, user, @push_data) + EventCreateService.new.push(project, current_user, @push_data) + SystemHooksService.new.execute_hooks(build_system_push_data.dup, :tag_push_hooks) project.execute_hooks(@push_data.dup, :tag_push_hooks) project.execute_services(@push_data.dup, :tag_push_hooks) - CreateCommitBuildsService.new.execute(project, @user, @push_data) + CreateCommitBuildsService.new.execute(project, current_user, @push_data) ProjectCacheWorker.perform_async(project.id) true @@ -18,14 +18,14 @@ class GitTagPushService private - def build_push_data(oldrev, newrev, ref) + def build_push_data commits = [] message = nil - if !Gitlab::Git.blank_ref?(newrev) - tag_name = Gitlab::Git.ref_name(ref) + if !Gitlab::Git.blank_ref?(params[:newrev]) + tag_name = Gitlab::Git.ref_name(params[:ref]) tag = project.repository.find_tag(tag_name) - if tag && tag.target == newrev + if tag && tag.target == params[:newrev] commit = project.commit(tag.target) commits = [commit].compact message = tag.message @@ -33,6 +33,11 @@ class GitTagPushService end Gitlab::PushDataBuilder. - build(project, user, oldrev, newrev, ref, commits, message) + build(project, current_user, params[:oldrev], params[:newrev], params[:ref], commits, message) + end + + def build_system_push_data + Gitlab::PushDataBuilder. + build(project, current_user, params[:oldrev], params[:newrev], params[:ref], [], '') end end diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb index f0615ec7420..e43b5b51e5b 100644 --- a/app/services/system_hooks_service.rb +++ b/app/services/system_hooks_service.rb @@ -3,17 +3,13 @@ class SystemHooksService execute_hooks(build_event_data(model, event)) end - private - - def execute_hooks(data) - SystemHook.all.each do |sh| - async_execute_hook(sh, data, 'system_hooks') + def execute_hooks(data, hooks_scope = :all) + SystemHook.send(hooks_scope).each do |hook| + hook.async_execute(data, 'system_hooks') end end - def async_execute_hook(hook, data, hook_name) - Sidekiq::Client.enqueue(SystemHookWorker, hook.id, data, hook_name) - end + private def build_event_data(model, event) data = { diff --git a/app/services/wiki_pages/base_service.rb b/app/services/wiki_pages/base_service.rb new file mode 100644 index 00000000000..9162f128602 --- /dev/null +++ b/app/services/wiki_pages/base_service.rb @@ -0,0 +1,27 @@ +module WikiPages + class BaseService < ::BaseService + + def hook_data(page, action) + hook_data = { + object_kind: page.class.name.underscore, + user: current_user.hook_attrs, + project: @project.hook_attrs, + object_attributes: page.hook_attrs, + # DEPRECATED + repository: @project.hook_attrs.slice(:name, :url, :description, :homepage) + } + + page_url = Gitlab::UrlBuilder.build(page) + hook_data[:object_attributes].merge!(url: page_url, action: action) + hook_data + end + + private + + def execute_hooks(page, action = 'create') + page_data = hook_data(page, action) + @project.execute_hooks(page_data, :wiki_page_hooks) + @project.execute_services(page_data, :wiki_page_hooks) + end + end +end diff --git a/app/services/wiki_pages/create_service.rb b/app/services/wiki_pages/create_service.rb new file mode 100644 index 00000000000..988c663b9d0 --- /dev/null +++ b/app/services/wiki_pages/create_service.rb @@ -0,0 +1,13 @@ +module WikiPages + class CreateService < WikiPages::BaseService + def execute + page = WikiPage.new(@project.wiki) + + if page.create(@params) + execute_hooks(page, 'create') + end + + page + end + end +end diff --git a/app/services/wiki_pages/update_service.rb b/app/services/wiki_pages/update_service.rb new file mode 100644 index 00000000000..8f6a50da838 --- /dev/null +++ b/app/services/wiki_pages/update_service.rb @@ -0,0 +1,11 @@ +module WikiPages + class UpdateService < WikiPages::BaseService + def execute(page) + if page.update(@params[:content], @params[:format], @params[:message]) + execute_hooks(page, 'update') + end + + page + end + end +end diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index aadd2c54f20..e0d8d16a954 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -155,7 +155,11 @@ = f.label :shared_runners_enabled do = f.check_box :shared_runners_enabled Enable shared runners for new projects - + .form-group + = f.label :shared_runners_text, class: 'control-label col-sm-2' + .col-sm-10 + = f.text_area :shared_runners_text, class: 'form-control', rows: 4 + .help-block Markdown enabled .form-group = f.label :max_artifacts_size, 'Maximum artifacts size (MB)', class: 'control-label col-sm-2' .col-sm-10 @@ -214,6 +218,13 @@ .help-block The sampling interval in seconds. Sampled data includes memory usage, retained Ruby objects, file descriptors and so on. + .form-group + = f.label :metrics_packet_size, 'Metrics per packet', class: 'control-label col-sm-2' + .col-sm-10 + = f.number_field :metrics_packet_size, class: 'form-control' + .help-block + The amount of points to store in a single UDP packet. More points + results in fewer but larger UDP packets being sent. %fieldset %legend Spam and Anti-bot Protection @@ -282,7 +293,7 @@ = f.check_box :repository_checks_enabled Enable Repository Checks .help-block - GitLab will periodically run + 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 @@ -290,7 +301,7 @@ = 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/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml index ad952052f25..67d23c80233 100644 --- a/app/views/admin/hooks/index.html.haml +++ b/app/views/admin/hooks/index.html.haml @@ -17,6 +17,27 @@ .col-sm-10 = f.text_field :url, class: "form-control" .form-group + = f.label :url, "Trigger", class: 'control-label' + .col-sm-10.prepend-top-10 + %div + System hook will be triggered on set of events like creating project + or adding ssh key. But you can also enable extra triggers like Push events. + + %div.prepend-top-default + = f.check_box :push_events, class: 'pull-left' + .prepend-left-20 + = f.label :push_events, class: 'list-label' do + %strong Push events + %p.light + This url will be triggered by a push to the repository + %div + = f.check_box :tag_push_events, class: 'pull-left' + .prepend-left-20 + = f.label :tag_push_events, class: 'list-label' do + %strong Tag push events + %p.light + This url will be triggered when a new tag is pushed to the repository + .form-group = f.label :enable_ssl_verification, "SSL verification", class: 'control-label checkbox' .col-sm-10 .checkbox @@ -31,13 +52,16 @@ .panel.panel-default .panel-heading System hooks (#{@hooks.count}) - %ul.well-list + %ul.content-list - @hooks.each do |hook| %li - .list-item-name - %strong= hook.url - %p SSL Verification: #{hook.enable_ssl_verification ? "enabled" : "disabled"} - - .pull-right + .controls = link_to 'Test Hook', admin_hook_test_path(hook), class: "btn btn-sm" = link_to 'Remove', admin_hook_path(hook), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-remove btn-sm" + .monospace= hook.url + %div + - %w(push_events tag_push_events issues_events note_events merge_requests_events build_events).each do |trigger| + - if hook.send(trigger) + %span.label.label-gray= trigger.titleize + %span.label.label-gray SSL Verification: #{hook.enable_ssl_verification ? "enabled" : "disabled"} + diff --git a/app/views/admin/users/_form.html.haml b/app/views/admin/users/_form.html.haml index b05fdbd5552..fe0b9d3a491 100644 --- a/app/views/admin/users/_form.html.haml +++ b/app/views/admin/users/_form.html.haml @@ -7,17 +7,17 @@ .form-group = f.label :name, class: 'control-label' .col-sm-10 - = f.text_field :name, required: true, autocomplete: "off", class: 'form-control' + = f.text_field :name, required: true, autocomplete: 'off', class: 'form-control' %span.help-inline * required .form-group = f.label :username, class: 'control-label' .col-sm-10 - = f.text_field :username, required: true, autocomplete: "off", class: 'form-control' + = f.text_field :username, required: true, autocomplete: 'off', autocorrect: 'off', autocapitalize: 'off', spellcheck: false, class: 'form-control' %span.help-inline * required .form-group = f.label :email, class: 'control-label' .col-sm-10 - = f.text_field :email, required: true, autocomplete: "off", class: 'form-control' + = f.text_field :email, required: true, autocomplete: 'off', class: 'form-control' %span.help-inline * required - if @user.new_record? diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml index f9ec3a89158..49ab8aad1d5 100644 --- a/app/views/dashboard/todos/index.html.haml +++ b/app/views/dashboard/todos/index.html.haml @@ -45,6 +45,7 @@ .prepend-top-default - if @todos.any? + .js-todos-options{ data: {per_page: @todos.limit_value, current_page: @todos.current_page, total_pages: @todos.total_pages} } - @todos.group_by(&:project).each do |group| .panel.panel-default.panel-small.js-todos-list - project = group[0] diff --git a/app/views/doorkeeper/applications/index.html.haml b/app/views/doorkeeper/applications/index.html.haml index 0aff79749ef..79df17ba612 100644 --- a/app/views/doorkeeper/applications/index.html.haml +++ b/app/views/doorkeeper/applications/index.html.haml @@ -1,5 +1,4 @@ - page_title "Applications" -- header_title page_title, applications_profile_path .row.prepend-top-default .col-lg-3.profile-settings-sidebar diff --git a/app/views/import/github/status.html.haml b/app/views/import/github/status.html.haml index 9639da4cb58..5b7f11440c1 100644 --- a/app/views/import/github/status.html.haml +++ b/app/views/import/github/status.html.haml @@ -26,7 +26,7 @@ - @already_added_projects.each do |project| %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"} %td - = link_to project.import_source, "https://github.com/#{project.import_source}", target: "_blank" + = github_project_link(project.import_source) %td = link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] %td.job-status @@ -43,7 +43,7 @@ - @repos.each do |repo| %tr{id: "repo_#{repo.id}"} %td - = link_to repo.full_name, "https://github.com/#{repo.full_name}", target: "_blank" + = github_project_link(repo.full_name) %td.import-target = repo.full_name %td.import-actions.job-status diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index c799e9c588d..ca9c2a0bf2e 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -25,6 +25,10 @@ .content-wrapper = render "layouts/flash" = yield :flash_message + - if defined?(nav) && nav + .layout-nav + %div{ class: container_class } + = render "layouts/nav/#{nav}" %div{ class: (container_class unless @no_container) } .content .clearfix diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index babfb032236..e4d1c773d03 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -6,6 +6,6 @@ = yield :scripts_body_top = render "layouts/header/default", title: header_title - = render 'layouts/page', sidebar: sidebar + = render 'layouts/page', sidebar: sidebar, nav: nav = yield :scripts_body diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml index 5cef652da14..ca49c313ff7 100644 --- a/app/views/layouts/nav/_dashboard.html.haml +++ b/app/views/layouts/nav/_dashboard.html.haml @@ -48,8 +48,7 @@ %span Help - %li.separate-item - = nav_link(controller: :profile) do + = nav_link(html_options: {class: profile_tab_class}) do = link_to profile_path, title: 'Profile Settings', data: {placement: 'bottom'} do = icon('user fw') %span diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml index 3b9d31a6fc5..d730840d63a 100644 --- a/app/views/layouts/nav/_profile.html.haml +++ b/app/views/layouts/nav/_profile.html.haml @@ -1,17 +1,9 @@ -%ul.nav.nav-sidebar - = nav_link do - = link_to root_path, title: 'Go to dashboard', class: 'back-link' do - = icon('caret-square-o-left fw') - %span - Go to dashboard - - %li.separate-item - +%ul.nav-links = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do = link_to profile_path, title: 'Profile Settings' do = icon('user fw') %span - Profile Settings + Profile = nav_link(controller: [:accounts, :two_factor_auths]) do = link_to profile_account_path, title: 'Account' do = icon('gear fw') @@ -27,7 +19,6 @@ = icon('envelope-o fw') %span Emails - %span.count= number_with_delimiter(current_user.emails.count + 1) - unless current_user.ldap_user? = nav_link(controller: :passwords) do = link_to edit_profile_password_path, title: 'Password' do @@ -45,7 +36,6 @@ = icon('key fw') %span SSH Keys - %span.count= number_with_delimiter(current_user.keys.count) = nav_link(controller: :preferences) do = link_to profile_preferences_path, title: 'Preferences' do -# TODO (rspeicher): Better icon? diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index 86b46e8c75e..a15b7758c4b 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -77,7 +77,7 @@ Merge Requests %span.count.merge_counter= number_with_delimiter(@project.merge_requests.opened.count) - - if project_nav_tab? :settings + - if project_nav_tab? :team = nav_link(controller: [:project_members, :teams]) do = link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: 'team-tab tab' do = icon('users fw') diff --git a/app/views/layouts/profile.html.haml b/app/views/layouts/profile.html.haml index dfa6cc5702e..b77d3402a2e 100644 --- a/app/views/layouts/profile.html.haml +++ b/app/views/layouts/profile.html.haml @@ -1,5 +1,6 @@ - page_title "Profile Settings" - header_title "Profile Settings", profile_path unless header_title -- sidebar "profile" +- sidebar "dashboard" +- nav "profile" = render template: "layouts/application" diff --git a/app/views/notify/closed_merge_request_email.html.haml b/app/views/notify/closed_merge_request_email.html.haml index 574e8bfef24..81c7c88fc96 100644 --- a/app/views/notify/closed_merge_request_email.html.haml +++ b/app/views/notify/closed_merge_request_email.html.haml @@ -1,2 +1,2 @@ %p - = "Merge Request ##{@merge_request.iid} was closed by #{@updated_by.name}" + = "Merge Request #{@merge_request.to_reference} was closed by #{@updated_by.name}" diff --git a/app/views/notify/closed_merge_request_email.text.haml b/app/views/notify/closed_merge_request_email.text.haml index 59db86b08bc..b435067d5a6 100644 --- a/app/views/notify/closed_merge_request_email.text.haml +++ b/app/views/notify/closed_merge_request_email.text.haml @@ -1,4 +1,4 @@ -= "Merge Request ##{@merge_request.iid} was closed by #{@updated_by.name}" += "Merge Request #{@merge_request.to_reference} was closed by #{@updated_by.name}" Merge Request url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)} diff --git a/app/views/notify/merge_request_status_email.html.haml b/app/views/notify/merge_request_status_email.html.haml index c9bf04f514e..41a320d6bd8 100644 --- a/app/views/notify/merge_request_status_email.html.haml +++ b/app/views/notify/merge_request_status_email.html.haml @@ -1,2 +1,2 @@ %p - = "Merge Request ##{@merge_request.iid} was #{@mr_status} by #{@updated_by.name}" + = "Merge Request #{@merge_request.to_reference} was #{@mr_status} by #{@updated_by.name}" diff --git a/app/views/notify/merge_request_status_email.text.haml b/app/views/notify/merge_request_status_email.text.haml index b96dd0fd8ab..7a5074a1dc3 100644 --- a/app/views/notify/merge_request_status_email.text.haml +++ b/app/views/notify/merge_request_status_email.text.haml @@ -1,4 +1,4 @@ -= "Merge Request ##{@merge_request.iid} was #{@mr_status} by #{@updated_by.name}" += "Merge Request #{@merge_request.to_reference} was #{@mr_status} by #{@updated_by.name}" Merge Request url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)} diff --git a/app/views/notify/merged_merge_request_email.html.haml b/app/views/notify/merged_merge_request_email.html.haml index 6762fae7f64..fbe506d4f4d 100644 --- a/app/views/notify/merged_merge_request_email.html.haml +++ b/app/views/notify/merged_merge_request_email.html.haml @@ -1,2 +1,2 @@ %p - = "Merge Request ##{@merge_request.iid} was merged" + = "Merge Request #{@merge_request.to_reference} was merged" diff --git a/app/views/notify/merged_merge_request_email.text.haml b/app/views/notify/merged_merge_request_email.text.haml index 34dbc60e19b..bfbae01094f 100644 --- a/app/views/notify/merged_merge_request_email.text.haml +++ b/app/views/notify/merged_merge_request_email.text.haml @@ -1,4 +1,4 @@ -= "Merge Request ##{@merge_request.iid} was merged" += "Merge Request #{@merge_request.to_reference} was merged" Merge Request url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)} diff --git a/app/views/notify/new_merge_request_email.text.erb b/app/views/notify/new_merge_request_email.text.erb index bdcca6e4ab7..d4aad8d1862 100644 --- a/app/views/notify/new_merge_request_email.text.erb +++ b/app/views/notify/new_merge_request_email.text.erb @@ -1,4 +1,4 @@ -New Merge Request #<%= @merge_request.iid %> +New Merge Request <%= @merge_request.to_reference %> <%= url_for(namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)) %> diff --git a/app/views/notify/note_merge_request_email.text.erb b/app/views/notify/note_merge_request_email.text.erb index 1d1411992a6..8cdab63829e 100644 --- a/app/views/notify/note_merge_request_email.text.erb +++ b/app/views/notify/note_merge_request_email.text.erb @@ -1,4 +1,4 @@ -New comment for Merge Request <%= @merge_request.iid %> +New comment for Merge Request <%= @merge_request.to_reference %> <%= url_for(namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request, anchor: "note_#{@note.id}")) %> diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml index 6efd119f260..afd3d79321f 100644 --- a/app/views/profiles/accounts/show.html.haml +++ b/app/views/profiles/accounts/show.html.haml @@ -1,5 +1,4 @@ - page_title "Account" -- header_title page_title, profile_account_path - if current_user.ldap_user? .alert.alert-info diff --git a/app/views/profiles/audit_log.html.haml b/app/views/profiles/audit_log.html.haml index f630c03e5f6..9c404b6935f 100644 --- a/app/views/profiles/audit_log.html.haml +++ b/app/views/profiles/audit_log.html.haml @@ -1,5 +1,4 @@ - page_title "Audit Log" -- header_title page_title, audit_log_profile_path .row.prepend-top-default .col-lg-3.profile-settings-sidebar diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml index 3f328f96cea..57527361eb6 100644 --- a/app/views/profiles/emails/index.html.haml +++ b/app/views/profiles/emails/index.html.haml @@ -1,5 +1,4 @@ - page_title "Emails" -- header_title page_title, profile_emails_path .row.prepend-top-default .col-lg-3.profile-settings-sidebar diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml index e0f8c9a5733..6a067a03535 100644 --- a/app/views/profiles/keys/index.html.haml +++ b/app/views/profiles/keys/index.html.haml @@ -1,5 +1,4 @@ - page_title "SSH Keys" -- header_title page_title, profile_keys_path .row.prepend-top-default .col-lg-3.profile-settings-sidebar diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml index a2a505c082b..7696f112bb3 100644 --- a/app/views/profiles/notifications/show.html.haml +++ b/app/views/profiles/notifications/show.html.haml @@ -1,5 +1,4 @@ - page_title "Notifications" -- header_title page_title, profile_notifications_path %div - if @user.errors.any? diff --git a/app/views/profiles/passwords/edit.html.haml b/app/views/profiles/passwords/edit.html.haml index 5ac8a8b9d09..243428b690e 100644 --- a/app/views/profiles/passwords/edit.html.haml +++ b/app/views/profiles/passwords/edit.html.haml @@ -1,5 +1,4 @@ - page_title "Password" -- header_title page_title, edit_profile_password_path .row.prepend-top-default .col-lg-3.profile-settings-sidebar diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml index f80211669fb..bfe53be6854 100644 --- a/app/views/profiles/preferences/show.html.haml +++ b/app/views/profiles/preferences/show.html.haml @@ -1,5 +1,4 @@ - page_title 'Preferences' -- header_title page_title, profile_preferences_path = form_for @user, url: profile_preferences_path, remote: true, method: :put, html: {class: 'row prepend-top-default js-preferences-form'} do |f| .col-lg-3.profile-settings-sidebar diff --git a/app/views/projects/_activity.html.haml b/app/views/projects/_activity.html.haml index 961b61d2e76..48b0dd6b121 100644 --- a/app/views/projects/_activity.html.haml +++ b/app/views/projects/_activity.html.haml @@ -9,4 +9,7 @@ = spinner :javascript - new Activities(); + var activity = new Activities(); + $(document).on('page:restore', function (event) { + activity.reloadActivities() + }) diff --git a/app/views/projects/_builds_settings.html.haml b/app/views/projects/_builds_settings.html.haml index b6074373e2b..0de019983ca 100644 --- a/app/views/projects/_builds_settings.html.haml +++ b/app/views/projects/_builds_settings.html.haml @@ -55,6 +55,9 @@ %li gcovr (C/C++) - %code ^TOTAL.*\s+(\d+\%)$ + %li + tap --coverage-report=text-summary (Node.js) - + %code ^Statements\s*:\s*([^%]+) .form-group .col-sm-offset-2.col-sm-10 diff --git a/app/views/projects/_last_commit.html.haml b/app/views/projects/_last_commit.html.haml index 386d72e7787..66c30283c7a 100644 --- a/app/views/projects/_last_commit.html.haml +++ b/app/views/projects/_last_commit.html.haml @@ -1,9 +1,8 @@ .project-last-commit - - ci_commit = project.ci_commit(commit.sha) - - if ci_commit - = link_to ci_status_path(ci_commit), class: "ci-status ci-#{ci_commit.status}" do - = ci_status_icon(ci_commit) - = ci_status_label(ci_commit) + - if commit.status + = link_to builds_namespace_project_commit_path(commit.project.namespace, commit.project, commit), class: "ci-status ci-#{commit.status}" do + = ci_icon_for_status(commit.status) + = ci_label_for_status(commit.status) = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id" = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit), class: "commit-row-message" diff --git a/app/views/projects/_readme.html.haml b/app/views/projects/_readme.html.haml index d1191928d4f..a9908eaecca 100644 --- a/app/views/projects/_readme.html.haml +++ b/app/views/projects/_readme.html.haml @@ -9,7 +9,7 @@ - else .gray-content-block.second-block.center %h3.page-title - This project does not have README yet + This project does not have a README yet - if can?(current_user, :push_code, @project) %p A @@ -18,5 +18,5 @@ distributed with computer software, forming part of its documentation. %p We recommend you to - = link_to "add README", new_readme_path, class: 'underlined-link' + = link_to "add a README", new_readme_path, class: 'underlined-link' file to the repository and GitLab will render it here instead of this message. diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml index f8b6fa253c4..fefa652a3da 100644 --- a/app/views/projects/blob/_editor.html.haml +++ b/app/views/projects/blob/_editor.html.haml @@ -13,7 +13,11 @@ required: true, class: 'form-control new-file-name' .pull-right - = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2' + .license-selector.js-license-selector.hide + = select_tag :license_type, grouped_options_for_select(licenses_for_select, @project.repository.license_key), include_blank: true, class: 'select2 license-select', data: {placeholder: 'Choose a license template', project: @project.name, fullname: @project.namespace.human_name} + + .encoding-selector + = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2' .file-content.code %pre.js-edit-mode-pane#editor #{params[:content] || local_assigns[:blob_data]} diff --git a/app/views/projects/blob/diff.html.haml b/app/views/projects/blob/diff.html.haml index 38e62c81fed..5926d181ba3 100644 --- a/app/views/projects/blob/diff.html.haml +++ b/app/views/projects/blob/diff.html.haml @@ -1,17 +1,17 @@ - if @lines.present? - if @form.unfold? && @form.since != 1 && !@form.bottom? - %tr.line_holder{ id: @form.since } + %tr.line_holder = 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 + %tr.line_holder{ id: line_old } %td.old_line.diff-line-num{ data: { linenumber: line_old } } - = link_to raw(line_old), "#" + = link_to raw(line_old), "##{line_old}" %td.new_line.diff-line-num{ data: { linenumber: line_old } } - = link_to raw(line_new) , "#" + = link_to raw(line_new) , "##{line_old}" %td.line_content.noteable_line==#{' ' * @form.indent}#{line} - if @form.unfold? && @form.bottom? && @form.to < @blob.loc diff --git a/app/views/projects/blob/new.html.haml b/app/views/projects/blob/new.html.haml index 1dd2b5c0af7..0459699432e 100644 --- a/app/views/projects/blob/new.html.haml +++ b/app/views/projects/blob/new.html.haml @@ -14,5 +14,5 @@ cancel_path: namespace_project_tree_path(@project.namespace, @project, @id) :javascript - blob = new NewBlob(gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}", null) + blob = new EditBlob(gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}") new NewCommitForm($('.js-new-blob-form')) diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml index aa85f495e39..0406fc21d77 100644 --- a/app/views/projects/builds/index.html.haml +++ b/app/views/projects/builds/index.html.haml @@ -58,6 +58,6 @@ %th Coverage %th - = render @builds, commit_sha: true, stage: true, allow_retry: true, coverage: @project.build_coverage_enabled? + = render @builds, commit_sha: true, ref: true, stage: true, allow_retry: true, coverage: @project.build_coverage_enabled? = paginate @builds, theme: 'gitlab' diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index b02aee3db21..99d72aa7935 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -10,10 +10,10 @@ - merge_request = @build.merge_request - if merge_request via - = link_to "merge request ##{merge_request.iid}", merge_request_path(merge_request) + = link_to "merge request #{merge_request.to_reference}", merge_request_path(merge_request) #up-build-trace - - builds = @build.commit.matrix_builds(@build) + - builds = @build.commit.builds.latest.to_a - if builds.size > 1 %ul.nav-links.no-top.no-bottom - builds.each do |build| @@ -110,7 +110,7 @@ = icon('folder-open') Browse - .build-widget + .build-widget.build-controls %h4.title Build ##{@build.id} - if can?(current_user, :update_build, @project) @@ -127,6 +127,9 @@ data: { confirm: 'Are you sure you want to erase this build?' } do = icon('eraser') Erase + - if @build.has_trace? + = link_to 'Raw', raw_namespace_project_build_path(@project.namespace, @project, @build), + class: 'btn btn-sm btn-success', target: '_blank' .clearfix - if @build.duration diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index 2cf9115e4dd..e123eb1cc7a 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -19,11 +19,12 @@ %td = link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "monospace" - %td - - if build.ref - = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref) - - else - .light none + - if defined?(ref) && ref + %td + - if build.ref + = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref) + - else + .light none - if defined?(runner) && runner %td @@ -48,6 +49,8 @@ %span.label.label-info triggered - if build.try(:allow_failure) %span.label.label-danger allowed to fail + - if defined?(retried) && retried + %span.label.label-warning retried %td.duration - if build.duration diff --git a/app/views/projects/commit/_builds.html.haml b/app/views/projects/commit/_builds.html.haml index 003b7c18d0e..5c9a319edeb 100644 --- a/app/views/projects/commit/_builds.html.haml +++ b/app/views/projects/commit/_builds.html.haml @@ -1,67 +1,2 @@ -.gray-content-block.middle-block - .pull-right - - if can?(current_user, :update_build, @ci_commit.project) - - if @ci_commit.builds.latest.failed.any?(&:retryable?) - = link_to "Retry failed", retry_builds_namespace_project_commit_path(@ci_commit.project.namespace, @ci_commit.project, @ci_commit.sha), class: 'btn btn-grouped btn-primary', method: :post - - - if @ci_commit.builds.running_or_pending.any? - = link_to "Cancel running", cancel_builds_namespace_project_commit_path(@ci_commit.project.namespace, @ci_commit.project, @ci_commit.sha), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post - - .oneline - = pluralize @statuses.count(:id), "build" - - if defined?(link_to_commit) && link_to_commit - for commit - = link_to @ci_commit.short_sha, namespace_project_commit_path(@ci_commit.project.namespace, @ci_commit.project, @ci_commit.sha), class: "monospace" - - if @ci_commit.duration > 0 - in - = time_interval_in_words @ci_commit.duration - -- if @ci_commit.yaml_errors.present? - .bs-callout.bs-callout-danger - %h4 Found errors in your .gitlab-ci.yml: - %ul - - @ci_commit.yaml_errors.split(",").each do |error| - %li= error - You can also test your .gitlab-ci.yml in the #{link_to "Lint", ci_lint_path} - -- if @ci_commit.project.builds_enabled? && !@ci_commit.ci_yaml_file - .bs-callout.bs-callout-warning - \.gitlab-ci.yml not found in this commit - -.table-holder - %table.table.builds - %thead - %tr - %th Status - %th Build ID - %th Ref - %th Stage - %th Name - %th Duration - %th Finished at - - if @ci_commit.project.build_coverage_enabled? - %th Coverage - %th - - @ci_commit.refs.each do |ref| - - builds = @ci_commit.statuses.for_ref(ref).latest.ordered - = render builds, coverage: @ci_commit.project.build_coverage_enabled?, stage: true, allow_retry: true - -- if @ci_commit.retried.any? - .gray-content-block.second-block - Retried builds - - .table-holder - %table.table.builds - %thead - %tr - %th Status - %th Build ID - %th Ref - %th Stage - %th Name - %th Duration - %th Finished at - - if @ci_commit.project.build_coverage_enabled? - %th Coverage - %th - = render @ci_commit.retried, coverage: @ci_commit.project.build_coverage_enabled?, stage: true +- @ci_commits.each do |ci_commit| + = render "ci_commit", ci_commit: ci_commit diff --git a/app/views/projects/commit/_revert.html.haml b/app/views/projects/commit/_change.html.haml index 52ca3ed5b14..44ef1fdbbe3 100644 --- a/app/views/projects/commit/_revert.html.haml +++ b/app/views/projects/commit/_change.html.haml @@ -1,13 +1,21 @@ -#modal-revert-commit.modal +- case type.to_s +- when 'revert' + - label = 'Revert' + - target_label = 'Revert in branch' +- when 'cherry-pick' + - label = 'Cherry-pick' + - target_label = 'Pick into branch' + +.modal{id: "modal-#{type}-commit"} .modal-dialog .modal-content .modal-header %a.close{href: "#", "data-dismiss" => "modal"} × - %h3.page-title== Revert this #{revert_commit_type(commit)} + %h3.page-title== #{label} this #{commit.change_type_title} .modal-body - = form_tag revert_namespace_project_commit_path(@project.namespace, @project, commit.id), method: :post, remote: false, class: 'form-horizontal js-create-dir-form js-requires-input' do + = form_tag send("#{type.underscore}_namespace_project_commit_path", @project.namespace, @project, commit.id), method: :post, remote: false, class: 'form-horizontal js-#{type}-form js-requires-input' do .form-group.branch - = label_tag 'target_branch', 'Revert in branch', class: 'control-label' + = label_tag 'target_branch', target_label, class: 'control-label' .col-sm-10 = select_tag "target_branch", grouped_options_refs, class: "select2 select2-sm js-target-branch" - if can?(current_user, :push_code, @project) @@ -20,7 +28,7 @@ - else = hidden_field_tag 'create_merge_request', 1 .form-actions - = submit_tag "Revert", class: 'btn btn-create' + = submit_tag label, class: 'btn btn-create' = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal" - unless can?(current_user, :push_code, @project) @@ -28,4 +36,4 @@ = commit_in_fork_help :javascript - new NewCommitForm($('.js-create-dir-form')) + new NewCommitForm($('.js-#{type}-form')) diff --git a/app/views/projects/commit/_ci_commit.html.haml b/app/views/projects/commit/_ci_commit.html.haml new file mode 100644 index 00000000000..25714e6cb47 --- /dev/null +++ b/app/views/projects/commit/_ci_commit.html.haml @@ -0,0 +1,69 @@ +.gray-content-block.middle-block + .pull-right + - if can?(current_user, :update_build, @project) + - if ci_commit.builds.latest.failed.any?(&:retryable?) + = link_to "Retry failed", retry_builds_namespace_project_commit_path(@project.namespace, @project, ci_commit.sha), class: 'btn btn-grouped btn-primary', method: :post + + - if ci_commit.builds.running_or_pending.any? + = link_to "Cancel running", cancel_builds_namespace_project_commit_path(@project.namespace, @project, ci_commit.sha), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post + + .oneline + = pluralize ci_commit.statuses.count(:id), "build" + - if ci_commit.ref + for + %span.label.label-info + = ci_commit.ref + - if defined?(link_to_commit) && link_to_commit + for commit + = link_to ci_commit.short_sha, namespace_project_commit_path(@project.namespace, @project, ci_commit.sha), class: "monospace" + - if ci_commit.duration > 0 + in + = time_interval_in_words ci_commit.duration + +- if ci_commit.yaml_errors.present? + .bs-callout.bs-callout-danger + %h4 Found errors in your .gitlab-ci.yml: + %ul + - ci_commit.yaml_errors.split(",").each do |error| + %li= error + You can also test your .gitlab-ci.yml in the #{link_to "Lint", ci_lint_path} + +- if @project.builds_enabled? && !ci_commit.ci_yaml_file + .bs-callout.bs-callout-warning + \.gitlab-ci.yml not found in this commit + +.table-holder + %table.table.builds + %thead + %tr + %th Status + %th Build ID + %th Stage + %th Name + %th Duration + %th Finished at + - if @project.build_coverage_enabled? + %th Coverage + %th + - builds = ci_commit.statuses.latest.ordered + = render builds, coverage: @project.build_coverage_enabled?, stage: true, ref: false, allow_retry: true + +- if ci_commit.retried.any? + .gray-content-block.second-block + Retried builds + + .table-holder + %table.table.builds + %thead + %tr + %th Status + %th Build ID + %th Ref + %th Stage + %th Name + %th Duration + %th Finished at + - if @project.build_coverage_enabled? + %th Coverage + %th + = render ci_commit.retried, coverage: @project.build_coverage_enabled?, stage: true, ref: false diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml index 71995fcc487..3d7c18a5f58 100644 --- a/app/views/projects/commit/_commit_box.html.haml +++ b/app/views/projects/commit/_commit_box.html.haml @@ -18,6 +18,7 @@ Browse Files - unless @commit.has_been_reverted?(current_user) = revert_commit_link(@commit, namespace_project_commit_path(@project.namespace, @project, @commit.id)) + = cherry_pick_commit_link(@commit, namespace_project_commit_path(@project.namespace, @project, @commit.id)) %div %p @@ -42,12 +43,12 @@ - @commit.parents.each do |parent| = link_to parent.short_id, namespace_project_commit_path(@project.namespace, @project, parent), class: "monospace" -- if @ci_commit +- if @commit.status .pull-right - = link_to ci_status_path(@ci_commit), class: "ci-status ci-#{@ci_commit.status}" do - = ci_status_icon(@ci_commit) + = link_to builds_namespace_project_commit_path(@project.namespace, @project, @commit.id), class: "ci-status ci-#{@commit.status}" do + = ci_icon_for_status(@commit.status) build: - = ci_status_label(@ci_commit) + = ci_label_for_status(@commit.status) .commit-info-row.branches %i.fa.fa-spinner.fa-spin diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml index 21e186120c3..e5e3d696035 100644 --- a/app/views/projects/commit/show.html.haml +++ b/app/views/projects/commit/show.html.haml @@ -5,7 +5,7 @@ .prepend-top-default = render "commit_box" -- if @ci_commit +- if @commit.status = render "ci_menu" - else %div.block-connector @@ -13,4 +13,5 @@ diff_refs: @diff_refs = render "projects/notes/notes_with_form" - if can_collaborate_with_project? - = render "projects/commit/revert", commit: @commit, title: @commit.title + - %w(revert cherry-pick).each do |type| + = render "projects/commit/change", type: type, commit: @commit, title: @commit.title diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index 34f27f1e793..c7d8c9a0d15 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -4,9 +4,8 @@ - notes = commit.notes - note_count = notes.user.count -- ci_commit = project.ci_commit(commit.sha) - cache_key = [project.path_with_namespace, commit.id, current_application_settings, note_count] -- cache_key.push(ci_commit.status) if ci_commit +- cache_key.push(commit.status) if commit.status = cache(cache_key) do %li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" } @@ -17,8 +16,8 @@ %a.text-expander.js-toggle-button ... .pull-right - - if ci_commit - = render_ci_status(ci_commit) + - if commit.status + = render_ci_status(commit) = clipboard_button(clipboard_text: commit.id) = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id" @@ -31,5 +30,5 @@ by = commit_author_link(commit, avatar: true, size: 24) .committed_ago - #{time_ago_with_tooltip(commit.committed_date, skip_js: true)} + #{time_ago_with_tooltip(commit.committed_date)} = link_to_browse_code(project, commit) diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml index c52cf25d40a..bcdb09208aa 100644 --- a/app/views/projects/commits/show.html.haml +++ b/app/views/projects/commits/show.html.haml @@ -39,4 +39,4 @@ = spinner :javascript - CommitsList.init("#{@ref}", #{@limit}); + CommitsList.init(#{@limit}); diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml index 6ad7b05155a..52d093871b4 100644 --- a/app/views/projects/empty.html.haml +++ b/app/views/projects/empty.html.haml @@ -14,8 +14,10 @@ %p If you already have files you can push them using command line instructions below. %p - Otherwise you can start with - = link_to "adding README", new_readme_path, class: 'underlined-link' + Otherwise you can start with adding a + = link_to "README", new_readme_path, class: 'underlined-link' + or a + = link_to "LICENSE", add_special_file_path(@project, file_name: 'LICENSE'), class: 'underlined-link' file to this project. - if can?(current_user, :push_code, @project) diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml index c15386b4883..f21c864e35c 100644 --- a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml +++ b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml @@ -15,12 +15,13 @@ - if defined?(commit_sha) && commit_sha %td = link_to generic_commit_status.short_sha, namespace_project_commit_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.sha), class: "monospace" - - %td - - if generic_commit_status.ref - = link_to generic_commit_status.ref, namespace_project_commits_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.ref) - - else - .light none + + - if defined?(ref) && ref + %td + - if generic_commit_status.ref + = link_to generic_commit_status.ref, namespace_project_commits_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.ref) + - else + .light none - if defined?(runner) && runner %td diff --git a/app/views/projects/hooks/index.html.haml b/app/views/projects/hooks/index.html.haml index e39224d86c6..aae3abcad4b 100644 --- a/app/views/projects/hooks/index.html.haml +++ b/app/views/projects/hooks/index.html.haml @@ -74,16 +74,15 @@ .panel.panel-default .panel-heading Webhooks (#{@hooks.count}) - %ul.well-list + %ul.content-list - @hooks.each do |hook| %li - .pull-right + .controls = link_to 'Test Hook', test_namespace_project_hook_path(@project.namespace, @project, hook), class: "btn btn-sm btn-grouped" = link_to 'Remove', namespace_project_hook_path(@project.namespace, @project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-sm btn-grouped" - .clearfix - %span.monospace= hook.url - %p + .monospace= hook.url + %div - %w(push_events tag_push_events issues_events note_events merge_requests_events build_events).each do |trigger| - if hook.send(trigger) %span.label.label-gray= trigger.titleize - SSL Verification: #{hook.enable_ssl_verification ? "enabled" : "disabled"} + %span.label.label-gray SSL Verification: #{hook.enable_ssl_verification ? "enabled" : "disabled"} diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index 7a8009f6da4..9ad86ed71c9 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -48,6 +48,11 @@ = link_to namespace_project_issues_path(issue.project.namespace, issue.project, milestone_title: issue.milestone.title) do = icon('clock-o') = issue.milestone.title + - if issue.due_date + %span{class: "#{'cred' if issue.overdue?}"} + + = icon('calendar') + = issue.due_date.to_s(:medium) - if issue.labels.any? - issue.labels.each do |label| diff --git a/app/views/projects/issues/_related_branches.html.haml b/app/views/projects/issues/_related_branches.html.haml index b10cd03515f..bdfa0c7009e 100644 --- a/app/views/projects/issues/_related_branches.html.haml +++ b/app/views/projects/issues/_related_branches.html.haml @@ -5,7 +5,7 @@ - @related_branches.each do |branch| %li - sha = @project.repository.find_branch(branch).target - - ci_commit = @project.ci_commit(sha) if sha + - ci_commit = @project.ci_commit(sha, branch) if sha - if ci_commit %span.related-branch-ci-status = render_ci_status(ci_commit) diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 7125d7d9d1c..8d05060f563 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -86,8 +86,10 @@ = spinner = render 'shared/issuable/sidebar', issuable: @merge_request -- if @merge_request.can_be_reverted? - = render "projects/commit/revert", commit: @merge_request.merge_commit, title: @merge_request.title +- if @merge_request.can_be_reverted?(current_user) + = render "projects/commit/change", type: 'revert', commit: @merge_request.merge_commit, title: @merge_request.title +- if @merge_request.can_be_cherry_picked? + = render "projects/commit/change", type: 'cherry-pick', commit: @merge_request.merge_commit, title: @merge_request.title :javascript var merge_request; diff --git a/app/views/projects/merge_requests/edit.html.haml b/app/views/projects/merge_requests/edit.html.haml index fc62bb5bce9..b31ea5e5321 100644 --- a/app/views/projects/merge_requests/edit.html.haml +++ b/app/views/projects/merge_requests/edit.html.haml @@ -1,7 +1,7 @@ -- page_title "Edit", "#{@merge_request.title} (##{@merge_request.iid})", "Merge Requests" +- page_title "Edit", "#{@merge_request.title} (#{@merge_request.to_reference}", "Merge Requests" = render "header_title" %h3.page-title - Edit Merge Request ##{@merge_request.iid} + Edit Merge Request #{@merge_request.to_reference} %hr = render 'form' diff --git a/app/views/projects/merge_requests/invalid.html.haml b/app/views/projects/merge_requests/invalid.html.haml index fc03ee73a3d..f5bf16ef3ad 100644 --- a/app/views/projects/merge_requests/invalid.html.haml +++ b/app/views/projects/merge_requests/invalid.html.haml @@ -1,4 +1,4 @@ -- page_title "#{@merge_request.title} (##{@merge_request.iid})", "Merge Requests" +- page_title "#{@merge_request.title} (#{@merge_request.to_reference}", "Merge Requests" = render "header_title" .merge-request diff --git a/app/views/projects/merge_requests/show/_builds.html.haml b/app/views/projects/merge_requests/show/_builds.html.haml index 307a75d02ca..a116ffe2e15 100644 --- a/app/views/projects/merge_requests/show/_builds.html.haml +++ b/app/views/projects/merge_requests/show/_builds.html.haml @@ -1 +1,2 @@ -= render "projects/commit/builds", link_to_commit: true += render "projects/commit/ci_commit", ci_commit: @ci_commit, link_to_commit: true + 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 0a99e8c9591..36c275e8be1 100644 --- a/app/views/projects/merge_requests/show/_mr_title.html.haml +++ b/app/views/projects/merge_requests/show/_mr_title.html.haml @@ -24,9 +24,9 @@ %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 'Edit', edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'issuable-edit' = 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 + = 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" do = icon('pencil-square-o') Edit diff --git a/app/views/projects/merge_requests/widget/_merged.html.haml b/app/views/projects/merge_requests/widget/_merged.html.haml index 3abae9f0bf6..ec4beae9727 100644 --- a/app/views/projects/merge_requests/widget/_merged.html.haml +++ b/app/views/projects/merge_requests/widget/_merged.html.haml @@ -44,3 +44,8 @@ $('.remove_source_branch_in_progress').hide(); $('.remove_source_branch_widget.failed').show(); }); + - else + %p + The changes were merged into + #{link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch"}. + = render 'projects/merge_requests/widget/merged_buttons' diff --git a/app/views/projects/merge_requests/widget/_merged_buttons.haml b/app/views/projects/merge_requests/widget/_merged_buttons.haml index 85a3a6ba9e2..56167509af9 100644 --- a/app/views/projects/merge_requests/widget/_merged_buttons.haml +++ b/app/views/projects/merge_requests/widget/_merged_buttons.haml @@ -1,11 +1,14 @@ -- source_branch_exists = local_assigns.fetch(:source_branch_exists, false) -- mr_can_be_reverted = @merge_request.can_be_reverted? +- can_remove_source_branch = local_assigns.fetch(:source_branch_exists, false) && @merge_request.can_remove_source_branch?(current_user) +- mr_can_be_reverted = @merge_request.can_be_reverted?(current_user) +- mr_can_be_cherry_picked = @merge_request.can_be_cherry_picked? -- if source_branch_exists || mr_can_be_reverted +- if can_remove_source_branch || mr_can_be_reverted || mr_can_be_cherry_picked .btn-group - - if source_branch_exists + - if can_remove_source_branch = link_to namespace_project_branch_path(@merge_request.source_project.namespace, @merge_request.source_project, @merge_request.source_branch), remote: true, method: :delete, class: "btn btn-default btn-grouped btn-sm remove_source_branch" do = icon('trash-o') Remove Source Branch - if mr_can_be_reverted = revert_commit_link(@merge_request.merge_commit, namespace_project_merge_request_path(@project.namespace, @project, @merge_request), btn_class: 'sm') + - if mr_can_be_cherry_picked + = cherry_pick_commit_link(@merge_request.merge_commit, namespace_project_merge_request_path(@project.namespace, @project, @merge_request), btn_class: 'sm') diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index 6e9ecdf7649..aeb7c1d5ee4 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -8,7 +8,9 @@ .note-header = link_to_member(note.project, note.author, avatar: false) .inline.note-headline-light - = "#{note.author.to_reference} commented" + = "#{note.author.to_reference}" + - if !note.system + commented %a{ href: "##{dom_id(note)}" } = time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note-created-ago') .note-actions diff --git a/app/views/projects/runners/_shared_runners.html.haml b/app/views/projects/runners/_shared_runners.html.haml index 6a37f444bb7..9fa4127c948 100644 --- a/app/views/projects/runners/_shared_runners.html.haml +++ b/app/views/projects/runners/_shared_runners.html.haml @@ -1,7 +1,10 @@ %h3 Shared runners -.bs-callout.bs-callout-warning - GitLab Runners do not offer secure isolation between projects that they do builds for. You are TRUSTING all GitLab users who can push code to project A, B or C to run shell scripts on the machine hosting runner X. +.bs-callout.bs-callout-warning.shared-runners-description + - if shared_runners_text.present? + = markdown(shared_runners_text, pipeline: 'plain_markdown') + - else + Shared runners execute code of different projects on the same Runner unless you configure GitLab Runner Autoscale with MaxBuilds 1 (which it is on GitLab.com). %hr - if @project.shared_runners_enabled? = link_to toggle_shared_runners_namespace_project_runners_path(@project.namespace, @project), class: 'btn btn-warning', method: :post do diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index 4310f038fc9..d854ac21725 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -36,9 +36,9 @@ %li = link_to 'Changelog', changelog_path(@project) - - if @repository.license + - if @repository.license_blob %li - = link_to 'License', license_path(@project) + = link_to license_short_name(@project), license_path(@project) - if @repository.contribution_guide %li @@ -47,15 +47,15 @@ - if current_user && can_push_branch?(@project, @project.default_branch) - unless @repository.changelog %li.missing - = link_to add_changelog_path(@project) do + = link_to add_special_file_path(@project, file_name: 'CHANGELOG') do Add Changelog - - unless @repository.license + - unless @repository.license_blob %li.missing - = link_to add_license_path(@project) do + = link_to add_special_file_path(@project, file_name: 'LICENSE') do Add License - unless @repository.contribution_guide %li.missing - = link_to add_contribution_guide_path(@project) do + = link_to add_special_file_path(@project, file_name: 'CONTRIBUTING.md', commit_message: 'Add contribution guide') do Add Contribution guide - if @repository.commit diff --git a/app/views/repository_check_mailer/notify.html.haml b/app/views/repository_check_mailer/notify.html.haml index df16f503570..a585147ddd1 100644 --- a/app/views/repository_check_mailer/notify.html.haml +++ b/app/views/repository_check_mailer/notify.html.haml @@ -3,3 +3,6 @@ %p = link_to "See the affected projects in the GitLab admin panel", admin_namespaces_projects_url(last_repository_check_failed: 1) + +%p + You are receiving this message because you are a GitLab administrator for #{Gitlab.config.gitlab.url}. diff --git a/app/views/repository_check_mailer/notify.text.haml b/app/views/repository_check_mailer/notify.text.haml index 02f3f80288a..93db151329e 100644 --- a/app/views/repository_check_mailer/notify.text.haml +++ b/app/views/repository_check_mailer/notify.text.haml @@ -1,3 +1,6 @@ #{@message}. \ View details: #{admin_namespaces_projects_url(last_repository_check_failed: 1)} + +You are receiving this message because you are a GitLab administrator +for #{Gitlab.config.gitlab.url}. diff --git a/app/views/search/results/_merge_request.html.haml b/app/views/search/results/_merge_request.html.haml index faeb2b55c6f..333f6533213 100644 --- a/app/views/search/results/_merge_request.html.haml +++ b/app/views/search/results/_merge_request.html.haml @@ -2,7 +2,7 @@ %h4 = link_to [merge_request.target_project.namespace.becomes(Namespace), merge_request.target_project, merge_request] do %span.term.str-truncated= merge_request.title - .pull-right ##{merge_request.iid} + .pull-right #{merge_request.to_reference} - if merge_request.description.present? .description.term = preserve do diff --git a/app/views/shared/_label_row.html.haml b/app/views/shared/_label_row.html.haml index b38c5e18efb..9ce5562e667 100644 --- a/app/views/shared/_label_row.html.haml +++ b/app/views/shared/_label_row.html.haml @@ -2,4 +2,4 @@ %span.label-name = link_to_label(label, tooltip: false) %span.prepend-left-10 - = markdown(label.description, pipeline: :single_line) + = markdown(label.description, pipeline: :single_line)
\ No newline at end of file diff --git a/app/views/shared/_labels_row.html.haml b/app/views/shared/_labels_row.html.haml new file mode 100644 index 00000000000..dc89e36419c --- /dev/null +++ b/app/views/shared/_labels_row.html.haml @@ -0,0 +1,3 @@ +- labels.each do |label| + %span.label-row + = link_to_label(label, tooltip: false) diff --git a/app/views/shared/_service_settings.html.haml b/app/views/shared/_service_settings.html.haml index fc935166bf6..4eaf7c2a025 100644 --- a/app/views/shared/_service_settings.html.haml +++ b/app/views/shared/_service_settings.html.haml @@ -62,6 +62,14 @@ %strong Build events %p.light This url will be triggered when a build status changes + - if @service.supported_events.include?("wiki_page") + %div + = form.check_box :wiki_page_events, class: 'pull-left' + .prepend-left-20 + = form.label :wiki_page_events, class: 'list-label' do + %strong Wiki Page events + %p.light + This url will be triggered when a wiki page is created/updated - @service.fields.each do |field| diff --git a/app/views/shared/_sort_dropdown.html.haml b/app/views/shared/_sort_dropdown.html.haml index e3a6a5a68b6..d327bd0a96f 100644 --- a/app/views/shared/_sort_dropdown.html.haml +++ b/app/views/shared/_sort_dropdown.html.haml @@ -20,6 +20,11 @@ = sort_title_milestone_soon = link_to page_filter_path(sort: sort_value_milestone_later) do = sort_title_milestone_later + - if controller.controller_name == 'issues' || controller.action_name == 'issues' + = link_to page_filter_path(sort: sort_value_due_date_soon) do + = sort_title_due_date_soon + = link_to page_filter_path(sort: sort_value_due_date_later) do + = sort_title_due_date_later = link_to page_filter_path(sort: sort_value_upvotes) do = sort_title_upvotes = link_to page_filter_path(sort: sort_value_downvotes) do diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index 921eaefd79a..fc1101646fb 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -23,6 +23,7 @@ .filter-item.inline.labels-filter = render "shared/issuable/label_dropdown" + .pull-right = render 'shared/sort_dropdown' @@ -46,9 +47,10 @@ .filter-item.inline = button_tag "Update issues", class: "btn update_selected_issues btn-save" -- if @label - .gray-content-block.second-block - = render "shared/label_row", label: @label + - if !@labels.nil? + .gray-content-block.second-block.filtered-labels{ class: ("hidden" if !@labels.any?) } + - if @labels.any? + = render "shared/labels_row", labels: @labels :javascript new UsersSelect(); diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index bae15b7f844..18b091df39b 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -75,8 +75,9 @@ = f.label :label_ids, "Labels", class: 'control-label' .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" } + .issuable-form-select-holder + = f.collection_select :label_ids, issuable.project.labels.all, :id, :name, + { selected: issuable.label_ids }, multiple: true, class: 'select2', data: { placeholder: "Select labels" } - else %span.light No labels yet. @@ -88,9 +89,10 @@ .form-group = label_tag :move_to_project_id, 'Move', class: 'control-label' .col-sm-10 - - projects = project_options(issuable, current_user, ability: :admin_issue) - = select_tag(:move_to_project_id, projects, include_blank: true, - class: 'select2', data: { placeholder: 'Select project' }) + .issuable-form-select-holder + - projects = project_options(issuable, current_user, ability: :admin_issue) + = select_tag(:move_to_project_id, projects, include_blank: true, + class: 'select2', data: { placeholder: 'Select project' }) %span{ data: { toggle: 'tooltip', placement: 'auto top' }, style: 'cursor: default', title: 'Moving an issue will copy the discussion to a different project and close it here. All participants will be notified of the new location.' } @@ -102,13 +104,15 @@ .form-group = f.label :source_branch, class: 'control-label' .col-sm-10 - = f.select(:source_branch, [@merge_request.source_branch], { }, { class: 'source_branch select2 span2', disabled: true }) + .issuable-form-select-holder + = f.select(:source_branch, [@merge_request.source_branch], { }, { class: 'source_branch select2 span2', disabled: true }) .form-group = f.label :target_branch, class: 'control-label' .col-sm-10 - = f.select(:target_branch, @merge_request.target_branches, { include_blank: true }, { class: 'target_branch select2 span2', disabled: @merge_request.new_record?, data: {placeholder: "Select branch"} }) + .issuable-form-select-holder + = f.select(:target_branch, @merge_request.target_branches, { include_blank: true }, { class: 'target_branch select2 span2', disabled: @merge_request.new_record?, data: {placeholder: "Select branch"} }) - if @merge_request.new_record? - %p.help-block + = link_to 'Change branches', mr_change_branches_path(@merge_request) - is_footer = !(issuable.is_a?(MergeRequest) && issuable.new_record?) diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml index f722e61eeac..61fd1e9c335 100644 --- a/app/views/shared/issuable/_label_dropdown.html.haml +++ b/app/views/shared/issuable/_label_dropdown.html.haml @@ -1,44 +1,14 @@ - if params[:label_name].present? - = hidden_field_tag(:label_name, params[:label_name]) + - if params[:label_name].respond_to?('any?') + - params[:label_name].each do |label| + = hidden_field_tag "label_name[]", label, id: nil .dropdown - %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"}} + %button.dropdown-menu-toggle.js-label-select.js-filter-submit.js-multiselect.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") + = h(multi_label_name(params[:label_name], "Label")) = icon('chevron-down') .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable - .dropdown-page-one - = dropdown_title("Filter by label") - = dropdown_filter("Search labels") - = dropdown_content - - if @project - = dropdown_footer do - %ul.dropdown-footer-list - - if can? current_user, :admin_label, @project - %li - %a.dropdown-toggle-page{href: "#"} - Create new - %li - = link_to namespace_project_labels_path(@project.namespace, @project) do - - if can? current_user, :admin_label, @project - Manage labels - - else - View labels + = render partial: "shared/issuable/label_page_default", locals: { title: "Filter by label" } - if can? current_user, :admin_label, @project and @project - .dropdown-page-two.dropdown-new-label - = dropdown_title("Create new label", back: true) - = dropdown_content do - .dropdown-labels-error.js-label-error - %input#new_label_name.dropdown-input-field{type: "text", placeholder: "Name new label"} - .suggest-colors.suggest-colors-dropdown - - suggested_colors.each do |color| - = link_to '#', style: "background-color: #{color}", data: { color: color } do -   - .dropdown-label-color-input - .dropdown-label-color-preview.js-dropdown-label-color-preview - %input#new_label_color.dropdown-input-field{ type: "text" } - .clearfix - %button.btn.btn-primary.pull-left.js-new-label-btn{type: "button"} - Create - %button.btn.btn-default.pull-right.js-cancel-label-btn{type: "button"} - Cancel + = render partial: "shared/issuable/label_page_create" = dropdown_loading diff --git a/app/views/shared/issuable/_label_page_create.html.haml b/app/views/shared/issuable/_label_page_create.html.haml new file mode 100644 index 00000000000..3bc57d3d2ac --- /dev/null +++ b/app/views/shared/issuable/_label_page_create.html.haml @@ -0,0 +1,17 @@ +.dropdown-page-two.dropdown-new-label + = dropdown_title("Create new label", back: true) + = dropdown_content do + .dropdown-labels-error.js-label-error + %input#new_label_name.default-dropdown-input{ type: "text", placeholder: "Name new label" } + .suggest-colors.suggest-colors-dropdown + - suggested_colors.each do |color| + = link_to '#', style: "background-color: #{color}", data: { color: color } do +   + .dropdown-label-color-input + .dropdown-label-color-preview.js-dropdown-label-color-preview + %input#new_label_color.default-dropdown-input{ type: "text" } + .clearfix + %button.btn.btn-primary.pull-left.js-new-label-btn{ type: "button" } + Create + %button.btn.btn-default.pull-right.js-cancel-label-btn{ type: "button" } + Cancel diff --git a/app/views/shared/issuable/_label_page_default.html.haml b/app/views/shared/issuable/_label_page_default.html.haml new file mode 100644 index 00000000000..7f4867417f7 --- /dev/null +++ b/app/views/shared/issuable/_label_page_default.html.haml @@ -0,0 +1,20 @@ +- title = local_assigns.fetch(:title, 'Assign labels') +- filter_placeholder = local_assigns.fetch(:filter_placeholder, 'Search labels') +.dropdown-page-one + = dropdown_title(title) + = dropdown_filter(filter_placeholder) + = dropdown_content + - if @project + = dropdown_footer do + %ul.dropdown-footer-list + - if can? current_user, :admin_label, @project + %li + %a.dropdown-toggle-page{href: "#"} + Create new + %li + = link_to namespace_project_labels_path(@project.namespace, @project), :"data-is-link" => true do + - if can? current_user, :admin_label, @project + Manage labels + - else + View labels + = dropdown_loading
\ No newline at end of file diff --git a/app/views/shared/issuable/_nav.html.haml b/app/views/shared/issuable/_nav.html.haml index a6970b7eebb..1d9b09a5ef1 100644 --- a/app/views/shared/issuable/_nav.html.haml +++ b/app/views/shared/issuable/_nav.html.haml @@ -4,22 +4,22 @@ - else - page_context_word = 'issues' %li{class: ("active" if params[:state] == 'opened')} - = link_to page_filter_path(state: 'opened'), title: "Filter by #{page_context_word} that are currently opened." do + = link_to page_filter_path(state: 'opened', label: true), title: "Filter by #{page_context_word} that are currently opened." do #{state_filters_text_for(:opened, @project)} - if defined?(type) && type == :merge_requests %li{class: ("active" if params[:state] == 'merged')} - = link_to page_filter_path(state: 'merged'), title: 'Filter by merge requests that are currently merged.' do + = link_to page_filter_path(state: 'merged', label: true), title: 'Filter by merge requests that are currently merged.' do #{state_filters_text_for(:merged, @project)} %li{class: ("active" if params[:state] == 'closed')} - = link_to page_filter_path(state: 'closed'), title: 'Filter by merge requests that are currently closed and unmerged.' do + = link_to page_filter_path(state: 'closed', label: true), title: 'Filter by merge requests that are currently closed and unmerged.' do #{state_filters_text_for(:closed, @project)} - else %li{class: ("active" if params[:state] == 'closed')} - = link_to page_filter_path(state: 'closed'), title: 'Filter by issues that are currently closed.' do + = link_to page_filter_path(state: 'closed', label: true), title: 'Filter by issues that are currently closed.' do #{state_filters_text_for(:closed, @project)} %li{class: ("active" if params[:state] == 'all')} - = link_to page_filter_path(state: 'all'), title: "Show all #{page_context_word}." do + = link_to page_filter_path(state: 'all', label: true), title: "Show all #{page_context_word}." do #{state_filters_text_for(:all, @project)} diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 08bfd93f4e6..991d4d675dd 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -10,14 +10,14 @@ = sidebar_gutter_toggle_icon .issuable-nav.hide-collapsed.pull-right.btn-group{role: 'group', "aria-label" => '...'} - if prev_issuable = prev_issuable_for(issuable) - = link_to 'Prev', [@project.namespace.becomes(Namespace), @project, prev_issuable], class: 'btn btn-default prev-btn' + = link_to 'Prev', [@project.namespace.becomes(Namespace), @project, prev_issuable], class: 'btn btn-default prev-btn issuable-pager' - else - %a.btn.btn-default.disabled{href: '#'} + %a.btn.btn-default.issuable-pager.disabled{href: '#'} Prev - if next_issuable = next_issuable_for(issuable) - = link_to 'Next', [@project.namespace.becomes(Namespace), @project, next_issuable], class: 'btn btn-default next-btn' + = link_to 'Next', [@project.namespace.becomes(Namespace), @project, next_issuable], class: 'btn btn-default next-btn issuable-pager' - else - %a.btn.btn-default.disabled{href: '#'} + %a.btn.btn-default.issuable-pager.disabled{href: '#'} Next = form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f| @@ -58,7 +58,7 @@ - if issuable.milestone = issuable.milestone.title - else - No + None .title.hide-collapsed Milestone = icon('spinner spin', class: 'block-loading') @@ -75,6 +75,34 @@ = f.hidden_field 'milestone_id', value: issuable.milestone_id, id: nil = dropdown_tag('Milestone', options: { title: 'Assign milestone', toggle_class: 'js-milestone-select js-extra-options', filter: true, dropdown_class: 'dropdown-menu-selectable', placeholder: 'Search milestones', data: { show_no: true, field_name: "#{issuable.to_ability_name}[milestone_id]", project_id: @project.id, issuable_id: issuable.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), ability_name: issuable.to_ability_name, issue_update: issuable_json_path(issuable), use_id: true }}) + - if issuable.has_attribute?(:due_date) + .block.due_date + .sidebar-collapsed-icon + = icon('calendar') + %span.js-due-date-sidebar-value + = issuable.due_date.try(:to_s, :medium) || 'None' + .title.hide-collapsed + Due date + = icon('spinner spin', class: 'block-loading') + - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project) + = link_to 'Edit', '#', class: 'edit-link pull-right' + .value.bold.hide-collapsed + - if issuable.due_date + = issuable.due_date.to_s(:medium) + - else + .light None + - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project) + .selectbox.hide-collapsed + = f.hidden_field :due_date, value: issuable.due_date + .dropdown + %button.dropdown-menu-toggle.js-due-date-select{ type: 'button', data: { toggle: 'dropdown', field_name: "#{issuable.to_ability_name}[due_date]", ability_name: issuable.to_ability_name, issue_update: issuable_json_path(issuable) } } + %span.dropdown-toggle-text Due date + = icon('chevron-down') + .dropdown-menu.dropdown-menu-due-date + = dropdown_title('Due date') + = dropdown_content do + .js-due-date-calendar + - if issuable.project.labels.any? .block.labels .sidebar-collapsed-icon @@ -101,23 +129,9 @@ Label = icon('chevron-down') .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable - .dropdown-page-one - = dropdown_title("Assign labels") - = dropdown_filter("Search labels") - = dropdown_content - - if @project - = dropdown_footer do - %ul.dropdown-footer-list - - if can? current_user, :admin_label, @project - %li - %a.dropdown-toggle-page{href: "#"} - Create new - %li - = link_to namespace_project_labels_path(@project.namespace, @project) do - - if can? current_user, :admin_label, @project - Manage labels - - else - View labels + = render partial: "shared/issuable/label_page_default" + - if can? current_user, :admin_label, @project and @project + = render partial: "shared/issuable/label_page_create" = render "shared/issuable/participants", participants: issuable.participants(current_user) - if current_user @@ -150,6 +164,7 @@ :javascript new MilestoneSelect('{"namespace":"#{@project.namespace.path}","path":"#{@project.path}"}'); new LabelsSelect(); - new IssuableContext('#{current_user.to_json(only: [:username, :id, :name])}'); + new IssuableContext('#{escape_javascript(current_user.to_json(only: [:username, :id, :name]))}'); new Subscription('.subscription') new Sidebar(); + new DueDateSelect(); diff --git a/app/views/shared/milestones/_issuable.html.haml b/app/views/shared/milestones/_issuable.html.haml index e1127b2311c..47b66d44e43 100644 --- a/app/views/shared/milestones/_issuable.html.haml +++ b/app/views/shared/milestones/_issuable.html.haml @@ -23,5 +23,5 @@ - if assignee = link_to polymorphic_path(base_url_args, { milestone_title: @milestone.title, assignee_id: issuable.assignee_id, state: 'all' }), - class: 'has-tooltip', data: { 'original-title' => "Assigned to #{sanitize(assignee.name)}", container: 'body' } do + class: 'has-tooltip', title: "Assigned to #{assignee.name}", data: { container: 'body' } do - image_tag(avatar_icon(issuable.assignee, 16), class: "avatar s16", alt: '') diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml index 53ff8959bc8..ab8b022411d 100644 --- a/app/views/shared/projects/_project.html.haml +++ b/app/views/shared/projects/_project.html.haml @@ -6,9 +6,8 @@ - css_class = '' unless local_assigns[:css_class] - show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true && project.commit - css_class += " no-description" if project.description.blank? && !show_last_commit_as_description -- ci_commit = project.ci_commit(project.commit.sha) if ci && !project.empty_repo? && project.commit - cache_key = [project.namespace, project, controller.controller_name, controller.action_name, current_application_settings, 'v2.3'] -- cache_key.push(ci_commit.status) if ci_commit +- cache_key.push(project.commit.status) if project.commit.try(:status) %li.project-row{ class: css_class } = cache(cache_key) do @@ -16,9 +15,9 @@ - if project.main_language %span = project.main_language - - if ci_commit + - if project.commit.try(:status) %span - = render_ci_status(ci_commit) + = render_ci_status(project.commit) - if forks %span = icon('code-fork') diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 0c4b6a5618b..3028491e5b6 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -6,8 +6,6 @@ = content_for :meta_tags do = auto_discovery_link_tag(:atom, user_url(@user, format: :atom), title: "#{@user.name} activity") -= render 'shared/show_aside' - .user-profile .cover-block .cover-controls diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb index 9e1215b21a6..f3327ca9e61 100644 --- a/app/workers/post_receive.rb +++ b/app/workers/post_receive.rb @@ -39,7 +39,7 @@ class PostReceive end if Gitlab::Git.tag_ref?(ref) - GitTagPushService.new.execute(post_received.project, @user, oldrev, newrev, ref) + GitTagPushService.new(post_received.project, @user, oldrev: oldrev, newrev: newrev, ref: ref).execute elsif Gitlab::Git.branch_ref?(ref) GitPushService.new(post_received.project, @user, oldrev: oldrev, newrev: newrev, ref: ref).execute end @@ -47,7 +47,7 @@ class PostReceive end private - + def log(message) Gitlab::GitLogger.error("POST-RECEIVE: #{message}") end diff --git a/app/workers/repository_check/single_repository_worker.rb b/app/workers/repository_check/single_repository_worker.rb index e54ae86d06c..a76729e3c74 100644 --- a/app/workers/repository_check/single_repository_worker.rb +++ b/app/workers/repository_check/single_repository_worker.rb @@ -15,10 +15,10 @@ module RepositoryCheck private def check(project) + repositories = [project.repository] + repositories << project.wiki.repository if project.wiki_enabled? # 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? + repositories.map { |repository| git_fsck(repository.path_to_repo) }.all? end def git_fsck(path) |