diff options
264 files changed, 3011 insertions, 1602 deletions
diff --git a/CHANGELOG b/CHANGELOG index 01e7e7a1606..72679b89d82 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.9.0 (unreleased) + - Fix builds API response not including commit data - Fix error when CI job variables key specified but not defined - Fix pipeline status when there are no builds in pipeline - Fix Error 500 when using closes_issues API with an external issue tracker @@ -8,17 +9,22 @@ v 8.9.0 (unreleased) - Bulk assign/unassign labels to issues. - Ability to prioritize labels !4009 / !3205 (Thijs Wouters) - Show Star and Fork buttons on mobile. + - Performance improvements on RelativeLinkFilter - Fix endless redirections when accessing user OAuth applications when they are disabled - Allow enabling wiki page events from Webhook management UI - Bump rouge to 1.11.0 + - Fix MR-auto-close text added to description - Fix issue with arrow keys not working in search autocomplete dropdown - Fix an issue where note polling stopped working if a window was in the background during a refresh. + - Pre-processing Markdown now only happens when needed - Make EmailsOnPushWorker use Sidekiq mailers queue - Redesign all Devise emails. !4297 - Don't show 'Leave Project' to group members - Fix wiki page events' webhook to point to the wiki repository + - Add a border around images to differentiate them from the background. - Don't show tags for revert and cherry-pick operations + - Show image ID on registry page - Fix issue todo not remove when leave project !4150 (Long Nguyen) - Allow customisable text on the 'nearly there' page after a user signs up - Bump recaptcha gem to 3.0.0 to remove deprecated stoken support @@ -70,6 +76,7 @@ v 8.9.0 (unreleased) - Todos will display target state if issuable target is 'Closed' or 'Merged' - Validate only and except regexp - Fix bug when sorting issues by milestone due date and filtering by two or more labels + - POST to API /projects/:id/runners/:runner_id would give 409 if the runner was already enabled for this project - Add support for using Yubikeys (U2F) for two-factor authentication - Link to blank group icon doesn't throw a 404 anymore - Remove 'main language' feature @@ -77,13 +84,16 @@ v 8.9.0 (unreleased) - Pipelines can be canceled only when there are running builds - Allow authentication using personal access tokens - Use downcased path to container repository as this is expected path by Docker + - Allow to use CI token to fetch LFS objects - Custom notification settings - Projects pending deletion will render a 404 page - Measure queue duration between gitlab-workhorse and Rails - Added Gfm autocomplete for labels + - Added edit note 'up' shortcut documentation to the help panel and docs screenshot #18114 - Make Omniauth providers specs to not modify global configuration - Remove unused JiraIssue class and replace references with ExternalIssue. !4659 (Ilan Shamir) - Make authentication service for Container Registry to be compatible with < Docker 1.11 + - Make it possible to lock a runner from being enabled for other projects - Add Application Setting to configure Container Registry token expire delay (default 5min) - Cache assigned issue and merge request counts in sidebar nav - Use Knapsack only in CI environment @@ -101,6 +111,7 @@ v 8.9.0 (unreleased) - An indicator is now displayed at the top of the comment field for confidential issues. - Show categorised search queries in the search autocomplete - RepositoryCheck::SingleRepositoryWorker public and private methods are now instrumented + - Dropdown for `.gitlab-ci.yml` templates - Improve issuables APIs performance when accessing notes !4471 - Add sorting dropdown to tags page !4423 - External links now open in a new tab @@ -129,11 +140,17 @@ v 8.9.0 (unreleased) - Various associations are now eager loaded when parsing issue references to reduce the number of queries executed - Set inverse_of for Project/Service association to reduce the number of queries - Update tanuki logo highlight/loading colors + - Remove explicit Gitlab::Metrics.action assignments, are already automatic. - Use Git cached counters for branches and tags on project page + - Cache participable participants in an instance variable. - Filter parameters for request_uri value on instrumented transactions. - Remove duplicated keys add UNIQUE index to keys fingerprint column + - ExtractsPath get ref_names from repository cache, if not there access git. - Cache user todo counts from TodoService - Ensure Todos counters doesn't count Todos for projects pending delete + - Add left/right arrows horizontal navigation + - Add tooltip to pin/unpin navbar + - Add new sub nav style to Wiki and Graphs sub navigation v 8.8.5 - Import GitHub repositories respecting the API rate limit !4166 @@ -48,7 +48,7 @@ gem 'attr_encrypted', '~> 3.0.0' gem 'u2f', '~> 0.2.1' # Browser detection -gem "browser", '~> 2.0.3' +gem "browser", '~> 2.2' # Extracting information from a git repository # Provide access to Gitlab::Git library @@ -330,7 +330,7 @@ gem "newrelic_rpm", '~> 3.14' gem 'octokit', '~> 4.3.0' -gem "mail_room", "~> 0.7" +gem "mail_room", "~> 0.8" gem 'email_reply_parser', '~> 0.5.8' diff --git a/Gemfile.lock b/Gemfile.lock index 49e548fb94f..ba16e4bf337 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -98,7 +98,7 @@ GEM autoprefixer-rails (>= 5.2.1) sass (>= 3.3.4) brakeman (3.3.2) - browser (2.0.3) + browser (2.2.0) builder (3.2.2) bullet (5.0.0) activesupport (>= 3.0.0) @@ -398,7 +398,7 @@ GEM systemu (~> 2.6.2) mail (2.6.4) mime-types (>= 1.16, < 4) - mail_room (0.7.0) + mail_room (0.8.0) method_source (0.8.2) mime-types (2.99.2) mimemagic (0.3.0) @@ -833,7 +833,7 @@ DEPENDENCIES binding_of_caller (~> 0.7.2) bootstrap-sass (~> 3.3.0) brakeman (~> 3.3.0) - browser (~> 2.0.3) + browser (~> 2.2) bullet bundler-audit byebug @@ -899,7 +899,7 @@ DEPENDENCIES license_finder licensee (~> 8.0.0) loofah (~> 2.0.3) - mail_room (~> 0.7) + mail_room (~> 0.8) method_source (~> 0.8) minitest (~> 5.7.0) mousetrap-rails (~> 1.4.6) diff --git a/app/assets/javascripts/api.js.coffee b/app/assets/javascripts/api.js.coffee index 3f61ea1eaf4..cf46f15a156 100644 --- a/app/assets/javascripts/api.js.coffee +++ b/app/assets/javascripts/api.js.coffee @@ -7,6 +7,7 @@ labelsPath: "/api/:version/projects/:id/labels" licensePath: "/api/:version/licenses/:key" gitignorePath: "/api/:version/gitignores/:key" + gitlabCiYmlPath: "/api/:version/gitlab_ci_ymls/:key" group: (group_id, callback) -> url = Api.buildUrl(Api.groupPath) @@ -110,6 +111,12 @@ $.get url, (gitignore) -> callback(gitignore) + gitlabCiYml: (key, callback) -> + url = Api.buildUrl(Api.gitlabCiYmlPath).replace(':key', key) + + $.get url, (file) -> + callback(file) + 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/application.js.coffee b/app/assets/javascripts/application.js.coffee index 4529c514555..0206db461da 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -121,6 +121,11 @@ window.onload = -> setTimeout shiftWindow, 100 $ -> + + $document = $(document) + $window = $(window) + $body = $('body') + gl.utils.preventDisabledButtons() bootstrapBreakpoint = bp.getBreakpointSize() @@ -152,7 +157,7 @@ $ -> ), 1 # Initialize tooltips - $('body').tooltip( + $body.tooltip( selector: '.has-tooltip, [data-toggle="tooltip"]' placement: (_, el) -> $el = $(el) @@ -171,7 +176,7 @@ $ -> flash.show() # Disable form buttons while a form is submitting - $('body').on 'ajax:complete, ajax:beforeSend, submit', 'form', (e) -> + $body.on 'ajax:complete, ajax:beforeSend, submit', 'form', (e) -> buttons = $('[type="submit"]', @) switch e.type @@ -184,7 +189,7 @@ $ -> $('.account-box').hover -> $(@).toggleClass('hover') # Commit show suppressed diff - $(document).on 'click', '.diff-content .js-show-suppressed-diff', -> + $document.on 'click', '.diff-content .js-show-suppressed-diff', -> $container = $(@).parent() $container.next('table').show() $container.remove() @@ -197,13 +202,13 @@ $ -> $('.navbar-toggle i').toggleClass("fa-angle-right fa-angle-left") # Show/hide comments on diff - $("body").on "click", ".js-toggle-diff-comments", (e) -> + $body.on "click", ".js-toggle-diff-comments", (e) -> $(@).toggleClass('active') $(@).closest(".diff-file").find(".notes_holder").toggle() e.preventDefault() - $(document).off "click", '.js-confirm-danger' - $(document).on "click", '.js-confirm-danger', (e) -> + $document.off "click", '.js-confirm-danger' + $document.on "click", '.js-confirm-danger', (e) -> e.preventDefault() btn = $(e.target) text = btn.data("confirm-danger-message") @@ -211,7 +216,7 @@ $ -> new ConfirmDangerModal(form, text) - $(document).on 'click', 'button', -> + $document.on 'click', 'button', -> $(this).blur() $('input[type="search"]').each -> @@ -219,7 +224,7 @@ $ -> $this.attr 'value', $this.val() return - $(document) + $document .off 'keyup', 'input[type="search"]' .on 'keyup', 'input[type="search"]' , (e) -> $this = $(this) @@ -227,7 +232,7 @@ $ -> $sidebarGutterToggle = $('.js-sidebar-toggle') - $(document) + $document .off 'breakpoint:change' .on 'breakpoint:change', (e, breakpoint) -> if breakpoint is 'sm' or breakpoint is 'xs' @@ -239,14 +244,14 @@ $ -> oldBootstrapBreakpoint = bootstrapBreakpoint bootstrapBreakpoint = bp.getBreakpointSize() if bootstrapBreakpoint != oldBootstrapBreakpoint - $(document).trigger('breakpoint:change', [bootstrapBreakpoint]) + $document.trigger('breakpoint:change', [bootstrapBreakpoint]) checkInitialSidebarSize = -> bootstrapBreakpoint = bp.getBreakpointSize() if bootstrapBreakpoint is "xs" or "sm" - $(document).trigger('breakpoint:change', [bootstrapBreakpoint]) + $document.trigger('breakpoint:change', [bootstrapBreakpoint]) - $(window) + $window .off "resize.app" .on "resize.app", (e) -> fitSidebarForSize() @@ -256,29 +261,45 @@ $ -> new Aside() # Sidenav pinning - if $(window).width() < 1440 and $.cookie('pin_nav') is 'true' + if $window.width() < 1440 and $.cookie('pin_nav') is 'true' $.cookie('pin_nav', 'false', { path: '/' }) $('.page-with-sidebar') .toggleClass('page-sidebar-collapsed page-sidebar-expanded') .removeClass('page-sidebar-pinned') $('.navbar-fixed-top').removeClass('header-pinned-nav') - $(document) + $document .off 'click', '.js-nav-pin' .on 'click', '.js-nav-pin', (e) -> e.preventDefault() + $pinBtn = $(e.currentTarget) + $page = $ '.page-with-sidebar' + $topNav = $ '.navbar-fixed-top' + $tooltip = $ "##{$pinBtn.attr('aria-describedby')}" + doPinNav = not $page.is('.page-sidebar-pinned') + tooltipText = 'Pin navigation' + $(this).toggleClass 'is-active' - if $.cookie('pin_nav') is 'true' - $.cookie 'pin_nav', 'false', { path: '/' } - $('.page-with-sidebar') - .removeClass('page-sidebar-pinned') - .toggleClass('page-sidebar-collapsed page-sidebar-expanded') - $('.navbar-fixed-top') - .removeClass('header-pinned-nav') - .toggleClass('header-collapsed header-expanded') + if doPinNav + $page.addClass('page-sidebar-pinned') + $topNav.addClass('header-pinned-nav') else - $.cookie 'pin_nav', 'true', { path: '/' } - $('.page-with-sidebar').addClass('page-sidebar-pinned') - $('.navbar-fixed-top').addClass('header-pinned-nav') + $tooltip.remove() # Remove it immediately when collapsing the sidebar + $page.removeClass('page-sidebar-pinned') + .toggleClass('page-sidebar-collapsed page-sidebar-expanded') + $topNav.removeClass('header-pinned-nav') + .toggleClass('header-collapsed header-expanded') + + # Save settings + $.cookie 'pin_nav', doPinNav, { path: '/' } + + if $.cookie('pin_nav') is 'true' or doPinNav + tooltipText = 'Unpin navigation' + + # Update tooltip text immediately + $tooltip.find('.tooltip-inner').text(tooltipText) + + # Persist tooltip title + $pinBtn.attr('title', tooltipText).tooltip('fixTitle') diff --git a/app/assets/javascripts/blob/blob_ci_yaml.js.coffee b/app/assets/javascripts/blob/blob_ci_yaml.js.coffee new file mode 100644 index 00000000000..d9a03d05529 --- /dev/null +++ b/app/assets/javascripts/blob/blob_ci_yaml.js.coffee @@ -0,0 +1,23 @@ +#= require blob/template_selector + +class @BlobCiYamlSelector extends TemplateSelector + requestFile: (query) -> + Api.gitlabCiYml query.name, @requestFileSuccess.bind(@) + +class @BlobCiYamlSelectors + constructor: (opts) -> + { + @$dropdowns = $('.js-gitlab-ci-yml-selector') + @editor + } = opts + + @$dropdowns.each (i, dropdown) => + $dropdown = $(dropdown) + + new BlobCiYamlSelector( + pattern: /(.gitlab-ci.yml)/, + data: $dropdown.data('data'), + wrapper: $dropdown.closest('.js-gitlab-ci-yml-selector-wrap'), + dropdown: $dropdown, + editor: @editor + ) diff --git a/app/assets/javascripts/blob/edit_blob.js.coffee b/app/assets/javascripts/blob/edit_blob.js.coffee index 636f909dbd0..19e584519d7 100644 --- a/app/assets/javascripts/blob/edit_blob.js.coffee +++ b/app/assets/javascripts/blob/edit_blob.js.coffee @@ -15,6 +15,7 @@ class @EditBlob new BlobLicenseSelectors { @editor } new BlobGitignoreSelectors { @editor } + new BlobCiYamlSelectors { @editor } initModePanesAndLinks: -> @$editModePanes = $(".js-edit-mode-pane") diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee index 2a8a1f05b35..2a7bf0bc306 100644 --- a/app/assets/javascripts/gl_dropdown.js.coffee +++ b/app/assets/javascripts/gl_dropdown.js.coffee @@ -58,7 +58,7 @@ class GitLabDropdownFilter filter: (search_text) -> data = @options.data() - if data? + if data? and not @options.filterByText results = data if search_text isnt '' @@ -102,10 +102,11 @@ class GitLabDropdownFilter $el = $(@) matches = fuzzaldrinPlus.match($el.text().trim(), search_text) - if matches.length - $el.show() - else - $el.hide() + unless $el.is('.dropdown-header') + if matches.length + $el.show() + else + $el.hide() else elements.show() @@ -191,6 +192,7 @@ class GitLabDropdown if @options.filterable @filter = new GitLabDropdownFilter @filterInput, filterInputBlur: @filterInputBlur + filterByText: @options.filterByText remote: @options.filterRemote query: @options.data keys: searchFields diff --git a/app/assets/javascripts/gl_form.js.coffee b/app/assets/javascripts/gl_form.js.coffee index d540cc4dc46..77512d187c9 100644 --- a/app/assets/javascripts/gl_form.js.coffee +++ b/app/assets/javascripts/gl_form.js.coffee @@ -34,6 +34,8 @@ class @GLForm # form and textarea event listeners @addEventListeners() + gl.text.init(@form) + # hide discard button @form.find('.js-note-discard').hide() @@ -42,6 +44,7 @@ class @GLForm clearEventListeners: -> @textarea.off 'focus' @textarea.off 'blur' + gl.text.removeListeners(@form) addEventListeners: -> @textarea.on 'focus', -> diff --git a/app/assets/javascripts/graphs/stat_graph_contributors_graph.js.coffee b/app/assets/javascripts/graphs/stat_graph_contributors_graph.js.coffee index 584d281a510..834a81af459 100644 --- a/app/assets/javascripts/graphs/stat_graph_contributors_graph.js.coffee +++ b/app/assets/javascripts/graphs/stat_graph_contributors_graph.js.coffee @@ -121,7 +121,11 @@ class @ContributorsMasterGraph extends ContributorsGraph class @ContributorsAuthorGraph extends ContributorsGraph constructor: (@data) -> - @width = $('.content').width()/2 - 100 + # Don't split graph size in half for mobile devices. + if $(window).width() < 768 + @width = $('.content').width() - 80 + else + @width = ($('.content').width() / 2) - 100 @height = 200 @x = null @y = null diff --git a/app/assets/javascripts/lib/text_utility.js.coffee b/app/assets/javascripts/lib/text_utility.js.coffee new file mode 100644 index 00000000000..bb2772dfed2 --- /dev/null +++ b/app/assets/javascripts/lib/text_utility.js.coffee @@ -0,0 +1,79 @@ +((w) -> + w.gl ?= {} + w.gl.text ?= {} + + gl.text.randomString = -> Math.random().toString(36).substring(7) + + gl.text.replaceRange = (s, start, end, substitute) -> + s.substring(0, start) + substitute + s.substring(end); + + gl.text.selectedText = (text, textarea) -> + text.substring(textarea.selectionStart, textarea.selectionEnd) + + gl.text.insertText = (textArea, text, tag, selected, wrap) -> + selectedSplit = selected.split('\n') + startChar = if not wrap and textArea.selectionStart > 0 then '\n' else '' + + if selectedSplit.length > 1 and not wrap + insertText = selectedSplit.map((val) -> + if val.indexOf(tag) is 0 + "#{val.replace(tag, '')}" + else + "#{tag}#{val}" + ).join('\n') + else + insertText = "#{startChar}#{tag}#{selected}#{if wrap then tag else ' '}" + + if document.queryCommandSupported('insertText') + document.execCommand 'insertText', false, insertText + else + try + document.execCommand("ms-beginUndoUnit") + + textArea.value = @replaceRange( + text, + textArea.selectionStart, + textArea.selectionEnd, + insertText) + try + document.execCommand("ms-endUndoUnit") + + @moveCursor(textArea, tag, wrap) + + gl.text.moveCursor = (textArea, tag, wrapped) -> + return unless textArea.setSelectionRange + + if textArea.selectionStart is textArea.selectionEnd + if wrapped + pos = textArea.selectionStart - tag.length + else + pos = textArea.selectionStart + + textArea.setSelectionRange pos, pos + + gl.text.updateText = (textArea, tag, wrap) -> + $textArea = $(textArea) + oldVal = $textArea.val() + textArea = $textArea.get(0) + text = $textArea.val() + selected = @selectedText(text, textArea) + $textArea.focus() + + @insertText(textArea, text, tag, selected, wrap) + + gl.text.init = (form) -> + self = @ + $('.js-md', form) + .off 'click' + .on 'click', -> + $this = $(@) + self.updateText( + $this.closest('.md-area').find('textarea'), + $this.data('md-tag'), + not $this.data('md-prepend') + ) + + gl.text.removeListeners = (form) -> + $('.js-md', form).off() + +) window diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index e2d3241437b..17f7e180127 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -102,12 +102,15 @@ class @Notes keydownNoteText: (e) -> $this = $(this) - if $this.val() is '' and e.which is 38 #aka the up key + if $this.val() is '' and e.which is 38 and not isMetaKey e myLastNote = $("li.note[data-author-id='#{gon.current_user_id}'][data-editable]:last") if myLastNote.length myLastNoteEditBtn = myLastNote.find('.js-note-edit') myLastNoteEditBtn.trigger('click', [true, myLastNote]) + isMetaKey = (e) -> + (e.metaKey or e.ctrlKey or e.altKey or e.shiftKey) + initRefresh: -> clearInterval(Notes.interval) Notes.interval = setInterval => diff --git a/app/assets/javascripts/notifications_dropdown.js.coffee b/app/assets/javascripts/notifications_dropdown.js.coffee index 74d2298c1fa..0bbd082c156 100644 --- a/app/assets/javascripts/notifications_dropdown.js.coffee +++ b/app/assets/javascripts/notifications_dropdown.js.coffee @@ -1,5 +1,5 @@ class @NotificationsDropdown - $ -> + constructor: -> $(document) .off 'click', '.update-notification' .on 'click', '.update-notification', (e) -> @@ -18,7 +18,8 @@ class @NotificationsDropdown .off 'ajax:success', '.notification-form' .on 'ajax:success', '.notification-form', (e, data) -> if data.saved - new Flash('Notification settings saved', 'notice') - $(e.currentTarget).closest('.notification-dropdown').replaceWith(data.html) + $(e.currentTarget) + .closest('.notification-dropdown') + .replaceWith(data.html) else new Flash('Failed to save new settings', 'alert') diff --git a/app/assets/javascripts/project.js.coffee b/app/assets/javascripts/project.js.coffee index d12bad97a05..96e10dd7e8a 100644 --- a/app/assets/javascripts/project.js.coffee +++ b/app/assets/javascripts/project.js.coffee @@ -19,6 +19,7 @@ class @Project $('.clone').text(url) # Ref switcher + @initRefSwitcher() $('.project-refs-select').on 'change', -> $(@).parents('form').submit() @@ -34,7 +35,6 @@ class @Project $(@).parents('.no-password-message').remove() e.preventDefault() - @projectSelectDropdown() projectSelectDropdown: -> @@ -50,3 +50,39 @@ class @Project changeProject: (url) -> window.location = url + + initRefSwitcher: -> + $('.js-project-refs-dropdown').each -> + $dropdown = $(@) + selected = $dropdown.data('selected') + + $dropdown.glDropdown( + data: (term, callback) -> + $.ajax( + url: $dropdown.data('refs-url') + data: + ref: $dropdown.data('ref') + ).done (refs) -> + callback(refs) + selectable: true + filterable: true + filterByText: true + fieldName: 'ref' + renderRow: (ref) -> + if ref.header? + "<li class='dropdown-header'>#{ref.header}</li>" + else + isActiveClass = if ref is selected then 'is-active' else '' + + "<li> + <a href='#' data-ref='#{escape(ref)}' class='#{isActiveClass}'> + #{ref} + </a> + </li>" + id: (obj, $el) -> + $el.data('ref') + toggleLabel: (obj, $el) -> + $el.text().trim() + clicked: (e) -> + $dropdown.closest('form').submit() + ) diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss index 3cbddc59f11..a306b8f3f29 100644 --- a/app/assets/stylesheets/framework.scss +++ b/app/assets/stylesheets/framework.scss @@ -37,3 +37,4 @@ @import "framework/timeline.scss"; @import "framework/typography.scss"; @import "framework/zen.scss"; +@import "framework/blank"; diff --git a/app/assets/stylesheets/framework/blank.scss b/app/assets/stylesheets/framework/blank.scss new file mode 100644 index 00000000000..40b5171a8c6 --- /dev/null +++ b/app/assets/stylesheets/framework/blank.scss @@ -0,0 +1,23 @@ +.blank-state { + padding-top: 20px; + padding-bottom: 20px; + text-align: center; +} + +.blank-state-no-icon { + padding-top: 40px; + padding-bottom: 40px; +} + +.blank-state-title { + margin-top: 0; + margin-bottom: 5px; + font-size: 19px; + font-weight: normal; +} + +.blank-state-text { + margin-top: 0; + margin-bottom: $gl-padding; + font-size: 15px; +} diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss index d5fe5bc2ef1..38023818709 100644 --- a/app/assets/stylesheets/framework/blocks.scss +++ b/app/assets/stylesheets/framework/blocks.scss @@ -97,6 +97,22 @@ } } +.sub-header-block { + background-color: $white-light; + border-bottom: 1px solid $white-dark; + padding: 11px 0; + margin-bottom: 11px; + + .oneline { + line-height: 35px; + } + + &.no-bottom-space { + border-bottom: 0; + margin-bottom: 0; + } +} + .cover-block { text-align: center; background: $background-color; diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index d4d579a083d..00111dfa706 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -461,10 +461,12 @@ } } - .ui-state-active, - .ui-state-hover { - color: $md-link-color; - background-color: $calendar-hover-bg; + .ui-datepicker-calendar { + .ui-state-hover, + .ui-state-active { + color: #fff; + border: 0; + } } .ui-datepicker-prev, diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss index fd885b38680..fd8eaa8a691 100644 --- a/app/assets/stylesheets/framework/markdown_area.scss +++ b/app/assets/stylesheets/framework/markdown_area.scss @@ -65,6 +65,11 @@ a { padding-top: 0; line-height: 1; + border-bottom: 1px solid $border-color; + + &.btn.btn-xs { + padding: 2px 5px; + } } } } @@ -97,5 +102,30 @@ white-space: pre-wrap; word-break: keep-all; } + + @include bulleted-list; + } +} + +.toolbar-group { + float: left; + margin-right: -5px; + margin-left: $gl-padding; + + &:first-child { + margin-left: 0; + } +} + +.toolbar-btn { + float: left; + padding: 0 5px; + color: #959494; + background: transparent; + border: 0; + outline: 0; + + &:hover { + color: $gl-link-color; } } diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss index 828e7224231..5ec5a96a597 100644 --- a/app/assets/stylesheets/framework/mixins.scss +++ b/app/assets/stylesheets/framework/mixins.scss @@ -110,3 +110,17 @@ font-size: 16px; line-height: 24px; } + +@mixin bulleted-list { + > ul { + list-style-type: disc; + + ul { + list-style-type: circle; + + ul { + list-style-type: square; + } + } + } +}
\ No newline at end of file diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index a55918f8711..0281b06d3ba 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -18,6 +18,13 @@ opacity: 0; transition-duration: .3s; } + + .fa { + position: relative; + top: 3px; + font-size: 13px; + color: $btn-placeholder-gray; + } } @mixin scrolling-links() { @@ -104,10 +111,6 @@ width: 50%; line-height: 28px; - &.wiki-page { - padding: 16px 10px 11px; - } - /* Small devices (phones, tablets, 768px and lower) */ @media (max-width: $screen-sm-min) { width: 100%; @@ -136,7 +139,7 @@ } /* Small devices (phones, tablets, 768px and lower) */ - @media (max-width: $screen-sm-max) { + @media (max-width: $screen-xs-max) { width: 100%; } } @@ -220,6 +223,7 @@ form { display: block; height: auto; + margin-bottom: 14px; input { width: 100%; @@ -319,11 +323,19 @@ .fade-right { @include fade(left, rgba(250, 250, 250, 0.4), $background-color); right: 0; + + .fa { + right: -7px; + } } .fade-left { @include fade(right, rgba(250, 250, 250, 0.4), $background-color); left: 0; + + .fa { + left: -7px; + } } li { diff --git a/app/assets/stylesheets/framework/panels.scss b/app/assets/stylesheets/framework/panels.scss index ae7bdf14c40..874416e1007 100644 --- a/app/assets/stylesheets/framework/panels.scss +++ b/app/assets/stylesheets/framework/panels.scss @@ -9,6 +9,10 @@ margin-top: -2px; float: right; } + + .dropdown-menu-toggle { + line-height: 20px; + } } .panel-body { diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss index f242706ebe4..21d87cc9d34 100644 --- a/app/assets/stylesheets/framework/selects.scss +++ b/app/assets/stylesheets/framework/selects.scss @@ -165,11 +165,6 @@ background-size: 16px 16px !important; } -/** Branch/tag selector **/ -.project-refs-form .select2-container { - width: 160px !important; -} - .select2-results .select2-no-results, .select2-results .select2-searching, .select2-results .select2-ajax-error, diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index a0bb3427af0..98f917ce69b 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -91,7 +91,6 @@ text-decoration: none; font-weight: normal; outline: none; - white-space: nowrap; &:hover, &:active, diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss index 761e33f0df7..de534d28421 100644 --- a/app/assets/stylesheets/pages/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -80,9 +80,14 @@ .commit { padding: 10px 0; + position: relative; @media (min-width: $screen-sm-min) { - padding-left: 46px; + padding-left: 20px; + + .commit-info-block { + padding-left: 44px; + } } &:not(:last-child) { @@ -95,8 +100,11 @@ vertical-align: baseline; } + .avatar { - margin-left: -46px; + position: absolute; + top: 10px; + left: 16px; } .item-title { diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 1a7d5f9666e..5286b73cc50 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -4,6 +4,11 @@ margin-bottom: $gl-padding; border-radius: 3px; + .commit-short-id { + font-family: $regular_font; + font-weight: 400; + } + .diff-header { position: relative; background: $background-color; diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss index a34b06f1054..1aa4e06d975 100644 --- a/app/assets/stylesheets/pages/editor.scss +++ b/app/assets/stylesheets/pages/editor.scss @@ -60,13 +60,14 @@ .encoding-selector, .license-selector, - .gitignore-selector { + .gitignore-selector, + .gitlab-ci-yml-selector { display: inline-block; vertical-align: top; font-family: $regular_font; } - .gitignore-selector, .license-selector { + .gitignore-selector, .license-selector, .gitlab-ci-yml-selector { .dropdown { line-height: 21px; } @@ -76,4 +77,10 @@ width: 220px; } } + + .gitlab-ci-yml-selector { + .dropdown-menu-toggle { + width: 250px; + } + } } diff --git a/app/assets/stylesheets/pages/help.scss b/app/assets/stylesheets/pages/help.scss index 4a95b7b852e..0b710ef168b 100644 --- a/app/assets/stylesheets/pages/help.scss +++ b/app/assets/stylesheets/pages/help.scss @@ -57,4 +57,11 @@ .documentation { padding: 7px; + + // Border around images in the help pages. + img:not(.emoji) { + border: 1px solid $table-border-gray; + padding: 5px; + margin: 5px; + } } diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 687117233f6..21ff6ab71f0 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -4,6 +4,13 @@ margin-right: 1px; } } + + // Border around images in issue and MR descriptions. + .description img:not(.emoji) { + border: 1px solid $table-border-gray; + padding: 5px; + margin: 5px; + } } .issuable-filter-count { diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index 046c38aba44..f5f67e2cd84 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -50,11 +50,10 @@ .label-row { .label-name { - display: block; + display: inline-block; margin-bottom: 10px; @media (min-width: $screen-sm-min) { - display: inline-block; width: 200px; margin-bottom: 0; } @@ -63,6 +62,7 @@ .label-description { display: block; margin-bottom: 10px; + margin-left: 50px; @media (min-width: $screen-sm-min) { display: inline-block; diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index e67271adfb1..aca82f7f7bf 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -119,7 +119,12 @@ margin-bottom: 0; } - @media (max-width: $screen-sm-max) { + .btn-grouped { + margin-left: 0; + margin-right: 7px; + } + + @media (max-width: $screen-xs-max) { h4 { font-size: 15px; } @@ -131,10 +136,14 @@ .btn, .btn-group, .accept-action { - width: 100%; margin-bottom: 4px; } + .accept-action { + width: 100%; + text-align: center; + } + .accept-control { width: 100%; text-align: center; @@ -284,7 +293,7 @@ margin-bottom: 0; } - @media (min-width: $screen-sm-min) { + @media (min-width: $screen-xs-min) { float: left; width: 50%; margin-bottom: 0; diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index de5ccbe2b6c..3784010348a 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -179,6 +179,10 @@ border-top: 1px solid $border-color; } +.md-helper { + padding-top: 10px; +} + .toolbar-button { padding: 0; background: none; diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 35d728aec83..ffba3dc5bc6 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -84,24 +84,14 @@ ul.notes { word-wrap: break-word; @include md-typography; + // Reset ul style types since we're nested inside a ul already + @include bulleted-list; + // On diffs code should wrap nicely and not overflow code { white-space: pre-wrap; } - // Reset ul style types since we're nested inside a ul already - & > ul { - list-style-type: disc; - - ul { - list-style-type: circle; - - ul { - list-style-type: square; - } - } - } - ul.task-list { ul:not(.task-list) { padding-left: 1.3em; @@ -117,6 +107,13 @@ ul.notes { code { word-break: keep-all; } + + // Border around images in issue and MR comments. + img:not(.emoji) { + border: 1px solid $table-border-gray; + padding: 5px; + margin: 5px 0; + } } } diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index f138a2f5387..d3e59d7fdb9 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -101,7 +101,8 @@ .notifications-btn { - .fa-bell { + .fa-bell, + .fa-spinner { margin-right: 6px; } @@ -373,7 +374,7 @@ a.deploy-project-label { .project-stats { margin-top: $gl-padding; margin-bottom: 0; - padding: 16px 0; + padding: 0; background-color: $white-light; font-size: 0; @@ -382,13 +383,14 @@ a.deploy-project-label { } .nav li { - display: inline; + display: inline-block; + margin: 16px 0; + margin-right: 16px; } .nav > li > a { background-color: transparent; - margin-right: 12px; - padding: 0 10px; + padding: 5px 10px; font-size: 15px; color: $notes-light-color; } @@ -402,12 +404,17 @@ a.deploy-project-label { font-size: 17px; } - li.missing a { - color: #5a6069; - border: 1px dashed #dce0e5; + li.missing { + border: 1px dashed $border-gray-light; + border-radius: $border-radius-default; + + a { + color: $notes-light-color; + display: block; + } &:hover { - background-color: #f0f2f5; + background-color: $gray-normal; } } @@ -616,3 +623,9 @@ pre.light-well { color: $gl-text-green; } } + +.project-refs-form { + .dropdown-menu { + width: 300px; + } +} diff --git a/app/assets/stylesheets/pages/stat_graph.scss b/app/assets/stylesheets/pages/stat_graph.scss index 85a0304196c..69288b31cc4 100644 --- a/app/assets/stylesheets/pages/stat_graph.scss +++ b/app/assets/stylesheets/pages/stat_graph.scss @@ -14,24 +14,38 @@ font-size: 10px; } +#contributors-master { + @include make-md-column(12); + + svg { + width: 100%; + } +} + #contributors { .contributors-list { margin: 0 0 10px; list-style: none; padding: 0; + + svg { + width: 100%; + } } .person { - &:nth-child(even) { - float: right; - } - float: left; + @include make-md-column(6); margin-top: 10px; + + @media (max-width: $screen-sm-min) { + width: 100%; + } } .person .spark { display: block; background: #f3f3f3; + width: 100%; } .person .area-contributor { diff --git a/app/controllers/admin/appearances_controller.rb b/app/controllers/admin/appearances_controller.rb index 26cf74e4849..4b0ec54b3f4 100644 --- a/app/controllers/admin/appearances_controller.rb +++ b/app/controllers/admin/appearances_controller.rb @@ -5,6 +5,7 @@ class Admin::AppearancesController < Admin::ApplicationController end def preview + render 'preview', layout: 'devise' end def create diff --git a/app/controllers/admin/runner_projects_controller.rb b/app/controllers/admin/runner_projects_controller.rb index d25619d94e0..bf20c5305a7 100644 --- a/app/controllers/admin/runner_projects_controller.rb +++ b/app/controllers/admin/runner_projects_controller.rb @@ -1,15 +1,14 @@ class Admin::RunnerProjectsController < Admin::ApplicationController before_action :project, only: [:create] - def index - @runner_projects = project.runner_projects.all - @runner_project = project.runner_projects.new - end - def create @runner = Ci::Runner.find(params[:runner_project][:runner_id]) - if @runner.assign_to(@project, current_user) + return head(403) if @runner.is_shared? || @runner.locked? + + runner_project = @runner.assign_to(@project, current_user) + + if runner_project.persisted? redirect_to admin_runner_path(@runner) else redirect_to admin_runner_path(@runner), alert: 'Failed adding runner to project' diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index dd1bc6f5d52..9cc31620d9f 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -36,6 +36,10 @@ class ApplicationController < ActionController::Base render_404 end + rescue_from Gitlab::Access::AccessDeniedError do |exception| + render_403 + end + def redirect_back_or_default(default: root_path, options: {}) redirect_to request.referer.present? ? :back : default, options end diff --git a/app/controllers/concerns/membership_actions.rb b/app/controllers/concerns/membership_actions.rb index a24273fad0b..52dc396af6a 100644 --- a/app/controllers/concerns/membership_actions.rb +++ b/app/controllers/concerns/membership_actions.rb @@ -21,29 +21,18 @@ module MembershipActions def leave @member = membershipable.members.find_by(user_id: current_user) - return render_403 unless @member + Members::DestroyService.new(@member, current_user).execute source_type = @member.real_source_type.humanize(capitalize: false) - - if can?(current_user, action_member_permission(:destroy, @member), @member) - notice = - if @member.request? - "Your access request to the #{source_type} has been withdrawn." - else - "You left the \"#{@member.source.human_name}\" #{source_type}." - end - @member.destroy - - redirect_to [:dashboard, @member.real_source_type.tableize], notice: notice - else - if cannot_leave? - alert = "You can not leave the \"#{@member.source.human_name}\" #{source_type}." - alert << " Transfer or delete the #{source_type}." - redirect_to polymorphic_url(membershipable), alert: alert + notice = + if @member.request? + "Your access request to the #{source_type} has been withdrawn." else - render_403 + "You left the \"#{@member.source.human_name}\" #{source_type}." end - end + redirect_path = @member.request? ? @member.source : [:dashboard, @member.real_source_type.tableize] + + redirect_to redirect_path, notice: notice end protected @@ -51,8 +40,4 @@ module MembershipActions def membershipable raise NotImplementedError end - - def cannot_leave? - raise NotImplementedError - end end diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb index d0f2e2949f0..2c49fe3833e 100644 --- a/app/controllers/groups/group_members_controller.rb +++ b/app/controllers/groups/group_members_controller.rb @@ -36,9 +36,7 @@ class Groups::GroupMembersController < Groups::ApplicationController def destroy @group_member = @group.group_members.find(params[:id]) - return render_403 unless can?(current_user, :destroy_group_member, @group_member) - - @group_member.destroy + Members::DestroyService.new(@group_member, current_user).execute respond_to do |format| format.html { redirect_to group_group_members_path(@group), notice: 'User was successfully removed from group.' } @@ -68,8 +66,4 @@ class Groups::GroupMembersController < Groups::ApplicationController # MembershipActions concern alias_method :membershipable, :group - - def cannot_leave? - @group.last_owner?(current_user) - end end diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb index 776ba92c9ab..996909a28c6 100644 --- a/app/controllers/projects/application_controller.rb +++ b/app/controllers/projects/application_controller.rb @@ -74,7 +74,7 @@ class Projects::ApplicationController < ApplicationController end def require_branch_head - unless @repository.branch_names.include?(@ref) + unless @repository.branch_exists?(@ref) redirect_to( namespace_project_tree_path(@project.namespace, @project, @ref), notice: "This action is not allowed unless you are on a branch" diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index 127bd1a4318..487963fdcd7 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -54,6 +54,6 @@ class Projects::PipelinesController < Projects::ApplicationController end def commit - @commit ||= @pipeline.commit_data + @commit ||= @pipeline.commit end end diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 35d067cd029..6ba32d33403 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -50,9 +50,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController def destroy @project_member = @project.project_members.find(params[:id]) - return render_403 unless can?(current_user, :destroy_project_member, @project_member) - - @project_member.destroy + Members::DestroyService.new(@project_member, current_user).execute respond_to do |format| format.html do @@ -98,8 +96,4 @@ class Projects::ProjectMembersController < Projects::ApplicationController # MembershipActions concern alias_method :membershipable, :project - - def cannot_leave? - current_user == @project.owner - end end diff --git a/app/controllers/projects/runner_projects_controller.rb b/app/controllers/projects/runner_projects_controller.rb index bedeb4a295c..dc1a18f8d42 100644 --- a/app/controllers/projects/runner_projects_controller.rb +++ b/app/controllers/projects/runner_projects_controller.rb @@ -6,11 +6,13 @@ class Projects::RunnerProjectsController < Projects::ApplicationController def create @runner = Ci::Runner.find(params[:runner_project][:runner_id]) + return head(403) if @runner.is_shared? || @runner.locked? return head(403) unless current_user.ci_authorized_runners.include?(@runner) path = runners_path(project) + runner_project = @runner.assign_to(project, current_user) - if @runner.assign_to(project, current_user) + if runner_project.persisted? redirect_to path else redirect_to path, alert: 'Failed adding runner to project' diff --git a/app/controllers/projects/runners_controller.rb b/app/controllers/projects/runners_controller.rb index 0b4fa572501..53c36635efe 100644 --- a/app/controllers/projects/runners_controller.rb +++ b/app/controllers/projects/runners_controller.rb @@ -5,10 +5,9 @@ class Projects::RunnersController < Projects::ApplicationController layout 'project_settings' def index - @runners = project.runners.ordered - @specific_runners = current_user.ci_authorized_runners. - where.not(id: project.runners). - ordered.page(params[:page]).per(20) + @project_runners = project.runners.ordered + @assignable_runners = current_user.ci_authorized_runners. + assignable_for(project).ordered.page(params[:page]).per(20) @shared_runners = Ci::Runner.shared.active @shared_runners_count = @shared_runners.count(:all) end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 8044c637825..2b1f50fd01e 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -1,7 +1,7 @@ class ProjectsController < Projects::ApplicationController include ExtractsPath - before_action :authenticate_user!, except: [:show, :activity] + before_action :authenticate_user!, except: [:show, :activity, :refs] before_action :project, except: [:new, :create] before_action :repository, except: [:new, :create] before_action :assign_ref_vars, :tree, only: [:show], if: :repo_exists? @@ -251,6 +251,24 @@ class ProjectsController < Projects::ApplicationController } end + def refs + options = { + 'Branches' => @repository.branch_names, + } + + unless @repository.tag_count.zero? + options['Tags'] = VersionSorter.rsort(@repository.tag_names) + end + + # If reference is commit id - we should add it to branch/tag selectbox + ref = Addressable::URI.unescape(params[:ref]) + if ref && options.flatten(2).exclude?(ref) && ref =~ /\A[0-9a-zA-Z]{6,52}\z/ + options['Commits'] = [ref] + end + + render json: options.to_json + end + private def determine_layout @@ -285,8 +303,14 @@ class ProjectsController < Projects::ApplicationController project.repository_exists? && !project.empty_repo? end - # Override get_id from ExtractsPath, which returns the branch and file path + # Override extract_ref from ExtractsPath, which returns the branch and file path # for the blob/tree, which in this case is just the root of the default branch. + # This way we avoid to access the repository.ref_names. + def extract_ref(_id) + [get_id, ''] + end + + # Override get_id from ExtractsPath in this case is just the root of the default branch. def get_id project.repository.root_ref end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 439b015b3b8..41859841834 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -101,22 +101,6 @@ module ApplicationHelper 'Never' end - def grouped_options_refs - repository = @project.repository - - options = [ - ['Branches', repository.branch_names], - ['Tags', VersionSorter.rsort(repository.tag_names)] - ] - - # If reference is commit id - we should add it to branch/tag selectbox - if @ref && !options.flatten.include?(@ref) && @ref =~ /\A[0-9a-zA-Z]{6,52}\z/ - options << ['Commit', [@ref]] - end - - grouped_options_for_select(options, @ref || @project.default_branch) - end - # Define whenever show last push event # with suggestion to create MR def show_last_push_widget?(event) @@ -132,7 +116,7 @@ module ApplicationHelper return false if project.merge_requests.where(source_branch: event.branch_name).opened.any? # Skip if user removed branch right after that - return false unless project.repository.branch_names.include?(event.branch_name) + return false unless project.repository.branch_exists?(event.branch_name) true end diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index 5b54b34070c..4b4bc3d4276 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -29,7 +29,7 @@ module BlobHelper if !on_top_of_branch?(project, ref) button_tag "Edit", class: "btn disabled has-tooltip btn-file-option", title: "You can only edit files when you are on a branch", data: { container: 'body' } elsif can_edit_blob?(blob, project, ref) - link_to "Edit", edit_path, class: 'btn btn-file-option' + link_to "Edit", edit_path, class: 'btn btn-sm' elsif can?(current_user, :fork_project, project) continue_params = { to: edit_path, @@ -186,12 +186,16 @@ module BlobHelper end def gitignore_names - return @gitignore_names if defined?(@gitignore_names) + @gitignore_names ||= + Gitlab::Template::Gitignore.categories.keys.map do |k| + [k, Gitlab::Template::Gitignore.by_category(k).map { |t| { name: t.name } }] + end.to_h + end - @gitignore_names = { - Global: Gitlab::Gitignore.global.map { |gitignore| { name: gitignore.name } }, - # Note that the key here doesn't cover it really - Languages: Gitlab::Gitignore.languages_frameworks.map{ |gitignore| { name: gitignore.name } } - } + def gitlab_ci_ymls + @gitlab_ci_ymls ||= + Gitlab::Template::GitlabCiYml.categories.keys.map do |k| + [k, Gitlab::Template::GitlabCiYml.by_category(k).map { |t| { name: t.name } }] + end.to_h end end diff --git a/app/helpers/branches_helper.rb b/app/helpers/branches_helper.rb index 3ee3fc74f0c..c533659b600 100644 --- a/app/helpers/branches_helper.rb +++ b/app/helpers/branches_helper.rb @@ -10,7 +10,7 @@ module BranchesHelper end def can_push_branch?(project, branch_name) - return false unless project.repository.branch_names.include?(branch_name) + return false unless project.repository.branch_exists?(branch_name) ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(branch_name) end diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb index 067a00660aa..1a259656f31 100644 --- a/app/helpers/gitlab_markdown_helper.rb +++ b/app/helpers/gitlab_markdown_helper.rb @@ -50,8 +50,6 @@ module GitlabMarkdownHelper context[:project] ||= @project - text = Banzai.pre_process(text, context) - html = Banzai.render(text, context) context.merge!( @@ -185,4 +183,17 @@ module GitlabMarkdownHelper '' end end + + def markdown_toolbar_button(options = {}) + data = options[:data].merge({ container: "body" }) + content_tag :button, + type: "button", + class: "toolbar-btn js-md has-tooltip hidden-xs", + tabindex: -1, + data: data, + title: options[:title], + aria: { label: options[:title] } do + icon(options[:icon]) + end + end end diff --git a/app/mailers/emails/members.rb b/app/mailers/emails/members.rb index 6dde2e9847d..45311690293 100644 --- a/app/mailers/emails/members.rb +++ b/app/mailers/emails/members.rb @@ -12,6 +12,11 @@ module Emails @member_id = member_id admins = member_source.members.owners_and_masters.includes(:user).pluck(:notification_email) + # A project in a group can have no explicit owners/masters, in that case + # we fallbacks to the group's owners/masters. + if admins.empty? && member_source.respond_to?(:group) && member_source.group + admins = member_source.group.members.owners_and_masters.includes(:user).pluck(:notification_email) + end mail(to: admins, subject: subject("Request to join the #{member_source.human_name} #{member_source.model_name.singular}")) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index d618c84e983..2b0bec33131 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -300,18 +300,12 @@ module Ci project.valid_runners_token? token end - def can_be_served?(runner) - return false unless has_tags? || runner.run_untagged? - - (tag_list - runner.tag_list).empty? - end - def has_tags? tag_list.any? end def any_runners_online? - project.any_runners? { |runner| runner.active? && runner.online? && can_be_served?(runner) } + project.any_runners? { |runner| runner.active? && runner.online? && runner.can_pick?(self) } end def stuck? diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 5b264ecffc5..ca5a685dd11 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -37,22 +37,22 @@ module Ci end def git_author_name - commit_data.author_name if commit_data + commit.try(:author_name) end def git_author_email - commit_data.author_email if commit_data + commit.try(:author_email) end def git_commit_message - commit_data.message if commit_data + commit.try(:message) end def short_sha Ci::Pipeline.truncate_sha(sha) end - def commit_data + def commit @commit ||= project.commit(sha) rescue nil diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index adb65292208..b64ec79ec2b 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -4,7 +4,7 @@ module Ci LAST_CONTACT_TIME = 5.minutes.ago AVAILABLE_SCOPES = %w[specific shared active paused online] - FORM_EDITABLE = %i[description tag_list active run_untagged] + FORM_EDITABLE = %i[description tag_list active run_untagged locked] has_many :builds, class_name: 'Ci::Build' has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject' @@ -26,6 +26,13 @@ module Ci .where("ci_runner_projects.gl_project_id = :project_id OR ci_runners.is_shared = true", project_id: project_id) end + scope :assignable_for, ->(project) do + # FIXME: That `to_sql` is needed to workaround a weird Rails bug. + # Without that, placeholders would miss one and couldn't match. + where(locked: false). + where.not("id IN (#{project.runners.select(:id).to_sql})").specific + end + validate :tag_constraints acts_as_taggable @@ -56,7 +63,7 @@ module Ci def assign_to(project, current_user = nil) self.is_shared = false if shared? self.save - project.runner_projects.create!(runner_id: self.id) + project.runner_projects.create(runner_id: self.id) end def display_name @@ -91,6 +98,10 @@ module Ci !shared? end + def can_pick?(build) + assignable_for?(build.project) && accepting_tags?(build) + end + def only_for?(project) projects == [project] end @@ -111,5 +122,13 @@ module Ci 'can not be empty when runner is not allowed to pick untagged jobs') end end + + def assignable_for?(project) + !locked? || projects.exists?(id: project.id) + end + + def accepting_tags?(build) + (run_untagged? || build.has_tags?) && (build.tag_list - tag_list).empty? + end end end diff --git a/app/models/commit.rb b/app/models/commit.rb index d69d518fadd..174ccbaea6c 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -271,6 +271,32 @@ class Commit merged_merge_request ? 'merge request' : 'commit' end + # Get the URI type of the given path + # + # Used to build URLs to files in the repository in GFM. + # + # path - String path to check + # + # Examples: + # + # uri_type('doc/README.md') # => :blob + # uri_type('doc/logo.png') # => :raw + # uri_type('doc/api') # => :tree + # uri_type('not/found') # => :nil + # + # Returns a symbol + def uri_type(path) + entry = @raw.tree.path(path) + if entry[:type] == :blob + blob = Gitlab::Git::Blob.new(name: entry[:name]) + blob.image? ? :raw : :blob + else + entry[:type] + end + rescue Rugged::TreeError + nil + end + private def repo_changes diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index ab13db4b297..e437e3417a8 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -8,6 +8,8 @@ class CommitStatus < ActiveRecord::Base belongs_to :pipeline, class_name: 'Ci::Pipeline', foreign_key: :commit_id, touch: true belongs_to :user + delegate :commit, to: :pipeline + validates :pipeline, presence: true, unless: :importing? validates_presence_of :name diff --git a/app/models/concerns/participable.rb b/app/models/concerns/participable.rb index 9056722f45e..9822844357d 100644 --- a/app/models/concerns/participable.rb +++ b/app/models/concerns/participable.rb @@ -53,6 +53,16 @@ module Participable # # Returns an Array of User instances. def participants(current_user = nil) + @participants ||= Hash.new do |hash, user| + hash[user] = raw_participants(user) + end + + @participants[current_user] + end + + private + + def raw_participants(current_user = nil) current_user ||= author ext = Gitlab::ReferenceExtractor.new(project, current_user) participants = Set.new diff --git a/app/models/member.rb b/app/models/member.rb index 4ee3f1bb5c2..c74a16367db 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -48,7 +48,6 @@ class Member < ActiveRecord::Base after_create :post_create_hook, unless: [:pending?, :importing?] after_update :post_update_hook, unless: [:pending?, :importing?] after_destroy :post_destroy_hook, unless: :pending? - after_destroy :post_decline_request, if: :request? delegate :name, :username, :email, to: :user, prefix: true @@ -188,7 +187,7 @@ class Member < ActiveRecord::Base end def send_request - # override in subclass + notification_service.new_access_request(self) end def post_create_hook @@ -215,10 +214,6 @@ class Member < ActiveRecord::Base post_create_hook end - def post_decline_request - # override in subclass - end - def system_hook_service SystemHooksService.new end diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb index 363db877968..2f13d339c89 100644 --- a/app/models/members/group_member.rb +++ b/app/models/members/group_member.rb @@ -33,12 +33,6 @@ class GroupMember < Member super end - def send_request - notification_service.new_group_access_request(self) - - super - end - def post_create_hook notification_service.new_group_member(self) @@ -64,10 +58,4 @@ class GroupMember < Member super end - - def post_decline_request - notification_service.decline_group_access_request(self) - - super - end end diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb index 250ee04fd1d..e9d3a82ba15 100644 --- a/app/models/members/project_member.rb +++ b/app/models/members/project_member.rb @@ -111,12 +111,6 @@ class ProjectMember < Member super end - def send_request - notification_service.new_project_access_request(self) - - super - end - def post_create_hook unless owner? event_service.join_project(self.project, self.user) @@ -152,12 +146,6 @@ class ProjectMember < Member super end - def post_decline_request - notification_service.decline_project_access_request(self) - - super - end - def event_service EventCreateService.new end diff --git a/app/models/repository.rb b/app/models/repository.rb index bbd7682d8e7..221c87164ca 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -191,8 +191,12 @@ class Repository end end + def ref_names + branch_names + tag_names + end + def branch_names - cache.fetch(:branch_names) { branches.map(&:name) } + @branch_names ||= cache.fetch(:branch_names) { branches.map(&:name) } end def branch_exists?(branch_name) @@ -267,6 +271,7 @@ class Repository def expire_branches_cache cache.expire(:branch_names) + @branch_names = nil @local_branches = nil end @@ -332,10 +337,6 @@ class Repository @lookup_cache ||= {} end - def expire_branch_names - cache.expire(:branch_names) - end - def expire_avatar_cache(branch_name = nil, revision = nil) # Avatars are pulled from the default branch, thus if somebody pushes to a # different branch there's no need to expire anything. diff --git a/app/models/user.rb b/app/models/user.rb index 2e458329cb9..876ccc69d8d 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -487,9 +487,8 @@ class User < ActiveRecord::Base events.recent.find do |event| project = Project.find_by_id(event.project_id) next unless project - repo = project.repository - if repo.branch_names.include?(event.branch_name) + if project.repository.branch_exists?(event.branch_name) merge_requests = MergeRequest.where("created_at >= ?", event.created_at). where(source_project_id: project.id, source_branch: event.branch_name) diff --git a/app/services/ci/register_build_service.rb b/app/services/ci/register_build_service.rb index f0ed09a629a..9a187f5d694 100644 --- a/app/services/ci/register_build_service.rb +++ b/app/services/ci/register_build_service.rb @@ -21,7 +21,7 @@ module Ci end build = builds.find do |build| - build.can_be_served?(current_runner) + current_runner.can_pick?(build) end if build diff --git a/app/services/members/destroy_service.rb b/app/services/members/destroy_service.rb new file mode 100644 index 00000000000..15358f80208 --- /dev/null +++ b/app/services/members/destroy_service.rb @@ -0,0 +1,21 @@ +module Members + class DestroyService < BaseService + attr_accessor :member, :current_user + + def initialize(member, user) + @member, @current_user = member, user + end + + def execute + unless member && can?(current_user, "destroy_#{member.type.underscore}".to_sym, member) + raise Gitlab::Access::AccessDeniedError + end + + member.destroy + + if member.request? && member.user != current_user + notification_service.decline_access_request(member) + end + end + end +end diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb index 1b48899bb0a..7fe57747265 100644 --- a/app/services/merge_requests/build_service.rb +++ b/app/services/merge_requests/build_service.rb @@ -83,7 +83,7 @@ module MergeRequests closes_issue = "Closes ##{iid}" if merge_request.description.present? - merge_request.description << closes_issue.prepend("\n") + merge_request.description += closes_issue.prepend("\n") else merge_request.description = closes_issue end diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 19832a19b2b..590350a11e5 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -181,15 +181,16 @@ class NotificationService end end - # Project access request - def new_project_access_request(project_member) - mailer.member_access_requested_email(project_member.real_source_type, project_member.id).deliver_later + # Members + def new_access_request(member) + mailer.member_access_requested_email(member.real_source_type, member.id).deliver_later end - def decline_project_access_request(project_member) - mailer.member_access_denied_email(project_member.real_source_type, project_member.project.id, project_member.user.id).deliver_later + def decline_access_request(member) + mailer.member_access_denied_email(member.real_source_type, member.source_id, member.user_id).deliver_later end + # Project invite def invite_project_member(project_member, token) mailer.member_invited_email(project_member.real_source_type, project_member.id, token).deliver_later end @@ -216,15 +217,7 @@ class NotificationService mailer.member_access_granted_email(project_member.real_source_type, project_member.id).deliver_later end - # Group access request - def new_group_access_request(group_member) - mailer.member_access_requested_email(group_member.real_source_type, group_member.id).deliver_later - end - - def decline_group_access_request(group_member) - mailer.member_access_denied_email(group_member.real_source_type, group_member.group.id, group_member.user.id).deliver_later - end - + # Group invite def invite_group_member(group_member, token) mailer.member_invited_email(group_member.real_source_type, group_member.id, token).deliver_later end diff --git a/app/views/admin/appearances/_form.html.haml b/app/views/admin/appearances/_form.html.haml index d88f3ad314d..dc083e50178 100644 --- a/app/views/admin/appearances/_form.html.haml +++ b/app/views/admin/appearances/_form.html.haml @@ -46,7 +46,7 @@ Maximum file size is 1MB. Pages are optimized for a 72x72 px header logo .form-actions - = f.submit 'Save', class: 'btn btn-save' + = f.submit 'Save', class: 'btn btn-save append-right-10' - if @appearance.persisted? = link_to 'Preview last save', preview_admin_appearances_path, class: 'btn', target: '_blank' diff --git a/app/views/admin/appearances/preview.html.haml b/app/views/admin/appearances/preview.html.haml index dd4a64e80bc..6c51639b840 100644 --- a/app/views/admin/appearances/preview.html.haml +++ b/app/views/admin/appearances/preview.html.haml @@ -1,29 +1,9 @@ - page_title "Preview | Appearance" -%h3.page-title - Appearance settings - Preview -%hr +.login-box + .login-heading + %h3 Existing user? Sign in + %form + = text_field_tag :login, nil, class: "form-control top", placeholder: "Username or Email" + = password_field_tag :password, nil, class: "form-control bottom", placeholder: "Password" + = button_tag "Sign in", class: "btn-create btn" -.ui-box - .title - Sign-in page - %div - .login-page - .container - .content - .login-title - %h1= brand_title - %hr - .container - .content - .row - .col-sm-7 - .brand-image - = brand_image - .brand_text - = brand_text - .col-sm-4 - .login-box - %h3.page-title Sign in - = text_field_tag :login, nil, class: "form-control top", placeholder: "Username or Email" - = password_field_tag :password, nil, class: "form-control bottom", placeholder: "Password" - = button_tag "Sign in", class: "btn-create btn" diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml index 5b8a0262ea0..50770465f07 100644 --- a/app/views/admin/groups/show.html.haml +++ b/app/views/admin/groups/show.html.haml @@ -88,28 +88,17 @@ = select_tag :access_level, options_for_select(GroupMember.access_level_roles), class: "project-access-select select2" %hr = button_tag 'Add users to group', class: "btn btn-create" + + = render 'shared/members/requests', membership_source: @group, members: @members.request + .panel.panel-default .panel-heading - %h3.panel-title - Members - %span.badge - #{@group.group_members.count} - %ul.well-list.group-users-list - - @members.each do |member| - - user = member.user - %li{class: dom_class(member), id: (dom_id(user) if user)} - .list-item-name - - if user - %strong - = link_to user.name, admin_user_path(user) - - else - %strong - = member.invite_email - (invited) - %span.pull-right.light - = member.human_access - - if can?(current_user, :destroy_group_member, member) - = link_to group_group_member_path(@group, member), data: { confirm: remove_member_message(member) }, method: :delete, remote: true, class: "btn-xs btn btn-remove", title: 'Remove user from group' do - %i.fa.fa-minus.fa-inverse + %strong= @group.name + group members + %span.badge= @group.members.non_request.size + .pull-right + = link_to icon('pencil-square-o', text: 'Manage Access'), polymorphic_url([@group, :members]), class: "btn btn-xs" + %ul.well-list.group-users-list.content-list + = render partial: 'shared/members/member', collection: @members.non_request, as: :member, locals: { show_controls: false } .panel-footer - = paginate @members, param_name: 'members_page', theme: 'gitlab' + = paginate @members.non_request, param_name: 'members_page', theme: 'gitlab' diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index 9e55a562e18..461d588415d 100644 --- a/app/views/admin/projects/show.html.haml +++ b/app/views/admin/projects/show.html.haml @@ -135,44 +135,27 @@ - if @group .panel.panel-default .panel-heading - %strong #{@group.name} - group members (#{@group.group_members.count}) + %strong= @group.name + group members + %span.badge= @group_members.non_request.size .pull-right = link_to admin_group_path(@group), class: 'btn btn-xs' do - %i.fa.fa-pencil-square-o - %ul.well-list - - @group_members.each do |member| - = render 'shared/members/member', member: member, show_controls: false + = icon('pencil-square-o', text: 'Manage Access') + %ul.well-list.content-list + = render partial: 'shared/members/member', collection: @group_members.non_request, as: :member, locals: { show_controls: false } .panel-footer - = paginate @group_members, param_name: 'group_members_page', theme: 'gitlab' + = paginate @group_members.non_request, param_name: 'group_members_page', theme: 'gitlab' + + = render 'shared/members/requests', membership_source: @project, members: @project_members.request .panel.panel-default .panel-heading - Project members - %small - (#{@project.users.count}) + %strong= @project.name + project members + %span.badge= @project.users.size .pull-right - = link_to namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-xs" do - %i.fa.fa-pencil-square-o - Manage Access - %ul.well-list.project_members - - @project_members.each do |project_member| - - user = project_member.user - %li.project_member - .list-item-name - - if user - %strong - = link_to user.name, admin_user_path(user) - - else - %strong - = project_member.invite_email - (invited) - .pull-right - - if project_member.owner? - %span.light Owner - - else - %span.light= project_member.human_access - = link_to namespace_project_project_member_path(@project.namespace, @project, project_member), data: { confirm: remove_member_message(project_member)}, method: :delete, remote: true, class: "btn btn-sm btn-remove" do - %i.fa.fa-times + = link_to icon('pencil-square-o', text: 'Manage Access'), polymorphic_url([@project, :members]), class: "btn btn-xs" + %ul.well-list.project_members.content-list + = render partial: 'shared/members/member', collection: @project_members.non_request, as: :member, locals: { show_controls: false } .panel-footer - = paginate @project_members, param_name: 'project_members_page', theme: 'gitlab' + = paginate @project_members.non_request, param_name: 'project_members_page', theme: 'gitlab' diff --git a/app/views/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml index e049b40bfab..61abfc6ecbe 100644 --- a/app/views/admin/runners/show.html.haml +++ b/app/views/admin/runners/show.html.haml @@ -28,7 +28,7 @@ .col-md-6 %h4 Restrict projects for this runner - if @runner.projects.any? - %table.table + %table.table.assigned-projects %thead %tr %th Assigned projects @@ -44,7 +44,7 @@ .pull-right = link_to 'Disable', [:admin, project.namespace.becomes(Namespace), project, runner_project], method: :delete, class: 'btn btn-danger btn-xs' - %table.table + %table.table.unassigned-projects %thead %tr %th Project diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml index a36531e095a..d6acade84f1 100644 --- a/app/views/groups/group_members/index.html.haml +++ b/app/views/groups/group_members/index.html.haml @@ -17,8 +17,7 @@ .panel-heading %strong #{@group.name} group members - %small - (#{@members.total_count}) + %span.badge= @members.non_request.size .controls = form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form' do .form-group diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml index 01648047ce2..8cc0b59edeb 100644 --- a/app/views/help/_shortcuts.html.haml +++ b/app/views/help/_shortcuts.html.haml @@ -28,8 +28,12 @@ .key ⌘ shift p - else .key ctrl shift p - %td Toggle Markdown preview + %tr + %td.shortcut + .key + %i.fa.fa-arrow-up + %td Edit last comment (when focused on an empty textarea) %tbody %tr %th diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index 199ab3c38c3..2234bf79c87 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -13,7 +13,7 @@ = image_tag avatar_icon(current_user, 60), alt: 'Profile', class: 'avatar avatar s36' .username = current_user.username - = link_to '#', class: "nav-header-btn text-center pin-nav-btn #{'is-active' if pinned_nav?} js-nav-pin", title: 'Pin/Unpin navigation' do + = link_to '#', class: "nav-header-btn text-center pin-nav-btn has-tooltip #{'is-active' if pinned_nav?} js-nav-pin", title: pinned_nav? ? "Unpin navigation" : "Pin Navigation", data: {placement: 'right', container: 'body'} do %span.sr-only Toggle navigation pinning = icon('thumb-tack') - if defined?(nav) && nav diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml index 4722c9d9353..66e5ec1ad1a 100644 --- a/app/views/layouts/nav/_admin.html.haml +++ b/app/views/layouts/nav/_admin.html.haml @@ -2,7 +2,8 @@ = render 'layouts/nav/admin_settings' %ul.nav-links.scrolling-tabs - .fade-left + %li.fade-left + = icon('arrow-left') = nav_link(controller: %w(dashboard admin projects users groups builds runners), html_options: {class: 'home'}) do = link_to admin_root_path, title: 'Overview', class: 'shortcuts-tree' do %span @@ -36,4 +37,5 @@ = link_to admin_spam_logs_path, title: "Spam Logs" do %span Spam Logs - .fade-right + %li.fade-right + = icon('arrow-right') diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml index 66361a644dd..f7aa9fab7cf 100644 --- a/app/views/layouts/nav/_group.html.haml +++ b/app/views/layouts/nav/_group.html.haml @@ -2,7 +2,8 @@ = render 'layouts/nav/group_settings' %ul.nav-links.scrolling-tabs - .fade-left + %li.fade-left + = icon('arrow-left') = nav_link(path: 'groups#show', html_options: {class: 'home'}) do = link_to group_path(@group), title: 'Home' do %span @@ -31,4 +32,5 @@ = link_to group_group_members_path(@group), title: 'Members' do %span Members - .fade-right + %li.fade-right + = icon('arrow-right') diff --git a/app/views/layouts/nav/_group_settings.html.haml b/app/views/layouts/nav/_group_settings.html.haml index dac46648b9f..3a24b09ab7e 100644 --- a/app/views/layouts/nav/_group_settings.html.haml +++ b/app/views/layouts/nav/_group_settings.html.haml @@ -1,16 +1,22 @@ - if current_user - - if access = @group.users.find_by(id: current_user.id) - .controls - .dropdown.group-settings-dropdown - %a.dropdown-new.btn.btn-default#group-settings-button{href: '#', 'data-toggle' => 'dropdown'} - = icon('cog') - = icon('caret-down') - %ul.dropdown-menu.dropdown-menu-align-right - - if can?(current_user, :admin_group, @group) - = nav_link(path: 'groups#projects') do - = link_to projects_group_path(@group), title: 'Projects' do - Projects - %li.divider - %li - = link_to edit_group_path(@group) do - Edit Group + - can_edit = can?(current_user, :admin_group, @group) + - member = @group.members.non_request.find_by(user_id: current_user.id) + - can_leave = member && can?(current_user, :destroy_group_member, member) + + .controls + .dropdown.group-settings-dropdown + %a.dropdown-new.btn.btn-default#group-settings-button{href: '#', 'data-toggle' => 'dropdown'} + = icon('cog') + = icon('caret-down') + %ul.dropdown-menu.dropdown-menu-align-right + = nav_link(path: 'groups#projects') do + = link_to 'Projects', projects_group_path(@group), title: 'Projects' + %li.divider + - if can_edit + %li + = link_to 'Edit Group', edit_group_path(@group) + - if can_leave + %li + = link_to polymorphic_path([:leave, @group, :members]), + data: { confirm: leave_confirmation_message(@group) }, method: :delete, title: 'Leave group' do + Leave Group diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml index bb6f14a6225..44ea939b7e4 100644 --- a/app/views/layouts/nav/_profile.html.haml +++ b/app/views/layouts/nav/_profile.html.haml @@ -1,5 +1,6 @@ %ul.nav-links.scrolling-tabs - .fade-left + %li.fade-left + = icon('arrow-left') = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do = link_to profile_path, title: 'Profile Settings' do %span @@ -43,4 +44,5 @@ = link_to audit_log_profile_path, title: 'Audit Log' do %span Audit Log - .fade-right + %li.fade-right + = icon('arrow-right') diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index 39ea4920ccc..27e840df503 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -5,19 +5,20 @@ = icon('cog') = icon('caret-down') %ul.dropdown-menu.dropdown-menu-align-right - - is_project_member = @project.users.exists?(current_user.id) - - access = @project.team.max_member_access(current_user.id) - can_edit = can?(current_user, :admin_project, @project) + -# We don't use @project.team.find_member because it searches for group members too... + - member = @project.members.non_request.find_by(user_id: current_user.id) + - can_leave = member && can?(current_user, :destroy_project_member, member) - = render 'layouts/nav/project_settings', access: access, can_edit: can_edit + = render 'layouts/nav/project_settings', can_edit: can_edit - - if can_edit || is_project_member + - if can_edit || can_leave %li.divider - if can_edit %li = link_to edit_project_path(@project) do Edit Project - - if is_project_member + - if can_leave %li = link_to polymorphic_path([:leave, @project, :members]), data: { confirm: leave_confirmation_message(@project) }, method: :delete, title: 'Leave project' do @@ -25,7 +26,8 @@ %div{ class: nav_control_class } %ul.nav-links.scrolling-tabs - .fade-left + %li.fade-left + = icon('arrow-left') = nav_link(path: 'projects#show', html_options: {class: 'home'}) do = link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do %span @@ -38,9 +40,9 @@ - if project_nav_tab? :files = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file commit commits compare repositories tags branches releases network)) do - = link_to project_files_path(@project), title: 'Code', class: 'shortcuts-tree' do + = link_to project_files_path(@project), title: 'Repository', class: 'shortcuts-tree' do %span - Code + Repository - if project_nav_tab? :pipelines = nav_link(controller: [:pipelines, :builds, :environments]) do @@ -109,4 +111,5 @@ %li.hidden = link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits' do Commits - .fade-right + %li.fade-right + = icon('arrow-right') diff --git a/app/views/layouts/nav/_project_settings.html.haml b/app/views/layouts/nav/_project_settings.html.haml index 13d32bd1354..51a54b4f262 100644 --- a/app/views/layouts/nav/_project_settings.html.haml +++ b/app/views/layouts/nav/_project_settings.html.haml @@ -3,7 +3,7 @@ = link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: 'team-tab tab' do %span Members -- if access && can_edit +- if can_edit - if @project.allowed_to_share_with_group? = nav_link(controller: :group_links) do = link_to namespace_project_group_links_path(@project.namespace, @project), title: "Groups" do diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml index 5afd83a522e..f77738f97f5 100644 --- a/app/views/profiles/notifications/show.html.haml +++ b/app/views/profiles/notifications/show.html.haml @@ -28,7 +28,7 @@ = label_tag :global_notification_level, "Global notification level", class: "label-light" %br .clearfix - .form-group.pull-left + .form-group.pull-left.global-notification-setting = render 'shared/notifications/button', notification_setting: @global_notification_setting, left_align: true .clearfix diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml index 28a28282fd3..ca6714ef42b 100644 --- a/app/views/projects/_md_preview.html.haml +++ b/app/views/projects/_md_preview.html.haml @@ -14,8 +14,17 @@ %span This is a confidential issue. Your comment will not be visible to the public. %li.pull-right - %button.zen-control.zen-control-full.js-zen-enter{ type: 'button', tabindex: -1 } - Go full screen + .toolbar-group + = markdown_toolbar_button({icon: "bold fw", data: { "md-tag" => "**" }, title: "Add bold text" }) + = markdown_toolbar_button({icon: "italic fw", data: { "md-tag" => "*" }, title: "Add italic text" }) + = markdown_toolbar_button({icon: "quote-right fw", data: { "md-tag" => "> ", "md-prepend" => true }, title: "Insert a quote" }) + = markdown_toolbar_button({icon: "code fw", data: { "md-tag" => "`" }, title: "Insert code" }) + = markdown_toolbar_button({icon: "list-ul fw", data: { "md-tag" => "* ", "md-prepend" => true }, title: "Add a bullet list" }) + = markdown_toolbar_button({icon: "list-ol fw", data: { "md-tag" => "1. ", "md-prepend" => true }, title: "Add a numbered list" }) + = markdown_toolbar_button({icon: "check-square-o fw", data: { "md-tag" => "* [ ] ", "md-prepend" => true }, title: "Add a task list" }) + .toolbar-group + %button.toolbar-btn.js-zen-enter.has-tooltip.hidden-xs{ type: "button", tabindex: -1, aria: { label: "Go full screen" }, title: "Go full screen", data: { container: "body" } } + =icon("arrows-alt fw") .md-write-holder = yield @@ -24,7 +33,7 @@ - if defined?(referenced_users) && referenced_users %div.referenced-users.hide %span - = icon('exclamation-triangle') + = icon("exclamation-triangle") You are about to add %strong %span.js-referenced-users-count 0 diff --git a/app/views/projects/badges/index.html.haml b/app/views/projects/badges/index.html.haml index ee63bc55a30..ac80951dd4f 100644 --- a/app/views/projects/badges/index.html.haml +++ b/app/views/projects/badges/index.html.haml @@ -7,7 +7,7 @@ %b Builds badge · = @build_badge.to_html .pull-right - = render 'shared/ref_switcher', destination: 'badges' + = render 'shared/ref_switcher', destination: 'badges', align_right: true .panel-body .row .col-md-2.text-center diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml index ae89637df60..29c7d45074a 100644 --- a/app/views/projects/blob/_editor.html.haml +++ b/app/views/projects/blob/_editor.html.haml @@ -17,6 +17,8 @@ = dropdown_tag("Choose a License template", options: { toggle_class: 'js-license-selector', title: "Choose a license", filter: true, placeholder: "Filter", data: { data: licenses_for_select, project: @project.name, fullname: @project.namespace.human_name } } ) .gitignore-selector.js-gitignore-selector-wrap.hidden = dropdown_tag("Choose a .gitignore template", options: { toggle_class: 'js-gitignore-selector', title: "Choose a template", filter: true, placeholder: "Filter", data: { data: gitignore_names } } ) + .gitlab-ci-yml-selector.js-gitlab-ci-yml-selector-wrap.hidden + = dropdown_tag("Choose a GitLab CI Yaml template", options: { toggle_class: 'js-gitlab-ci-yml-selector', title: "Choose a template", filter: true, placeholder: "Filter", data: { data: gitlab_ci_ymls } } ) .encoding-selector = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2' diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml index b8d8758fd2b..e38d1ff5ff0 100644 --- a/app/views/projects/ci/pipelines/_pipeline.html.haml +++ b/app/views/projects/ci/pipelines/_pipeline.html.haml @@ -24,8 +24,8 @@ %span.label.label-warning stuck %p.commit-title - - if commit_data = pipeline.commit_data - = link_to_gfm truncate(commit_data.title, length: 60), namespace_project_commit_path(@project.namespace, @project, commit_data.id), class: "commit-row-message" + - if commit = pipeline.commit + = link_to_gfm truncate(commit.title, length: 60), namespace_project_commit_path(@project.namespace, @project, commit.id), class: "commit-row-message" - else Cant find HEAD commit for this branch diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index a959b34a539..929496f81d8 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -10,29 +10,30 @@ = cache(cache_key) do %li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" } = commit_author_avatar(commit, size: 36) - .commit-row-title - %span.item-title - = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message" - %span.commit-row-message.visible-xs-inline - · - = commit.short_id - - if commit.status - = render_commit_status(commit, cssclass: 'visible-xs-inline') - - if commit.description? - %a.text-expander.hidden-xs.js-toggle-button ... + .commit-info-block + .commit-row-title + %span.item-title + = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message" + %span.commit-row-message.visible-xs-inline + · + = commit.short_id + - if commit.status + = render_commit_status(commit, cssclass: 'visible-xs-inline') + - if commit.description? + %a.text-expander.hidden-xs.js-toggle-button ... - .commit-actions.hidden-xs - - if commit.status - = render_commit_status(commit, cssclass: 'btn btn-transparent') - = clipboard_button_with_class({ clipboard_text: commit.id }, css_class: 'btn-transparent') - = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-short-id btn btn-transparent" - = link_to_browse_code(project, commit) + .commit-actions.hidden-xs + - if commit.status + = render_commit_status(commit, cssclass: 'btn btn-transparent') + = clipboard_button_with_class({ clipboard_text: commit.id }, css_class: 'btn-transparent') + = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-short-id btn btn-transparent" + = link_to_browse_code(project, commit) - - if commit.description? - %pre.commit-row-description.js-toggle-content - = preserve(markdown(escape_once(commit.description), pipeline: :single_line, author: commit.author)) + - if commit.description? + %pre.commit-row-description.js-toggle-content + = preserve(markdown(escape_once(commit.description), pipeline: :single_line, author: commit.author)) - .commit-row-info - = commit_author_link(commit, avatar: false, size: 24) - authored - #{time_ago_with_tooltip(commit.committed_date)} + .commit-row-info + = commit_author_link(commit, avatar: false, size: 24) + authored + #{time_ago_with_tooltip(commit.committed_date)} diff --git a/app/views/projects/commits/_head.html.haml b/app/views/projects/commits/_head.html.haml index c8aa849c217..54dab4bff07 100644 --- a/app/views/projects/commits/_head.html.haml +++ b/app/views/projects/commits/_head.html.haml @@ -1,7 +1,8 @@ .scrolling-tabs-container .nav-links.sub-nav.scrolling-tabs %ul{ class: (container_class) } - .fade-left + %li.fade-left + = icon('arrow-left') = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file)) do = link_to project_files_path(@project) do Files @@ -25,4 +26,5 @@ = nav_link(controller: [:tags, :releases]) do = link_to namespace_project_tags_path(@project.namespace, @project) do Tags - .fade-right + %li.fade-right + = icon('arrow-right') diff --git a/app/views/projects/compare/index.html.haml b/app/views/projects/compare/index.html.haml index c322942aeba..b22285c11e0 100644 --- a/app/views/projects/compare/index.html.haml +++ b/app/views/projects/compare/index.html.haml @@ -3,7 +3,7 @@ = render "projects/commits/head" %div{ class: (container_class) } - .row-content-block.second-block.content-component-block + .sub-header-block Compare branches, tags or commit ranges. %br Fill input field with commit id like diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml index cdc34f51d6d..f4ec7b767f6 100644 --- a/app/views/projects/compare/show.html.haml +++ b/app/views/projects/compare/show.html.haml @@ -1,24 +1,24 @@ +- @no_container = true - page_title "#{params[:from]}...#{params[:to]}" = render "projects/commits/head" +%div{ class: (container_class) } + .sub-header-block.no-bottom-space + = render "form" -.row-content-block - = render "form" - -- if @commits.present? - .prepend-top-default + - if @commits.present? = render "projects/commits/commit_list" = render "projects/diffs/diffs", diffs: @diffs, project: @project, diff_refs: @diff_refs -- else - .light-well.prepend-top-default - .center - %h4 - There isn't anything to compare. - %p.slead - - if params[:to] == params[:from] - %span.label-branch #{params[:from]} - and - %span.label-branch #{params[:to]} - are the same. - - else - You'll need to use different branch names to get a valid comparison. + - else + .light-well + .center + %h4 + There isn't anything to compare. + %p.slead + - if params[:to] == params[:from] + %span.label-branch #{params[:from]} + and + %span.label-branch #{params[:to]} + are the same. + - else + You'll need to use different branch names to get a valid comparison. diff --git a/app/views/projects/container_registry/_tag.html.haml b/app/views/projects/container_registry/_tag.html.haml index f35faa6afb5..10822b6184c 100644 --- a/app/views/projects/container_registry/_tag.html.haml +++ b/app/views/projects/container_registry/_tag.html.haml @@ -3,9 +3,9 @@ = escape_once(tag.name) = clipboard_button(clipboard_text: "docker pull #{tag.path}") %td - - if layer = tag.layers.first - %span.has-tooltip{ title: "#{layer.revision}" } - = layer.short_revision + - if tag.revision + %span.has-tooltip{ title: "#{tag.revision}" } + = tag.short_revision - else \- %td diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml index ae9e77e7d89..a03f117291f 100644 --- a/app/views/projects/environments/index.html.haml +++ b/app/views/projects/environments/index.html.haml @@ -3,16 +3,24 @@ = render "projects/pipelines/head" %div{ class: (container_class) } - - if can?(current_user, :create_environment, @project) + - if can?(current_user, :create_environment, @project) && !@environments.blank? .top-area .nav-controls = link_to new_namespace_project_environment_path(@project.namespace, @project), class: 'btn btn-create' do New environment - if @environments.blank? - %ul.content-list.environments - %li.nothing-here-block - No environments to show + .blank-state.blank-state-no-icon + %h2.blank-state-title + You don't have any environments right now. + %p.blank-state-text + Environments are places where code gets deployed, such as staging or production. + %br + = succeed "." do + = link_to "Read more about environments", help_page_path("ci", "environments") + - if can?(current_user, :create_environment, @project) + = link_to new_namespace_project_environment_path(@project.namespace, @project), class: 'btn btn-create' do + New environment - else .table-holder %table.table.environments diff --git a/app/views/projects/environments/new.html.haml b/app/views/projects/environments/new.html.haml index 54465828ba9..da325efecd2 100644 --- a/app/views/projects/environments/new.html.haml +++ b/app/views/projects/environments/new.html.haml @@ -4,6 +4,9 @@ .col-lg-3 %h4.prepend-top-0 New Environment - %p Environments allow you to track deployments of your application + %p + Environments allow you to track deployments of your application + = succeed "." do + = link_to "Read more about environments", help_page_path("ci", "environments") = render 'form' diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml index 069b77b5adf..4c15e2759d6 100644 --- a/app/views/projects/environments/show.html.haml +++ b/app/views/projects/environments/show.html.haml @@ -13,10 +13,14 @@ = link_to 'Destroy', namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to delete this environment?' }, class: 'btn btn-danger', method: :delete - if @deployments.blank? - %ul.content-list.environments - %li.nothing-here-block - No deployments for - %strong= @environment.name + .blank-state.blank-state-no-icon + %h2.blank-state-title + You don't have any deployments right now. + %p.blank-state-text + Define environments in the deploy stage(s) in + %code .gitlab-ci.yml + to track deployments here. + = link_to "Read more", help_page_path("ci", "environments"), class: "btn btn-success" - else .table-holder %table.table.environments diff --git a/app/views/projects/forks/index.html.haml b/app/views/projects/forks/index.html.haml index 4bcf2d9d533..dbe9ddfde2f 100644 --- a/app/views/projects/forks/index.html.haml +++ b/app/views/projects/forks/index.html.haml @@ -40,9 +40,3 @@ = render 'projects', projects: @forks - -- if @private_forks_count > 0 - .private-forks-notice - = icon('lock fw', base: 'circle', class: 'fa-lg private-fork-icon') - %strong= pluralize(@private_forks_count, 'private fork') - %span you have no access to. diff --git a/app/views/projects/graphs/_head.html.haml b/app/views/projects/graphs/_head.html.haml index 8becaea246f..a388d9a0a61 100644 --- a/app/views/projects/graphs/_head.html.haml +++ b/app/views/projects/graphs/_head.html.haml @@ -1,12 +1,14 @@ -- page_specific_javascripts asset_path("graphs/application.js") -%ul.nav-links - = nav_link(action: :show) do - = link_to 'Contributors', namespace_project_graph_path - = nav_link(action: :commits) do - = link_to 'Commits', commits_namespace_project_graph_path - = nav_link(action: :languages) do - = link_to 'Languages', languages_namespace_project_graph_path - - if @project.builds_enabled? - = nav_link(action: :ci) do - = link_to ci_namespace_project_graph_path do - Continuous Integration +.nav-links.sub-nav + %ul{ class: (container_class) } + + - page_specific_javascripts asset_path("graphs/application.js") + = nav_link(action: :show) do + = link_to 'Contributors', namespace_project_graph_path + = nav_link(action: :commits) do + = link_to 'Commits', commits_namespace_project_graph_path + = nav_link(action: :languages) do + = link_to 'Languages', languages_namespace_project_graph_path + - if @project.builds_enabled? + = nav_link(action: :ci) do + = link_to ci_namespace_project_graph_path do + Continuous Integration diff --git a/app/views/projects/graphs/ci.html.haml b/app/views/projects/graphs/ci.html.haml index 19ccc125ea8..e695d3ae369 100644 --- a/app/views/projects/graphs/ci.html.haml +++ b/app/views/projects/graphs/ci.html.haml @@ -1,15 +1,18 @@ +- @no_container = true - page_title "Continuous Integration", "Graphs" = render 'head' -.row-content-block.append-bottom-default - .oneline - A collection of graphs for Continuous Integration -#charts.ci-charts - .row - .col-md-6 - = render 'projects/graphs/ci/overall' - .col-md-6 - = render 'projects/graphs/ci/build_times' +%div{ class: (container_class) } + .sub-header-block + .oneline + A collection of graphs for Continuous Integration - %hr - = render 'projects/graphs/ci/builds' + #charts.ci-charts + .row + .col-md-6 + = render 'projects/graphs/ci/overall' + .col-md-6 + = render 'projects/graphs/ci/build_times' + + %hr + = render 'projects/graphs/ci/builds' diff --git a/app/views/projects/graphs/commits.html.haml b/app/views/projects/graphs/commits.html.haml index d9b2fb6c065..0daffe68f6f 100644 --- a/app/views/projects/graphs/commits.html.haml +++ b/app/views/projects/graphs/commits.html.haml @@ -1,52 +1,54 @@ +- @no_container = true - page_title "Commits", "Graphs" = render 'head' -.row-content-block.append-bottom-default - .tree-ref-holder - = render 'shared/ref_switcher', destination: 'graphs_commits' - %ul.breadcrumb.repo-breadcrumb - = commits_breadcrumbs +%div{ class: (container_class) } + .sub-header-block + .tree-ref-holder + = render 'shared/ref_switcher', destination: 'graphs_commits' + %ul.breadcrumb.repo-breadcrumb + = commits_breadcrumbs -%p.lead - Commit statistics for - %strong #{@ref} - #{@commits_graph.start_date.strftime('%b %d')} - #{@commits_graph.end_date.strftime('%b %d')} + %p.lead + Commit statistics for + %strong #{@ref} + #{@commits_graph.start_date.strftime('%b %d')} - #{@commits_graph.end_date.strftime('%b %d')} -.row - .col-md-6 - %ul - %li - %p.lead - %strong #{@commits_graph.commits.size} - commits during - %strong #{@commits_graph.duration} - days - %li - %p.lead - Average - %strong #{@commits_graph.commit_per_day} - commits per day - %li - %p.lead - Contributed by - %strong #{@commits_graph.authors} - authors - .col-md-6 - %div - %p.slead - Commits per day of month - %canvas#month-chart -.row - .col-md-6 - %div - %p.slead - Commits per day hour (UTC) - %canvas#hour-chart - .col-md-6 - %div - %p.slead - Commits per weekday - %canvas#weekday-chart + .row + .col-md-6 + %ul + %li + %p.lead + %strong #{@commits_graph.commits.size} + commits during + %strong #{@commits_graph.duration} + days + %li + %p.lead + Average + %strong #{@commits_graph.commit_per_day} + commits per day + %li + %p.lead + Contributed by + %strong #{@commits_graph.authors} + authors + .col-md-6 + %div + %p.slead + Commits per day of month + %canvas#month-chart + .row + .col-md-6 + %div + %p.slead + Commits per day hour (UTC) + %canvas#hour-chart + .col-md-6 + %div + %p.slead + Commits per weekday + %canvas#weekday-chart :javascript var responsiveChart = function (selector, data) { diff --git a/app/views/projects/graphs/languages.html.haml b/app/views/projects/graphs/languages.html.haml index 249c16f4709..6d97f552a8e 100644 --- a/app/views/projects/graphs/languages.html.haml +++ b/app/views/projects/graphs/languages.html.haml @@ -1,24 +1,26 @@ +- @no_container = true - page_title "Languages", "Graphs" = render 'head' -.row-content-block.append-bottom-default - .oneline - Programming languages used in this repository +%div{ class: (container_class) } + .sub-header-block + .oneline + Programming languages used in this repository -.row - .col-md-8 - %canvas#languages-chart{ height: 400 } - .col-md-4 - %ul.bordered-list - - @languages.each do |language| - %li - %span{ style: "color: #{language[:color]}" } - = icon('circle') - - = language[:label] - .pull-right - = language[:value] - \% + .row + .col-md-8 + %canvas#languages-chart{ height: 400 } + .col-md-4 + %ul.bordered-list + - @languages.each do |language| + %li + %span{ style: "color: #{language[:color]}" } + = icon('circle') + + = language[:label] + .pull-right + = language[:value] + \% :javascript var data = #{@languages.to_json}; diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml index 33970e7b909..9f7e2a361ff 100644 --- a/app/views/projects/graphs/show.html.haml +++ b/app/views/projects/graphs/show.html.haml @@ -1,29 +1,31 @@ +- @no_container = true - page_title "Contributors", "Graphs" = render 'head' -.row-content-block.append-bottom-default - .tree-ref-holder - = render 'shared/ref_switcher', destination: 'graphs' - %ul.breadcrumb.repo-breadcrumb - = commits_breadcrumbs - -.loading-graph - .center - %h3.page-title - %i.fa.fa-spinner.fa-spin - Building repository graph. - %p.slead Please wait a moment, this page will automatically refresh when ready. - -.stat-graph.hide - .header.clearfix - %h3#date_header.page-title - %p.light - Commits to #{@ref}, excluding merge commits. Limited to 6,000 commits. - %input#brush_change{:type => "hidden"} - .graphs - #contributors-master - #contributors.clearfix - %ol.contributors-list.clearfix +%div{ class: (container_class) } + .sub-header-block + .tree-ref-holder + = render 'shared/ref_switcher', destination: 'graphs' + %ul.breadcrumb.repo-breadcrumb + = commits_breadcrumbs + + .loading-graph + .center + %h3.page-title + %i.fa.fa-spinner.fa-spin + Building repository graph. + %p.slead Please wait a moment, this page will automatically refresh when ready. + + .stat-graph.hide + .header.clearfix + %h3#date_header.page-title + %p.light + Commits to #{@ref}, excluding merge commits. Limited to 6,000 commits. + %input#brush_change{:type => "hidden"} + .graphs.row + #contributors-master + #contributors.clearfix + %ol.contributors-list.clearfix diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml index e4ab064eda8..593af319a47 100644 --- a/app/views/projects/network/show.html.haml +++ b/app/views/projects/network/show.html.haml @@ -15,5 +15,5 @@ = check_box_tag :filter_ref, 1, @options[:filter_ref] %span Begin with the selected commit - .network-graph{ data: { url: '#{escape_javascript(@url)}', commit_url: '#{escape_javascript(@commit_url)}', ref: '#{escape_javascript(@ref)}', commit_id: '#{escape_javascript(@commit.id)}' } } + .network-graph{ data: { url: @url, commit_url: @commit_url, ref: @ref, commit_id: @commit.id } } = spinner nil, true diff --git a/app/views/projects/notes/_hints.html.haml b/app/views/projects/notes/_hints.html.haml index 0b002043408..7d1cbc62e86 100644 --- a/app/views/projects/notes/_hints.html.haml +++ b/app/views/projects/notes/_hints.html.haml @@ -5,4 +5,4 @@ is supported %button.toolbar-button.markdown-selector{ type: 'button', tabindex: '-1' } = icon('file-image-o', class: 'toolbar-button-icon') - Attach a file + Attach a file
\ No newline at end of file diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index bcdbff08011..c04d291412c 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -18,9 +18,9 @@ = time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note-created-ago') .note-actions - access = note.project.team.human_max_access(note.author.id) - - if access + - if access and not note.system %span.note-role.hidden-xs= access - - if current_user + - if current_user and not note.system = link_to '#', title: 'Award Emoji', class: 'note-action-button note-emoji-button js-add-award js-note-emoji', data: { position: 'right' } do = icon('spinner spin') = icon('smile-o') diff --git a/app/views/projects/project_members/_group_members.html.haml b/app/views/projects/project_members/_group_members.html.haml index cb6136c215a..e783d8c72c5 100644 --- a/app/views/projects/project_members/_group_members.html.haml +++ b/app/views/projects/project_members/_group_members.html.haml @@ -2,8 +2,7 @@ .panel-heading %strong #{@group.name} group members - %small - (#{members.count}) + %span.badge= members.size - if can?(current_user, :admin_group_member, @group) .controls = link_to 'Manage group members', diff --git a/app/views/projects/project_members/_shared_group_members.html.haml b/app/views/projects/project_members/_shared_group_members.html.haml index 952844acefc..840b57c2e63 100644 --- a/app/views/projects/project_members/_shared_group_members.html.haml +++ b/app/views/projects/project_members/_shared_group_members.html.haml @@ -1,6 +1,7 @@ - @project_group_links.each do |group_links| - shared_group = group_links.group - - shared_group_users_count = group_links.group.group_members.count + - shared_group_members = shared_group.members.non_request + - shared_group_users_count = shared_group_members.size .panel.panel-default .panel-heading Shared with @@ -15,7 +16,7 @@ Edit group members %ul.content-list = render partial: 'shared/members/member', - collection: shared_group.group_members.order(access_level: :desc).limit(20), + collection: shared_group_members.order(access_level: :desc).limit(20), as: :member, locals: { show_controls: false, show_roles: false } - if shared_group_users_count > 20 diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml index 03207614258..b0bfdd235f7 100644 --- a/app/views/projects/project_members/_team.html.haml +++ b/app/views/projects/project_members/_team.html.haml @@ -2,8 +2,7 @@ .panel-heading %strong #{@project.name} project members - %small - (#{members.count}) + %span.badge= members.size .controls = form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form' do .form-group diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index 357ccccaf1d..a2026c41d01 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -18,7 +18,7 @@ = render 'team', members: @project_members.non_request - if @group - = render "group_members", members: @group_members + = render "group_members", members: @group_members.non_request - if @project_group_links.any? && @project.allowed_to_share_with_group? = render "shared_group_members" diff --git a/app/views/projects/runners/_form.html.haml b/app/views/projects/runners/_form.html.haml index d62f5c8f131..c45a9d4f81f 100644 --- a/app/views/projects/runners/_form.html.haml +++ b/app/views/projects/runners/_form.html.haml @@ -13,6 +13,12 @@ = f.check_box :run_untagged %span.light Indicates whether this runner can pick jobs without tags .form-group + = label :locked, 'Lock to current projects', class: 'control-label' + .col-sm-10 + .checkbox + = f.check_box :locked + %span.light When a runner is locked, it cannot be assigned to other projects + .form-group = label_tag :token, class: 'control-label' do Token .col-sm-10 diff --git a/app/views/projects/runners/_runner.html.haml b/app/views/projects/runners/_runner.html.haml index 96e2aac451f..85225857758 100644 --- a/app/views/projects/runners/_runner.html.haml +++ b/app/views/projects/runners/_runner.html.haml @@ -2,8 +2,10 @@ %h4 = runner_status_icon(runner) %span.monospace - - if @runners.include?(runner) + - if @project_runners.include?(runner) = link_to runner.short_sha, runner_path(runner) + - if runner.locked? + = icon('lock', class: 'has-tooltip', title: 'Locked to current projects') %small = link_to edit_namespace_project_runner_path(@project.namespace, @project, runner) do %i.fa.fa-edit.btn @@ -11,7 +13,7 @@ = runner.short_sha .pull-right - - if @runners.include?(runner) + - if @project_runners.include?(runner) - if runner.belongs_to_one_project? = link_to 'Remove runner', runner_path(runner), data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm' - else diff --git a/app/views/projects/runners/_specific_runners.html.haml b/app/views/projects/runners/_specific_runners.html.haml index 8ae9f0d95f7..d469dda5b81 100644 --- a/app/views/projects/runners/_specific_runners.html.haml +++ b/app/views/projects/runners/_specific_runners.html.haml @@ -17,13 +17,13 @@ Start runner! -- if @runners.any? +- if @project_runners.any? %h4.underlined-title Runners activated for this project %ul.bordered-list.activated-specific-runners - = render partial: 'runner', collection: @runners, as: :runner + = render partial: 'runner', collection: @project_runners, as: :runner -- if @specific_runners.any? +- if @assignable_runners.any? %h4.underlined-title Available specific runners %ul.bordered-list.available-specific-runners - = render partial: 'runner', collection: @specific_runners, as: :runner - = paginate @specific_runners + = render partial: 'runner', collection: @assignable_runners, as: :runner + = paginate @assignable_runners diff --git a/app/views/projects/runners/show.html.haml b/app/views/projects/runners/show.html.haml index f24e1b9144e..61b99f35d74 100644 --- a/app/views/projects/runners/show.html.haml +++ b/app/views/projects/runners/show.html.haml @@ -23,6 +23,9 @@ %td Can run untagged jobs %td= @runner.run_untagged? ? 'Yes' : 'No' %tr + %td Locked to this project + %td= @runner.locked? ? 'Yes' : 'No' + %tr %td Tags %td - @runner.tag_list.each do |tag| diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index e9ca46a74bf..15f0d85194b 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -57,6 +57,10 @@ %li.missing = link_to add_special_file_path(@project, file_name: 'CONTRIBUTING.md', commit_message: 'Add contribution guide') do Add Contribution guide + - unless @repository.gitlab_ci_yml + %li.missing + = link_to add_special_file_path(@project, file_name: '.gitlab-ci.yml') do + Set Up CI - if @repository.commit .content-block.second-block.white diff --git a/app/views/projects/wikis/_main_links.html.haml b/app/views/projects/wikis/_main_links.html.haml index 4faa547769b..4ea75dbbf0c 100644 --- a/app/views/projects/wikis/_main_links.html.haml +++ b/app/views/projects/wikis/_main_links.html.haml @@ -1,4 +1,7 @@ - if (@page && @page.persisted?) + - if can?(current_user, :create_wiki, @project) + = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do + New Page = link_to namespace_project_wiki_history_path(@project.namespace, @project, @page), class: "btn" do Page History - if can?(current_user, :create_wiki, @project) diff --git a/app/views/projects/wikis/_nav.html.haml b/app/views/projects/wikis/_nav.html.haml index 988fe024e28..f8ea479e0b1 100644 --- a/app/views/projects/wikis/_nav.html.haml +++ b/app/views/projects/wikis/_nav.html.haml @@ -1,5 +1,5 @@ -.top-area - %ul.nav-links +.nav-links.sub-nav + %ul{ class: (container_class) } = nav_link(html_options: {class: params[:id] == 'home' ? 'active' : '' }) do = link_to 'Home', namespace_project_wiki_path(@project.namespace, @project, :home) @@ -10,9 +10,4 @@ = link_to namespace_project_wikis_git_access_path(@project.namespace, @project) do Git Access - .nav-controls - - if can?(current_user, :create_wiki, @project) - = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do - New Page - -= render 'projects/wikis/new' + = render 'projects/wikis/new' diff --git a/app/views/projects/wikis/_new.html.haml b/app/views/projects/wikis/_new.html.haml index 919daf0a7b2..4f8abcdc8e1 100644 --- a/app/views/projects/wikis/_new.html.haml +++ b/app/views/projects/wikis/_new.html.haml @@ -1,14 +1,17 @@ -%div#modal-new-wiki.modal - .modal-dialog - .modal-content - .modal-header - %a.close{href: "#", "data-dismiss" => "modal"} × - %h3.page-title New Wiki Page - .modal-body - %form.new-wiki-page - .form-group - = label_tag :new_wiki_path do - %span Page slug - = text_field_tag :new_wiki_path, nil, placeholder: 'how-to-setup', class: 'form-control', required: true, :'data-wikis-path' => namespace_project_wikis_path(@project.namespace, @project), autofocus: true - .form-actions - = button_tag 'Create Page', class: 'build-new-wiki btn btn-create' +- @no_container = true + +%div{ class: (container_class) } + %div#modal-new-wiki.modal + .modal-dialog + .modal-content + .modal-header + %a.close{href: "#", "data-dismiss" => "modal"} × + %h3.page-title New Wiki Page + .modal-body + %form.new-wiki-page + .form-group + = label_tag :new_wiki_path do + %span Page slug + = text_field_tag :new_wiki_path, nil, placeholder: 'how-to-setup', class: 'form-control', required: true, :'data-wikis-path' => namespace_project_wikis_path(@project.namespace, @project), autofocus: true + .form-actions + = button_tag 'Create Page', class: 'build-new-wiki btn btn-create' diff --git a/app/views/projects/wikis/edit.html.haml b/app/views/projects/wikis/edit.html.haml index cbd69ee1a73..bf5d09d50c2 100644 --- a/app/views/projects/wikis/edit.html.haml +++ b/app/views/projects/wikis/edit.html.haml @@ -1,19 +1,24 @@ +- @no_container = true - page_title "Edit", @page.title.capitalize, "Wiki" = render 'nav' -.top-area - .nav-text.wiki-page - %strong - - if @page.persisted? - = link_to @page.title.capitalize, namespace_project_wiki_path(@project.namespace, @project, @page) - - else - = @page.title.capitalize - %span.light - · - Edit Page +%div{ class: (container_class) } + .top-area + .nav-text + %strong + - if @page.persisted? + = link_to @page.title.capitalize, namespace_project_wiki_path(@project.namespace, @project, @page) + - else + = @page.title.capitalize + %span.light + · + Edit Page - .nav-controls - = render 'main_links' + .nav-controls + - if can?(current_user, :create_wiki, @project) + = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do + New Page + = render 'main_links' -= render 'form' + = render 'form' diff --git a/app/views/projects/wikis/git_access.html.haml b/app/views/projects/wikis/git_access.html.haml index ccceab6155e..6caf7230f35 100644 --- a/app/views/projects/wikis/git_access.html.haml +++ b/app/views/projects/wikis/git_access.html.haml @@ -1,32 +1,34 @@ +- @no_container = true - page_title "Git Access", "Wiki" = render 'nav' -.row-content-block - %span.oneline - Git access for - %strong= @project_wiki.path_with_namespace +%div{ class: (container_class) } + .sub-header-block + %span.oneline + Git access for + %strong= @project_wiki.path_with_namespace - .pull-right - = render "shared/clone_panel", project: @project_wiki + .pull-right + = render "shared/clone_panel", project: @project_wiki -.git-empty.prepend-top-default - %fieldset - %legend Install Gollum: - %pre.dark - :preserve - gem install gollum + .prepend-top-default + %fieldset + %legend Install Gollum: + %pre.dark + :preserve + gem install gollum - %legend Clone Your Wiki: - %pre.dark - :preserve - git clone #{ content_tag(:span, default_url_to_repo(@project_wiki), class: 'clone')} - cd #{h @project_wiki.path} + %legend Clone Your Wiki: + %pre.dark + :preserve + git clone #{ content_tag(:span, default_url_to_repo(@project_wiki), class: 'clone')} + cd #{h @project_wiki.path} - %legend Start Gollum And Edit Locally: - %pre.dark - :preserve - gollum - == Sinatra/1.3.5 has taken the stage on 4567 for development with backup from Thin - >> Thin web server (v1.5.0 codename Knife) - >> Maximum connections set to 1024 - >> Listening on 0.0.0.0:4567, CTRL+C to stop + %legend Start Gollum And Edit Locally: + %pre.dark + :preserve + gollum + == Sinatra/1.3.5 has taken the stage on 4567 for development with backup from Thin + >> Thin web server (v1.5.0 codename Knife) + >> Maximum connections set to 1024 + >> Listening on 0.0.0.0:4567, CTRL+C to stop diff --git a/app/views/projects/wikis/history.html.haml b/app/views/projects/wikis/history.html.haml index 45460ed9f41..630ee35b70b 100644 --- a/app/views/projects/wikis/history.html.haml +++ b/app/views/projects/wikis/history.html.haml @@ -1,37 +1,37 @@ - page_title "History", @page.title.capitalize, "Wiki" = render 'nav' +%div{ class: (container_class) } + .top-area + .nav-text + %strong + = link_to @page.title.capitalize, namespace_project_wiki_path(@project.namespace, @project, @page) + %span.light + · + History -.top-area - .nav-text - %strong - = link_to @page.title.capitalize, namespace_project_wiki_path(@project.namespace, @project, @page) - %span.light - · - History - -.table-holder - %table.table - %thead - %tr - %th Page version - %th Author - %th Commit Message - %th Last updated - %th Format - %tbody - - @page.versions.each_with_index do |version, index| - - commit = version + .table-holder + %table.table + %thead %tr - %td - = link_to project_wiki_path_with_version(@project, @page, - commit.id, index == 0) do - = truncate_sha(commit.id) - %td - = commit.author.name - %td - = commit.message - %td - #{time_ago_with_tooltip(version.authored_date)} - %td - %strong - = @page.page.wiki.page(@page.page.name, commit.id).try(:format) + %th Page version + %th Author + %th Commit Message + %th Last updated + %th Format + %tbody + - @page.versions.each_with_index do |version, index| + - commit = version + %tr + %td + = link_to project_wiki_path_with_version(@project, @page, + commit.id, index == 0) do + = truncate_sha(commit.id) + %td + = commit.author.name + %td + = commit.message + %td + #{time_ago_with_tooltip(version.authored_date)} + %td + %strong + = @page.page.wiki.page(@page.page.name, commit.id).try(:format) diff --git a/app/views/projects/wikis/pages.html.haml b/app/views/projects/wikis/pages.html.haml index 2f6162fa3c5..81d9f391c1c 100644 --- a/app/views/projects/wikis/pages.html.haml +++ b/app/views/projects/wikis/pages.html.haml @@ -1,12 +1,14 @@ +- @no_container = true - page_title "Pages", "Wiki" = render 'nav' -%ul.content-list - - @wiki_pages.each do |wiki_page| - %li - = link_to wiki_page.title, namespace_project_wiki_path(@project.namespace, @project, wiki_page) - %small (#{wiki_page.format}) - .pull-right - %small Last edited #{time_ago_with_tooltip(wiki_page.commit.authored_date)} -= paginate @wiki_pages, theme: 'gitlab' +%div{ class: (container_class) } + %ul.content-list + - @wiki_pages.each do |wiki_page| + %li + = link_to wiki_page.title, namespace_project_wiki_path(@project.namespace, @project, wiki_page) + %small (#{wiki_page.format}) + .pull-right + %small Last edited #{time_ago_with_tooltip(wiki_page.commit.authored_date)} + = paginate @wiki_pages, theme: 'gitlab' diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml index 9166c0edb3b..76f9b1ecd76 100644 --- a/app/views/projects/wikis/show.html.haml +++ b/app/views/projects/wikis/show.html.haml @@ -1,24 +1,26 @@ +- @no_container = true - page_title @page.title.capitalize, "Wiki" = render 'nav' -.top-area - .nav-text - %strong= @page.title.capitalize +%div{ class: (container_class) } + .top-area + .nav-text + %strong= @page.title.capitalize - %span.wiki-last-edit-by - · - last edited by #{@page.commit.author.name} #{time_ago_with_tooltip(@page.commit.authored_date)} + %span.wiki-last-edit-by + · + last edited by #{@page.commit.author.name} #{time_ago_with_tooltip(@page.commit.authored_date)} - .nav-controls - = render 'main_links' + .nav-controls + = render 'main_links' -- if @page.historical? - .warning_message - This is an old version of this page. - You can view the #{link_to "most recent version", namespace_project_wiki_path(@project.namespace, @project, @page)} or browse the #{link_to "history", namespace_project_wiki_history_path(@project.namespace, @project, @page)}. + - if @page.historical? + .warning_message + This is an old version of this page. + You can view the #{link_to "most recent version", namespace_project_wiki_path(@project.namespace, @project, @page)} or browse the #{link_to "history", namespace_project_wiki_history_path(@project.namespace, @project, @page)}. -.wiki-holder.prepend-top-default.append-bottom-default - .wiki - = preserve do - = render_wiki_content(@page) + .wiki-holder.prepend-top-default.append-bottom-default + .wiki + = preserve do + = render_wiki_content(@page) diff --git a/app/views/shared/_event_filter.html.haml b/app/views/shared/_event_filter.html.haml index 30055002213..aa18e6f236f 100644 --- a/app/views/shared/_event_filter.html.haml +++ b/app/views/shared/_event_filter.html.haml @@ -1,7 +1,9 @@ %ul.nav-links.event-filter.scrolling-tabs - .fade-left + %li.fade-left + = icon('arrow-left') = event_filter_link EventFilter.push, 'Push events' = event_filter_link EventFilter.merged, 'Merge events' = event_filter_link EventFilter.comments, 'Comments' = event_filter_link EventFilter.team, 'Team' - .fade-right + %li.fade-right + = icon('arrow-right') diff --git a/app/views/shared/_ref_switcher.html.haml b/app/views/shared/_ref_switcher.html.haml index eb2e1919e19..ea7162d4d63 100644 --- a/app/views/shared/_ref_switcher.html.haml +++ b/app/views/shared/_ref_switcher.html.haml @@ -1,7 +1,14 @@ +- dropdown_toggle_text = @ref || @project.default_branch = form_tag switch_namespace_project_refs_path(@project.namespace, @project), method: :get, class: "project-refs-form" do - = select_tag "ref", grouped_options_refs, class: "project-refs-select select2 select2-sm" = hidden_field_tag :destination, destination - if defined?(path) = hidden_field_tag :path, path - @options && @options.each do |key, value| = hidden_field_tag key, value, id: nil + .dropdown + = dropdown_toggle dropdown_toggle_text, { toggle: "dropdown", selected: dropdown_toggle_text, ref: @ref, refs_url: refs_namespace_project_path(@project.namespace, @project) }, { toggle_class: "js-project-refs-dropdown" } + .dropdown-menu.dropdown-menu-selectable{ class: ("dropdown-menu-align-right" if local_assigns[:align_right]) } + = dropdown_title "Switch branch/tag" + = dropdown_filter "Search branches and tags" + = dropdown_content + = dropdown_loading diff --git a/app/views/shared/members/_requests.html.haml b/app/views/shared/members/_requests.html.haml index b5963876034..e4bd2bdc265 100644 --- a/app/views/shared/members/_requests.html.haml +++ b/app/views/shared/members/_requests.html.haml @@ -3,6 +3,6 @@ .panel-heading %strong= membership_source.name access requests - %small= "(#{members.size})" + %span.badge= members.size %ul.content-list = render partial: 'shared/members/member', collection: members, as: :member diff --git a/app/views/shared/projects/_list.html.haml b/app/views/shared/projects/_list.html.haml index 2e08bb2ac08..3a9dd37dc7d 100644 --- a/app/views/shared/projects/_list.html.haml +++ b/app/views/shared/projects/_list.html.haml @@ -16,6 +16,12 @@ = render "shared/projects/project", project: project, skip_namespace: skip_namespace, avatar: avatar, stars: stars, css_class: css_class, ci: ci, use_creator_avatar: use_creator_avatar, forks: forks, show_last_commit_as_description: show_last_commit_as_description + + - if @private_forks_count && @private_forks_count > 0 + %li.project-row.private-forks-notice + = icon('lock fw', base: 'circle', class: 'fa-lg private-fork-icon') + %strong= pluralize(@private_forks_count, 'private fork') + %span you have no access to. = paginate(projects, remote: remote, theme: "gitlab") if projects.respond_to? :total_pages - else .nothing-here-block No projects found diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 09ffc319065..c6dc1e4ab38 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -215,7 +215,7 @@ Settings.gitlab.default_projects_features['container_registry'] = true if Settin Settings.gitlab.default_projects_features['visibility_level'] = Settings.send(:verify_constant, Gitlab::VisibilityLevel, Settings.gitlab.default_projects_features['visibility_level'], Gitlab::VisibilityLevel::PRIVATE) Settings.gitlab['repository_downloads_path'] = File.join(Settings.shared['path'], 'cache/archive') if Settings.gitlab['repository_downloads_path'].nil? Settings.gitlab['restricted_signup_domains'] ||= [] -Settings.gitlab['import_sources'] ||= ['github','bitbucket','gitlab','gitorious','google_code','fogbugz','git'] +Settings.gitlab['import_sources'] ||= %w[github bitbucket gitlab gitorious google_code fogbugz git gitlab_project] Settings.gitlab['trusted_proxies'] ||= [] diff --git a/config/routes.rb b/config/routes.rb index de6094fa0ed..e45293cdf7f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -295,7 +295,7 @@ Rails.application.routes.draw do post :repository_check end - resources :runner_projects + resources :runner_projects, only: [:create, :destroy] end end @@ -479,6 +479,7 @@ Rails.application.routes.draw do get :download_export get :autocomplete_sources get :activity + get :refs end scope module: :projects do diff --git a/db/migrate/20160509091049_add_locked_to_ci_runner.rb b/db/migrate/20160509091049_add_locked_to_ci_runner.rb new file mode 100644 index 00000000000..3fbaef3b7f0 --- /dev/null +++ b/db/migrate/20160509091049_add_locked_to_ci_runner.rb @@ -0,0 +1,13 @@ +class AddLockedToCiRunner < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + disable_ddl_transaction! + + def up + add_column_with_default(:ci_runners, :locked, :boolean, + default: false, allow_null: false) + end + + def down + remove_column(:ci_runners, :locked) + end +end diff --git a/db/migrate/20160615191922_set_missing_stage_on_ci_builds.rb b/db/migrate/20160615191922_set_missing_stage_on_ci_builds.rb new file mode 100644 index 00000000000..bd0463886bc --- /dev/null +++ b/db/migrate/20160615191922_set_missing_stage_on_ci_builds.rb @@ -0,0 +1,9 @@ +class SetMissingStageOnCiBuilds < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + def up + update_column_in_batches(:ci_builds, :stage, :test) do |table, query| + query.where(table[:stage].eq(nil)) + end + end +end diff --git a/db/migrate/20160620115026_add_index_on_runners_locked.rb b/db/migrate/20160620115026_add_index_on_runners_locked.rb new file mode 100644 index 00000000000..dfa5110dea4 --- /dev/null +++ b/db/migrate/20160620115026_add_index_on_runners_locked.rb @@ -0,0 +1,12 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddIndexOnRunnersLocked < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + disable_ddl_transaction! + + def change + add_concurrent_index :ci_runners, :locked + end +end diff --git a/db/schema.rb b/db/schema.rb index 29f90c47848..7a8377f687c 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160617301627) do +ActiveRecord::Schema.define(version: 20160620115026) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -287,9 +287,11 @@ ActiveRecord::Schema.define(version: 20160617301627) do t.string "platform" t.string "architecture" t.boolean "run_untagged", default: true, null: false + t.boolean "locked", default: false, null: false end add_index "ci_runners", ["description"], name: "index_ci_runners_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"} + add_index "ci_runners", ["locked"], name: "index_ci_runners_on_locked", using: :btree add_index "ci_runners", ["token"], name: "index_ci_runners_on_token", using: :btree add_index "ci_runners", ["token"], name: "index_ci_runners_on_token_trigram", using: :gin, opclasses: {"token"=>"gin_trgm_ops"} diff --git a/doc/administration/container_registry.md b/doc/administration/container_registry.md index 7870669fa77..de225385c7a 100644 --- a/doc/administration/container_registry.md +++ b/doc/administration/container_registry.md @@ -22,6 +22,7 @@ You can read more about Docker Registry at https://docs.docker.com/registry/intr - [Disable Container Registry per project](#disable-container-registry-per-project) - [Disable Container Registry for new projects site-wide](#disable-container-registry-for-new-projects-site-wide) - [Container Registry storage path](#container-registry-storage-path) +- [Container Registry storage driver](#container-registry-storage-driver) - [Storage limitations](#storage-limitations) - [Changelog](#changelog) @@ -306,8 +307,12 @@ the Container Registry by themselves, follow the steps below. ## Container Registry storage path -To change the storage path where Docker images will be stored, follow the -steps below. +>**Note:** +For configuring storage in the cloud instead of the filesystem, see the +[storage driver configuration](#container-registry-storage-driver). + +If you want to store your images on the filesystem, you can change the storage +path for the Container Registry, follow the steps below. This path is accessible to: @@ -349,6 +354,72 @@ The default location where images are stored in source installations, is 1. Save the file and [restart GitLab][] for the changes to take effect. +## Container Registry storage driver + +You can configure the Container Registry to use a different storage backend by +configuring a different storage driver. By default the GitLab Container Registry +is configured to use the filesystem driver, which makes use of [storage path](#container-registry-storage-path) +configuration. + +The different supported drivers are: + +| Driver | Description | +|------------|-------------------------------------| +| filesystem | Uses a path on the local filesystem | +| azure | Microsoft Azure Blob Storage | +| gcs | Google Cloud Storage | +| s3 | Amazon Simple Storage Service | +| swift | OpenStack Swift Object Storage | +| oss | Aliyun OSS | + +Read more about the individual driver's config options in the +[Docker Registry docs][storage-config]. + +> **Warning** GitLab will not backup Docker images that are not stored on the +filesystem. Remember to enable backups with your object storage provider if +desired. + +--- + +**Omnibus GitLab installations** + +To configure the storage driver in Omnibus: + +1. Edit `/etc/gitlab/gitlab.rb`: + + ```ruby + registry['storage'] = { + 's3' => { + 'accesskey' => 's3-access-key', + 'secretkey' => 's3-secret-key-for-access-key', + 'bucket' => 'your-s3-bucket' + } + } + ``` + +1. Save the file and [reconfigure GitLab][] for the changes to take effect. + +--- + +**Installations from source** + +Configuring the storage driver is done in your registry config YML file created +when you [deployed your docker registry][registry-deploy]. + +Example: + +``` +storage: + s3: + accesskey: 'AKIAKIAKI' + secretkey: 'secret123' + bucket: 'gitlab-registry-bucket-AKIAKIAKI' + cache: + blobdescriptor: inmemory + delete: + enabled: true +``` + ## Storage limitations Currently, there is no storage limitation, which means a user can upload an diff --git a/doc/api/README.md b/doc/api/README.md index 73f44603688..288f7f9ee69 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -32,6 +32,7 @@ following locations: - [Services](services.md) - [Session](session.md) - [Settings](settings.md) +- [Sidekiq metrics](sidekiq_metrics.md) - [System Hooks](system_hooks.md) - [Tags](tags.md) - [Users](users.md) diff --git a/doc/ci/runners/README.md b/doc/ci/runners/README.md index 400784da617..ddebd987650 100644 --- a/doc/ci/runners/README.md +++ b/doc/ci/runners/README.md @@ -96,6 +96,12 @@ To register the runner, run the command below and follow instructions: sudo gitlab-ci-multi-runner register ``` +### Lock a specific runner from being enabled for other projects + +You can configure a runner to assign it exclusively to a project. When a +runner is locked this way, it can no longer be enabled for other projects. +This setting is available on each runner in *Project Settings* > *Runners*. + ### Making an existing Shared Runner Specific If you are an admin on your GitLab instance, @@ -128,7 +134,7 @@ the appropriate dependencies to run Rails test suites. ### Prevent runner with tags from picking jobs without tags You can configure a runner to prevent it from picking jobs with tags when -the runnner does not have tags assigned. This setting is available on each +the runner does not have tags assigned. This setting is available on each runner in *Project Settings* > *Runners*. ### Be careful with sensitive information diff --git a/doc/project_services/emails_on_push.md b/doc/project_services/emails_on_push.md new file mode 100644 index 00000000000..2f9f36f962e --- /dev/null +++ b/doc/project_services/emails_on_push.md @@ -0,0 +1,17 @@ +## Enabling emails on push + +To receive email notifications for every change that is pushed to the project, visit +your project's **Settings > Services > Emails on push** and activate the service. + +In the _Recipients_ area, provide a list of emails separated by commas. + +You can configure any of the following settings depending on your preference. + ++ **Push events** - Email will be triggered when a push event is recieved ++ **Tag push events** - Email will be triggered when a tag is created and pushed ++ **Send from committer** - Send notifications from the committer's email address if the domain is part of the domain GitLab is running on (e.g. `user@gitlab.com`). ++ **Disable code diffs** - Don't include possibly sensitive code diffs in notification body. + +--- + +![Email on push service settings](img/emails_on_push_service.png) diff --git a/doc/project_services/img/emails_on_push_service.png b/doc/project_services/img/emails_on_push_service.png Binary files differnew file mode 100644 index 00000000000..cd6f79ad1eb --- /dev/null +++ b/doc/project_services/img/emails_on_push_service.png diff --git a/doc/project_services/project_services.md b/doc/project_services/project_services.md index a5af620d9be..f81a035f70b 100644 --- a/doc/project_services/project_services.md +++ b/doc/project_services/project_services.md @@ -33,7 +33,7 @@ further configuration instructions and details. Contributions are welcome. | Campfire | Simple web-based real-time group chat | | Custom Issue Tracker | Custom issue tracker | | Drone CI | Continuous Integration platform built on Docker, written in Go | -| Emails on push | Email the commits and diff of each push to a list of recipients | +| [Emails on push](emails_on_push.md) | Email the commits and diff of each push to a list of recipients | | External Wiki | Replaces the link to the internal wiki with a link to an external wiki | | Flowdock | Flowdock is a collaboration web app for technical teams | | Gemnasium | Gemnasium monitors your project dependencies and alerts you about updates and security vulnerabilities | diff --git a/doc/workflow/add-user/add-user.md b/doc/workflow/add-user/add-user.md index fffa0aba57f..4b551130255 100644 --- a/doc/workflow/add-user/add-user.md +++ b/doc/workflow/add-user/add-user.md @@ -8,7 +8,7 @@ You should have `master` or `owner` permissions to add or import a new user to your project. The first step to add or import a user, go to your project and click on -**Members** on the left side of your screen. +**Members** in the drop-down menu on the right side of your screen. ![Members](img/add_user_members_menu.png) @@ -87,3 +87,25 @@ invitation, change their access level or even delete them. Once the user accepts the invitation, they will be prompted to create a new GitLab account using the same e-mail address the invitation was sent to. + +## Request access to a project + +As a user, you can request to be a member of a project. Go to the project you'd +like to be a member of, and click the **Request Access** button on the right +side of your screen. + +![Request access button](img/request_access_button.png) + +--- + +Project owners & masters will be notified of your request and will be able to approve or +decline it on the members page. + +![Manage access requests](img/access_requests_management.png) + +--- + +If you change your mind before your request is approved, just click the +**Withdraw Access Request** button. + +![Withdraw access request button](img/withdraw_access_request_button.png) diff --git a/doc/workflow/add-user/img/access_requests_management.png b/doc/workflow/add-user/img/access_requests_management.png Binary files differnew file mode 100644 index 00000000000..e9641cb4f85 --- /dev/null +++ b/doc/workflow/add-user/img/access_requests_management.png diff --git a/doc/workflow/add-user/img/add_user_email_accept.png b/doc/workflow/add-user/img/add_user_email_accept.png Binary files differindex 910affc9659..18aabf93d50 100644 --- a/doc/workflow/add-user/img/add_user_email_accept.png +++ b/doc/workflow/add-user/img/add_user_email_accept.png diff --git a/doc/workflow/add-user/img/add_user_email_ready.png b/doc/workflow/add-user/img/add_user_email_ready.png Binary files differindex 5f02ce89b3e..385d64330c0 100644 --- a/doc/workflow/add-user/img/add_user_email_ready.png +++ b/doc/workflow/add-user/img/add_user_email_ready.png diff --git a/doc/workflow/add-user/img/add_user_email_search.png b/doc/workflow/add-user/img/add_user_email_search.png Binary files differindex 140979fbe13..84741edbca4 100644 --- a/doc/workflow/add-user/img/add_user_email_search.png +++ b/doc/workflow/add-user/img/add_user_email_search.png diff --git a/doc/workflow/add-user/img/add_user_give_permissions.png b/doc/workflow/add-user/img/add_user_give_permissions.png Binary files differindex 8ef9156c8d5..7e580384e54 100644 --- a/doc/workflow/add-user/img/add_user_give_permissions.png +++ b/doc/workflow/add-user/img/add_user_give_permissions.png diff --git a/doc/workflow/add-user/img/add_user_import_members_from_another_project.png b/doc/workflow/add-user/img/add_user_import_members_from_another_project.png Binary files differindex 5770d5cf0c4..8dbd73a5bc8 100644 --- a/doc/workflow/add-user/img/add_user_import_members_from_another_project.png +++ b/doc/workflow/add-user/img/add_user_import_members_from_another_project.png diff --git a/doc/workflow/add-user/img/add_user_imported_members.png b/doc/workflow/add-user/img/add_user_imported_members.png Binary files differindex dea4b3f40ad..abac1f59c02 100644 --- a/doc/workflow/add-user/img/add_user_imported_members.png +++ b/doc/workflow/add-user/img/add_user_imported_members.png diff --git a/doc/workflow/add-user/img/add_user_list_members.png b/doc/workflow/add-user/img/add_user_list_members.png Binary files differindex 7daa6ca7d9e..e17d88c6f5f 100644 --- a/doc/workflow/add-user/img/add_user_list_members.png +++ b/doc/workflow/add-user/img/add_user_list_members.png diff --git a/doc/workflow/add-user/img/add_user_members_menu.png b/doc/workflow/add-user/img/add_user_members_menu.png Binary files differindex f1797b95f67..ec5d39f402d 100644 --- a/doc/workflow/add-user/img/add_user_members_menu.png +++ b/doc/workflow/add-user/img/add_user_members_menu.png diff --git a/doc/workflow/add-user/img/add_user_search_people.png b/doc/workflow/add-user/img/add_user_search_people.png Binary files differindex 5ac10ce80d4..eaa062376f4 100644 --- a/doc/workflow/add-user/img/add_user_search_people.png +++ b/doc/workflow/add-user/img/add_user_search_people.png diff --git a/doc/workflow/add-user/img/request_access_button.png b/doc/workflow/add-user/img/request_access_button.png Binary files differnew file mode 100644 index 00000000000..984d640b0f0 --- /dev/null +++ b/doc/workflow/add-user/img/request_access_button.png diff --git a/doc/workflow/add-user/img/withdraw_access_request_button.png b/doc/workflow/add-user/img/withdraw_access_request_button.png Binary files differnew file mode 100644 index 00000000000..ff54a0e4384 --- /dev/null +++ b/doc/workflow/add-user/img/withdraw_access_request_button.png diff --git a/doc/workflow/groups.md b/doc/workflow/groups.md index 34ada1774d8..1a316e80976 100644 --- a/doc/workflow/groups.md +++ b/doc/workflow/groups.md @@ -51,6 +51,28 @@ If necessary, you can increase the access level of an individual user for a spec ![Barry effectively has 'Master' access to GitLab CI now](groups/override_access_level.png) +## Request access to a group + +As a user, you can request to be a member of a group. Go to the group you'd +like to be a member of, and click the **Request Access** button on the right +side of your screen. + +![Request access button](groups/request_access_button.png) + +--- + +Group owners & masters will be notified of your request and will be able to approve or +decline it on the members page. + +![Manage access requests](groups/access_requests_management.png) + +--- + +If you change your mind before your request is approved, just click the +**Withdraw Access Request** button. + +![Withdraw access request button](groups/withdraw_access_request_button.png) + ## Managing group memberships via LDAP In GitLab Enterprise Edition it is possible to manage GitLab group memberships using LDAP groups. diff --git a/doc/workflow/groups/access_requests_management.png b/doc/workflow/groups/access_requests_management.png Binary files differnew file mode 100644 index 00000000000..ffede8e9bd6 --- /dev/null +++ b/doc/workflow/groups/access_requests_management.png diff --git a/doc/workflow/groups/request_access_button.png b/doc/workflow/groups/request_access_button.png Binary files differnew file mode 100644 index 00000000000..ff0ac8747a7 --- /dev/null +++ b/doc/workflow/groups/request_access_button.png diff --git a/doc/workflow/groups/withdraw_access_request_button.png b/doc/workflow/groups/withdraw_access_request_button.png Binary files differnew file mode 100644 index 00000000000..99d7a326ed8 --- /dev/null +++ b/doc/workflow/groups/withdraw_access_request_button.png diff --git a/doc/workflow/shortcuts.png b/doc/workflow/shortcuts.png Binary files differindex beb6c53ec77..16be0413b64 100644 --- a/doc/workflow/shortcuts.png +++ b/doc/workflow/shortcuts.png diff --git a/features/admin/groups.feature b/features/admin/groups.feature index ab7de7ac315..657e847cf4a 100644 --- a/features/admin/groups.feature +++ b/features/admin/groups.feature @@ -27,13 +27,6 @@ Feature: Admin Groups Then I should see project shared with group @javascript - Scenario: Remove user from group - Given we have user "John Doe" in group - When I visit admin group page - And I remove user "John Doe" from group - Then I should not see "John Doe" in team list - - @javascript Scenario: Invite user to a group by e-mail When I visit admin group page When I select user "johndoe@gitlab.com" from user list as "Reporter" diff --git a/features/dashboard/group.feature b/features/dashboard/group.feature index e3c01db2ebb..3ae2c679dc1 100644 --- a/features/dashboard/group.feature +++ b/features/dashboard/group.feature @@ -5,53 +5,9 @@ Feature: Dashboard Group And "John Doe" is owner of group "Owned" And "John Doe" is guest of group "Guest" - # Leave groups - - @javascript - Scenario: Owner should be able to leave from group if he is not the last owner - Given "Mary Jane" is owner of group "Owned" - When I visit dashboard groups page - Then I should see group "Owned" in group list - Then I should see group "Guest" in group list - When I click on the "Leave" button for group "Owned" - And I visit dashboard groups page - Then I should not see group "Owned" in group list - Then I should see group "Guest" in group list - - @javascript - Scenario: Owner should not be able to leave from group if he is the last owner - Given "Mary Jane" is guest of group "Owned" - When I visit dashboard groups page - Then I should see group "Owned" in group list - Then I should see group "Guest" in group list - When I click on the "Leave" button for group "Owned" - Then I should see the "Can not leave message" - - @javascript - Scenario: Guest should be able to leave from group - Given "Mary Jane" is guest of group "Guest" - When I visit dashboard groups page - Then I should see group "Owned" in group list - Then I should see group "Guest" in group list - When I click on the "Leave" button for group "Guest" - When I visit dashboard groups page - Then I should see group "Owned" in group list - Then I should not see group "Guest" in group list - - @javascript - Scenario: Guest should be able to leave from group even if he is the only user in the group - When I visit dashboard groups page - Then I should see group "Owned" in group list - Then I should see group "Guest" in group list - When I click on the "Leave" button for group "Guest" - When I visit dashboard groups page - Then I should see group "Owned" in group list - Then I should not see group "Guest" in group list - Scenario: Create a group from dasboard And I visit dashboard groups page And I click new group link And submit form with new group "Samurai" info Then I should be redirected to group "Samurai" page And I should see newly created group "Samurai" - diff --git a/features/project/active_tab.feature b/features/project/active_tab.feature index c4f987a7923..57dda9c2234 100644 --- a/features/project/active_tab.feature +++ b/features/project/active_tab.feature @@ -10,9 +10,9 @@ Feature: Project Active Tab Then the active main tab should be Home And no other main tabs should be active - Scenario: On Project Code + Scenario: On Project Repository Given I visit my project's files page - Then the active main tab should be Code + Then the active main tab should be Repository And no other main tabs should be active Scenario: On Project Issues @@ -59,46 +59,46 @@ Feature: Project Active Tab And no other sub navs should be active And the active main tab should be Settings - # Sub Tabs: Code + # Sub Tabs: Repository - Scenario: On Project Code/Files + Scenario: On Project Repository/Files Given I visit my project's files page Then the active sub tab should be Files And no other sub tabs should be active - And the active main tab should be Code + And the active main tab should be Repository - Scenario: On Project Code/Commits + Scenario: On Project Repository/Commits Given I visit my project's commits page Then the active sub tab should be Commits And no other sub tabs should be active - And the active main tab should be Code + And the active main tab should be Repository - Scenario: On Project Code/Network + Scenario: On Project Repository/Network Given I visit my project's network page Then the active sub tab should be Network And no other sub tabs should be active - And the active main tab should be Code + And the active main tab should be Repository - Scenario: On Project Code/Compare + Scenario: On Project Repository/Compare Given I visit my project's commits page And I click the "Compare" tab Then the active sub tab should be Compare And no other sub tabs should be active - And the active main tab should be Code + And the active main tab should be Repository - Scenario: On Project Code/Branches + Scenario: On Project Repository/Branches Given I visit my project's commits page And I click the "Branches" tab Then the active sub tab should be Branches And no other sub tabs should be active - And the active main tab should be Code + And the active main tab should be Repository - Scenario: On Project Code/Tags + Scenario: On Project Repository/Tags Given I visit my project's commits page And I click the "Tags" tab Then the active sub tab should be Tags And no other sub tabs should be active - And the active main tab should be Code + And the active main tab should be Repository Scenario: On Project Issues/Browse Given I visit my project's issues page diff --git a/features/project/shortcuts.feature b/features/project/shortcuts.feature index c73d0b32337..f71f69ef060 100644 --- a/features/project/shortcuts.feature +++ b/features/project/shortcuts.feature @@ -8,21 +8,21 @@ Feature: Project Shortcuts @javascript Scenario: Navigate to files tab Given I press "g" and "f" - Then the active main tab should be Code + Then the active main tab should be Repository Then the active sub tab should be Files @javascript Scenario: Navigate to commits tab Given I visit my project's files page Given I press "g" and "c" - Then the active main tab should be Code + Then the active main tab should be Repository Then the active sub tab should be Commits @javascript Scenario: Navigate to network tab Given I press "g" and "n" Then the active sub tab should be Network - And the active main tab should be Code + And the active main tab should be Repository @javascript Scenario: Navigate to graphs tab diff --git a/features/steps/admin/groups.rb b/features/steps/admin/groups.rb index e1f1db2872f..8613dc537cc 100644 --- a/features/steps/admin/groups.rb +++ b/features/steps/admin/groups.rb @@ -62,7 +62,7 @@ class Spinach::Features::AdminGroups < Spinach::FeatureSteps step 'I should see "johndoe@gitlab.com" in team list in every project as "Reporter"' do page.within ".group-users-list" do - expect(page).to have_content "johndoe@gitlab.com (invited)" + expect(page).to have_content "johndoe@gitlab.com – Invited by" expect(page).to have_content "Reporter" end end @@ -92,12 +92,6 @@ class Spinach::Features::AdminGroups < Spinach::FeatureSteps current_group.add_reporter(user_john) end - step 'I remove user "John Doe" from group' do - page.within "#user_#{user_john.id}" do - click_link 'Remove user from group' - end - end - step 'I should not see "John Doe" in team list' do page.within ".group-users-list" do expect(page).not_to have_content "John Doe" diff --git a/features/steps/dashboard/group.rb b/features/steps/dashboard/group.rb index 9b79a3be49b..cf679fea530 100644 --- a/features/steps/dashboard/group.rb +++ b/features/steps/dashboard/group.rb @@ -4,44 +4,6 @@ class Spinach::Features::DashboardGroup < Spinach::FeatureSteps include SharedPaths include SharedUser - # Leave - - step 'I click on the "Leave" button for group "Owned"' do - find(:css, 'li', text: "Owner").find(:css, 'i.fa.fa-sign-out').click - # poltergeist always confirms popups. - end - - step 'I click on the "Leave" button for group "Guest"' do - find(:css, 'li', text: "Guest").find(:css, 'i.fa.fa-sign-out').click - # poltergeist always confirms popups. - end - - step 'I should not see the "Leave" button for group "Owned"' do - expect(find(:css, 'li', text: "Owner")).not_to have_selector(:css, 'i.fa.fa-sign-out') - # poltergeist always confirms popups. - end - - step 'I should not see the "Leave" button for groupr "Guest"' do - expect(find(:css, 'li', text: "Guest")).not_to have_selector(:css, 'i.fa.fa-sign-out') - # poltergeist always confirms popups. - end - - step 'I should see group "Owned" in group list' do - expect(page).to have_content("Owned") - end - - step 'I should not see group "Owned" in group list' do - expect(page).not_to have_content("Owned") - end - - step 'I should see group "Guest" in group list' do - expect(page).to have_content("Guest") - end - - step 'I should not see group "Guest" in group list' do - expect(page).not_to have_content("Guest") - end - step 'I click new group link' do click_link "New Group" end @@ -60,8 +22,4 @@ class Spinach::Features::DashboardGroup < Spinach::FeatureSteps expect(page).to have_content "Samurai" expect(page).to have_content "Tokugawa Shogunate" end - - step 'I should see the "Can not leave message"' do - expect(page).to have_content "You can not leave the \"Owned\" group." - end end diff --git a/features/steps/profile/notifications.rb b/features/steps/profile/notifications.rb index 979f4692d5a..7e339443b75 100644 --- a/features/steps/profile/notifications.rb +++ b/features/steps/profile/notifications.rb @@ -15,8 +15,6 @@ class Spinach::Features::ProfileNotifications < Spinach::FeatureSteps end step 'I should see Notification saved message' do - page.within '.flash-container' do - expect(page).to have_content 'Notification settings saved' - end + expect(page).to have_content 'On mention' end end diff --git a/features/steps/project/network_graph.rb b/features/steps/project/network_graph.rb index 9b59b682676..019b3124a86 100644 --- a/features/steps/project/network_graph.rb +++ b/features/steps/project/network_graph.rb @@ -20,11 +20,11 @@ class Spinach::Features::ProjectNetworkGraph < Spinach::FeatureSteps end step 'page should select "master" in select box' do - expect(page).to have_selector '.select2-chosen', text: "master" + expect(page).to have_selector '.dropdown-menu-toggle', text: "master" end step 'page should select "v1.0.0" in select box' do - expect(page).to have_selector '.select2-chosen', text: "v1.0.0" + expect(page).to have_selector '.dropdown-menu-toggle', text: "v1.0.0" end step 'page should have "master" on graph' do @@ -40,11 +40,19 @@ class Spinach::Features::ProjectNetworkGraph < Spinach::FeatureSteps end When 'I switch ref to "feature"' do - select 'feature', from: 'ref' + first('.js-project-refs-dropdown').click + + page.within '.project-refs-form' do + click_link 'feature' + end end When 'I switch ref to "v1.0.0"' do - select 'v1.0.0', from: 'ref' + first('.js-project-refs-dropdown').click + + page.within '.project-refs-form' do + click_link 'v1.0.0' + end end When 'click "Show only selected branch" checkbox' do @@ -68,11 +76,11 @@ class Spinach::Features::ProjectNetworkGraph < Spinach::FeatureSteps end step 'page should select "feature" in select box' do - expect(page).to have_selector '.select2-chosen', text: "feature" + expect(page).to have_selector '.dropdown-menu-toggle', text: "feature" end step 'page should select "v1.0.0" in select box' do - expect(page).to have_selector '.select2-chosen', text: "v1.0.0" + expect(page).to have_selector '.dropdown-menu-toggle', text: "v1.0.0" end step 'page should have "feature" on graph' do diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb index 98b57e5cbfb..76fefee9254 100644 --- a/features/steps/project/project.rb +++ b/features/steps/project/project.rb @@ -134,8 +134,8 @@ class Spinach::Features::Project < Spinach::FeatureSteps end step 'I should see Notification saved message' do - page.within '.flash-container' do - expect(page).to have_content 'Notification settings saved' + page.within '#notifications-button' do + expect(page).to have_content 'On mention' end end diff --git a/features/steps/project/project_find_file.rb b/features/steps/project/project_find_file.rb index 47de4b91df1..90771847909 100644 --- a/features/steps/project/project_find_file.rb +++ b/features/steps/project/project_find_file.rb @@ -13,12 +13,12 @@ class Spinach::Features::ProjectFindFile < Spinach::FeatureSteps end step 'I should see "find file" page' do - ensure_active_main_tab('Code') + ensure_active_main_tab('Repository') expect(page).to have_selector('.file-finder-holder', count: 1) end step 'I fill in Find by path with "git"' do - ensure_active_main_tab('Code') + ensure_active_main_tab('Repository') expect(page).to have_selector('.file-finder-holder', count: 1) end diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb index 79a3ed8197e..0fe046dcbf6 100644 --- a/features/steps/project/source/browse_files.rb +++ b/features/steps/project/source/browse_files.rb @@ -290,15 +290,23 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps end step "I switch ref to 'test'" do - select "'test'", from: 'ref' + first('.js-project-refs-dropdown').click + + page.within '.project-refs-form' do + click_link 'test' + end end step "I switch ref to fix" do - select "fix", from: 'ref' + first('.js-project-refs-dropdown').click + + page.within '.project-refs-form' do + click_link 'fix' + end end step "I see the ref 'test' has been selected" do - expect(page).to have_selector '.select2-chosen', text: "'test'" + expect(page).to have_selector '.dropdown-toggle-text', text: "'test'" end step "I visit the 'test' tree" do diff --git a/features/steps/shared/project_tab.rb b/features/steps/shared/project_tab.rb index bfee8793301..d6024212601 100644 --- a/features/steps/shared/project_tab.rb +++ b/features/steps/shared/project_tab.rb @@ -8,8 +8,8 @@ module SharedProjectTab ensure_active_main_tab('Project') end - step 'the active main tab should be Code' do - ensure_active_main_tab('Code') + step 'the active main tab should be Repository' do + ensure_active_main_tab('Repository') end step 'the active main tab should be Graphs' do diff --git a/lib/api/api.rb b/lib/api/api.rb index 0e7a1cc2623..f8f680a6311 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -33,7 +33,6 @@ module API mount ::API::Commits mount ::API::DeployKeys mount ::API::Files - mount ::API::Gitignores mount ::API::GroupMembers mount ::API::Groups mount ::API::Internal @@ -58,6 +57,7 @@ module API mount ::API::Subscriptions mount ::API::SystemHooks mount ::API::Tags + mount ::API::Templates mount ::API::Triggers mount ::API::Users mount ::API::Variables diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 2e397643ed1..5a23a18fe9c 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -423,6 +423,7 @@ module API class RunnerDetails < Runner expose :tag_list expose :run_untagged + expose :locked expose :version, :revision, :platform, :architecture expose :contacted_at expose :token, if: lambda { |runner, options| options[:current_user].is_admin? || !runner.is_shared? } @@ -444,11 +445,7 @@ module API expose :created_at, :started_at, :finished_at expose :user, with: User expose :artifacts_file, using: BuildArtifactFile, if: -> (build, opts) { build.artifacts? } - expose :commit, with: RepoCommit do |repo_obj, _options| - if repo_obj.respond_to?(:commit) - repo_obj.commit.commit_data - end - end + expose :commit, with: RepoCommit expose :runner, with: Runner end @@ -472,11 +469,11 @@ module API expose :content end - class GitignoresList < Grape::Entity + class TemplatesList < Grape::Entity expose :name end - class Gitignore < Grape::Entity + class Template < Grape::Entity expose :name, :content end end diff --git a/lib/api/gitignores.rb b/lib/api/gitignores.rb deleted file mode 100644 index 270c9501dd2..00000000000 --- a/lib/api/gitignores.rb +++ /dev/null @@ -1,29 +0,0 @@ -module API - class Gitignores < Grape::API - - # Get the list of the available gitignore templates - # - # Example Request: - # GET /gitignores - get 'gitignores' do - present Gitlab::Gitignore.all, with: Entities::GitignoresList - end - - # Get the text for a specific gitignore - # - # Parameters: - # name (required) - The name of a license - # - # Example Request: - # GET /gitignores/Elixir - # - get 'gitignores/:name' do - required_attributes! [:name] - - gitignore = Gitlab::Gitignore.find(params[:name]) - not_found!('.gitignore') unless gitignore - - present gitignore, with: Entities::Gitignore - end - end -end diff --git a/lib/api/internal.rb b/lib/api/internal.rb index 3ac7b50c4ce..1d361569d59 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -23,8 +23,6 @@ module API end post "/allowed" do - Gitlab::Metrics.action = 'Grape#/internal/allowed' - status 200 actor = diff --git a/lib/api/runners.rb b/lib/api/runners.rb index 4faba9dc87b..ecc8f2fc5a2 100644 --- a/lib/api/runners.rb +++ b/lib/api/runners.rb @@ -49,7 +49,7 @@ module API runner = get_runner(params[:id]) authenticate_update_runner!(runner) - attrs = attributes_for_keys [:description, :active, :tag_list, :run_untagged] + attrs = attributes_for_keys [:description, :active, :tag_list, :run_untagged, :locked] if runner.update(attrs) present runner, with: Entities::RunnerDetails, current_user: current_user else @@ -96,9 +96,14 @@ module API runner = get_runner(params[:runner_id]) authenticate_enable_runner!(runner) - Ci::RunnerProject.create(runner: runner, project: user_project) - present runner, with: Entities::Runner + runner_project = runner.assign_to(user_project) + + if runner_project.persisted? + present runner, with: Entities::Runner + else + conflict!("Runner was already enabled for this project") + end end # Disable project's runner @@ -163,6 +168,7 @@ module API def authenticate_enable_runner!(runner) forbidden!("Runner is shared") if runner.is_shared? + forbidden!("Runner is locked") if runner.locked? return if current_user.is_admin? forbidden!("No access granted") unless user_can_access_runner?(runner) end diff --git a/lib/api/templates.rb b/lib/api/templates.rb new file mode 100644 index 00000000000..18408797756 --- /dev/null +++ b/lib/api/templates.rb @@ -0,0 +1,36 @@ +module API + class Templates < Grape::API + TEMPLATE_TYPES = { + gitignores: Gitlab::Template::Gitignore, + gitlab_ci_ymls: Gitlab::Template::GitlabCiYml + }.freeze + + TEMPLATE_TYPES.each do |template, klass| + # Get the list of the available template + # + # Example Request: + # GET /gitignores + # GET /gitlab_ci_ymls + get template.to_s do + present klass.all, with: Entities::TemplatesList + end + + # Get the text for a specific template + # + # Parameters: + # name (required) - The name of a template + # + # Example Request: + # GET /gitignores/Elixir + # GET /gitlab_ci_ymls/Ruby + get "#{template}/:name" do + required_attributes! [:name] + + new_template = klass.find(params[:name]) + not_found!(template.to_s.singularize) unless new_template + + present new_template, with: Entities::Template + end + end + end +end diff --git a/lib/banzai.rb b/lib/banzai.rb index b467413a7dd..093382261ae 100644 --- a/lib/banzai.rb +++ b/lib/banzai.rb @@ -7,10 +7,6 @@ module Banzai Renderer.render_result(text, context) end - def self.pre_process(text, context) - Renderer.pre_process(text, context) - end - def self.post_process(html, context) Renderer.post_process(html, context) end diff --git a/lib/banzai/filter/relative_link_filter.rb b/lib/banzai/filter/relative_link_filter.rb index ea21c7b041c..c78da404607 100644 --- a/lib/banzai/filter/relative_link_filter.rb +++ b/lib/banzai/filter/relative_link_filter.rb @@ -14,6 +14,8 @@ module Banzai def call return doc unless linkable_files? + @uri_types = {} + doc.search('a:not(.gfm)').each do |el| process_link_attr el.attribute('href') end @@ -48,7 +50,7 @@ module Banzai uri.path = [ relative_url_root, context[:project].path_with_namespace, - path_type(file_path), + uri_type(file_path), ref || context[:project].default_branch, # if no ref exists, point to the default branch file_path ].compact.join('/').squeeze('/').chomp('/') @@ -87,7 +89,7 @@ module Banzai return path unless request_path parts = request_path.split('/') - parts.pop if path_type(request_path) != 'tree' + parts.pop if uri_type(request_path) != :tree while path.start_with?('../') parts.pop @@ -98,45 +100,20 @@ module Banzai end def file_exists?(path) - return false if path.nil? - repository.blob_at(current_sha, path).present? || - repository.tree(current_sha, path).entries.any? - end - - # Get the type of the given path - # - # path - String path to check - # - # Examples: - # - # path_type('doc/README.md') # => 'blob' - # path_type('doc/logo.png') # => 'raw' - # path_type('doc/api') # => 'tree' - # - # Returns a String - def path_type(path) - unescaped_path = Addressable::URI.unescape(path) - - if tree?(unescaped_path) - 'tree' - elsif image?(unescaped_path) - 'raw' - else - 'blob' - end + path.present? && !!uri_type(path) end - def tree?(path) - repository.tree(current_sha, path).entries.any? - end + def uri_type(path) + @uri_types[path] ||= begin + unescaped_path = Addressable::URI.unescape(path) - def image?(path) - repository.blob_at(current_sha, path).try(:image?) + current_commit.uri_type(unescaped_path) + end end - def current_sha - context[:commit].try(:id) || - ref ? repository.commit(ref).try(:sha) : repository.head_commit.sha + def current_commit + @current_commit ||= context[:commit] || + ref ? repository.commit(ref) : repository.head_commit end def relative_url_root @@ -148,7 +125,7 @@ module Banzai end def repository - context[:project].try(:repository) + @repository ||= context[:project].try(:repository) end end end diff --git a/lib/banzai/renderer.rb b/lib/banzai/renderer.rb index c14a9c4c722..6718acdef7e 100644 --- a/lib/banzai/renderer.rb +++ b/lib/banzai/renderer.rb @@ -30,13 +30,9 @@ module Banzai end def self.render_result(text, context = {}) - Pipeline[context[:pipeline]].call(text, context) - end + text = Pipeline[:pre_process].to_html(text, context) if text - def self.pre_process(text, context) - pipeline = Pipeline[:pre_process] - - pipeline.to_html(text, context) + Pipeline[context[:pipeline]].call(text, context) end # Perform post-processing on an HTML String diff --git a/lib/ci/api/runners.rb b/lib/ci/api/runners.rb index 0c41f22c7c5..bcc82969eb3 100644 --- a/lib/ci/api/runners.rb +++ b/lib/ci/api/runners.rb @@ -28,12 +28,9 @@ module Ci post "register" do required_attributes! [:token] - attributes = { description: params[:description], - tag_list: params[:tag_list] } - - unless params[:run_untagged].nil? - attributes[:run_untagged] = params[:run_untagged] - end + attributes = attributes_for_keys( + [:description, :tag_list, :run_untagged, :locked] + ) runner = if runner_registration_token_valid? diff --git a/lib/container_registry/tag.rb b/lib/container_registry/tag.rb index 7a0929d774e..708d01b95a1 100644 --- a/lib/container_registry/tag.rb +++ b/lib/container_registry/tag.rb @@ -3,6 +3,7 @@ module ContainerRegistry attr_reader :repository, :name delegate :registry, :client, to: :repository + delegate :revision, :short_revision, to: :config_blob, allow_nil: true def initialize(repository, name) @repository, @name = repository, name diff --git a/lib/gitlab/access.rb b/lib/gitlab/access.rb index 6d0e30e916f..831f1e635ba 100644 --- a/lib/gitlab/access.rb +++ b/lib/gitlab/access.rb @@ -5,6 +5,8 @@ # module Gitlab module Access + class AccessDeniedError < StandardError; end + GUEST = 10 REPORTER = 20 DEVELOPER = 30 diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb index 7e3f5abba62..ab7b811c5d8 100644 --- a/lib/gitlab/backend/grack_auth.rb +++ b/lib/gitlab/backend/grack_auth.rb @@ -31,7 +31,7 @@ module Grack auth! - lfs_response = Gitlab::Lfs::Router.new(project, @user, @request).try_call + lfs_response = Gitlab::Lfs::Router.new(project, @user, @ci, @request).try_call return lfs_response unless lfs_response.nil? if @user.nil? && !@ci diff --git a/lib/gitlab/gitignore.rb b/lib/gitlab/gitignore.rb deleted file mode 100644 index f46b43b61a4..00000000000 --- a/lib/gitlab/gitignore.rb +++ /dev/null @@ -1,56 +0,0 @@ -module Gitlab - class Gitignore - FILTER_REGEX = /\.gitignore\z/.freeze - - def initialize(path) - @path = path - end - - def name - File.basename(@path, '.gitignore') - end - - def content - File.read(@path) - end - - class << self - def all - languages_frameworks + global - end - - def find(key) - file_name = "#{key}.gitignore" - - directory = select_directory(file_name) - directory ? new(File.join(directory, file_name)) : nil - end - - def global - files_for_folder(global_dir).map { |file| new(File.join(global_dir, file)) } - end - - def languages_frameworks - files_for_folder(gitignore_dir).map { |file| new(File.join(gitignore_dir, file)) } - end - - private - - def select_directory(file_name) - [gitignore_dir, global_dir].find { |dir| File.exist?(File.join(dir, file_name)) } - end - - def global_dir - File.join(gitignore_dir, 'Global') - end - - def gitignore_dir - Rails.root.join('vendor/gitignore') - end - - def files_for_folder(dir) - Dir.glob("#{dir.to_s}/*.gitignore").map { |file| file.gsub(FILTER_REGEX, '') } - end - end - end -end diff --git a/lib/gitlab/lfs/response.rb b/lib/gitlab/lfs/response.rb index 9d9617761b3..e3ed2f6791d 100644 --- a/lib/gitlab/lfs/response.rb +++ b/lib/gitlab/lfs/response.rb @@ -2,10 +2,11 @@ module Gitlab module Lfs class Response - def initialize(project, user, request) + def initialize(project, user, ci, request) @origin_project = project @project = storage_project(project) @user = user + @ci = ci @env = request.env @request = request end @@ -189,7 +190,7 @@ module Gitlab return render_not_enabled unless Gitlab.config.lfs.enabled unless @project.public? - return render_unauthorized unless @user + return render_unauthorized unless @user || @ci return render_forbidden unless user_can_fetch? end @@ -210,7 +211,7 @@ module Gitlab def user_can_fetch? # Check user access against the project they used to initiate the pull - @user.can?(:download_code, @origin_project) + @ci || @user.can?(:download_code, @origin_project) end def user_can_push? diff --git a/lib/gitlab/lfs/router.rb b/lib/gitlab/lfs/router.rb index 78d02891102..69bd5e62305 100644 --- a/lib/gitlab/lfs/router.rb +++ b/lib/gitlab/lfs/router.rb @@ -1,9 +1,12 @@ module Gitlab module Lfs class Router - def initialize(project, user, request) + attr_reader :project, :user, :ci, :request + + def initialize(project, user, ci, request) @project = project @user = user + @ci = ci @env = request.env @request = request end @@ -80,7 +83,7 @@ module Gitlab def lfs return unless @project - Gitlab::Lfs::Response.new(@project, @user, @request) + Gitlab::Lfs::Response.new(@project, @user, @ci, @request) end def sanitize_tmp_filename(name) diff --git a/lib/gitlab/template/base_template.rb b/lib/gitlab/template/base_template.rb new file mode 100644 index 00000000000..760ff3e614a --- /dev/null +++ b/lib/gitlab/template/base_template.rb @@ -0,0 +1,67 @@ +module Gitlab + module Template + class BaseTemplate + def initialize(path) + @path = path + end + + def name + File.basename(@path, self.class.extension) + end + + def content + File.read(@path) + end + + class << self + def all + self.categories.keys.flat_map { |cat| by_category(cat) } + end + + def find(key) + file_name = "#{key}#{self.extension}" + + directory = select_directory(file_name) + directory ? new(File.join(category_directory(directory), file_name)) : nil + end + + def categories + raise NotImplementedError + end + + def extension + raise NotImplementedError + end + + def base_dir + raise NotImplementedError + end + + def by_category(category) + templates_for_directory(category_directory(category)) + end + + def category_directory(category) + File.join(base_dir, categories[category]) + end + + private + + def select_directory(file_name) + categories.keys.find do |category| + File.exist?(File.join(category_directory(category), file_name)) + end + end + + def templates_for_directory(dir) + dir << '/' unless dir.end_with?('/') + Dir.glob(File.join(dir, "*#{self.extension}")).select { |f| f =~ filter_regex }.map { |f| new(f) } + end + + def filter_regex + @filter_reges ||= /#{Regexp.escape(extension)}\z/ + end + end + end + end +end diff --git a/lib/gitlab/template/gitignore.rb b/lib/gitlab/template/gitignore.rb new file mode 100644 index 00000000000..964fbfd4de3 --- /dev/null +++ b/lib/gitlab/template/gitignore.rb @@ -0,0 +1,22 @@ +module Gitlab + module Template + class Gitignore < BaseTemplate + class << self + def extension + '.gitignore' + end + + def categories + { + "Languages" => '', + "Global" => 'Global' + } + end + + def base_dir + Rails.root.join('vendor/gitignore') + end + end + end + end +end diff --git a/lib/gitlab/template/gitlab_ci_yml.rb b/lib/gitlab/template/gitlab_ci_yml.rb new file mode 100644 index 00000000000..7f480fe33c0 --- /dev/null +++ b/lib/gitlab/template/gitlab_ci_yml.rb @@ -0,0 +1,27 @@ +module Gitlab + module Template + class GitlabCiYml < BaseTemplate + def content + explanation = "# This file is a template, and might need editing before it works on your project." + [explanation, super].join("\n") + end + + class << self + def extension + '.gitlab-ci.yml' + end + + def categories + { + "General" => '', + "Pages" => 'Pages' + } + end + + def base_dir + Rails.root.join('vendor/gitlab-ci-yml') + end + end + end + end +end diff --git a/lib/tasks/gitlab/update_gitignore.rake b/lib/tasks/gitlab/update_gitignore.rake deleted file mode 100644 index 4fd48cccb1d..00000000000 --- a/lib/tasks/gitlab/update_gitignore.rake +++ /dev/null @@ -1,46 +0,0 @@ -namespace :gitlab do - desc "GitLab | Update gitignore" - task :update_gitignore do - unless clone_gitignores - puts "Cloning the gitignores failed".color(:red) - return - end - - remove_unneeded_files(gitignore_directory) - remove_unneeded_files(global_directory) - - puts "Done".color(:green) - end - - def clone_gitignores - FileUtils.rm_rf(gitignore_directory) if Dir.exist?(gitignore_directory) - FileUtils.cd vendor_directory - - system('git clone --depth=1 --branch=master https://github.com/github/gitignore.git') - end - - # Retain only certain files: - # - The LICENSE, because we have to - # - The sub dir global - # - The gitignores themself - # - Dir.entires returns also the entries '.' and '..' - def remove_unneeded_files(path) - Dir.foreach(path) do |file| - FileUtils.rm_rf(File.join(path, file)) unless file =~ /(\.{1,2}|LICENSE|Global|\.gitignore)\z/ - end - end - - private - - def vendor_directory - Rails.root.join('vendor') - end - - def gitignore_directory - File.join(vendor_directory, 'gitignore') - end - - def global_directory - File.join(gitignore_directory, 'Global') - end -end diff --git a/lib/tasks/gitlab/update_templates.rake b/lib/tasks/gitlab/update_templates.rake new file mode 100644 index 00000000000..4f76dad7286 --- /dev/null +++ b/lib/tasks/gitlab/update_templates.rake @@ -0,0 +1,54 @@ +namespace :gitlab do + desc "GitLab | Update templates" + task :update_templates do + TEMPLATE_DATA.each { |template| update(template) } + end + + def update(template) + sub_dir = template.repo_url.match(/([a-z-]+)\.git\z/)[1] + dir = File.join(vendor_directory, sub_dir) + + unless clone_repository(template.repo_url, dir) + puts "Cloning the #{sub_dir} templates failed".red + return + end + + remove_unneeded_files(dir, template.cleanup_regex) + puts "Done".green + end + + def clone_repository(url, directory) + FileUtils.rm_rf(directory) if Dir.exist?(directory) + + system("git clone #{url} --depth=1 --branch=master #{directory}") + end + + # Retain only certain files: + # - The LICENSE, because we have to + # - The sub dirs so we can organise the file by category + # - The templates themself + # - Dir.entries returns also the entries '.' and '..' + def remove_unneeded_files(directory, regex) + Dir.foreach(directory) do |file| + FileUtils.rm_rf(File.join(directory, file)) unless file =~ regex + end + end + + private + + Template = Struct.new(:repo_url, :cleanup_regex) + TEMPLATE_DATA = [ + Template.new( + "https://github.com/github/gitignore.git", + /(\.{1,2}|LICENSE|Global|\.gitignore)\z/ + ), + Template.new( + "https://gitlab.com/gitlab-org/gitlab-ci-yml.git", + /(\.{1,2}|LICENSE|Pages|\.gitlab-ci.yml)\z/ + ) + ] + + def vendor_directory + Rails.root.join('vendor') + end +end diff --git a/spec/controllers/groups/group_members_controller_spec.rb b/spec/controllers/groups/group_members_controller_spec.rb index 89c2c26a367..c8601341d54 100644 --- a/spec/controllers/groups/group_members_controller_spec.rb +++ b/spec/controllers/groups/group_members_controller_spec.rb @@ -118,9 +118,7 @@ describe Groups::GroupMembersController do it 'cannot removes himself from the group' do delete :leave, group_id: group - expect(response).to redirect_to(group_path(group)) - expect(response).to set_flash[:alert].to "You can not leave the \"#{group.name}\" group. Transfer or delete the group." - expect(group.users).to include user + expect(response.status).to eq(403) end end @@ -134,7 +132,7 @@ describe Groups::GroupMembersController do delete :leave, group_id: group expect(response).to set_flash.to 'Your access request to the group has been withdrawn.' - expect(response).to redirect_to(dashboard_groups_path) + expect(response).to redirect_to(group_path(group)) expect(group.members.request).to be_empty expect(group.users).not_to include user end diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb index fc5f458e795..e5e750c855f 100644 --- a/spec/controllers/projects/project_members_controller_spec.rb +++ b/spec/controllers/projects/project_members_controller_spec.rb @@ -171,11 +171,7 @@ describe Projects::ProjectMembersController do delete :leave, namespace_id: project.namespace, project_id: project - expect(response).to redirect_to( - namespace_project_path(project.namespace, project) - ) - expect(response).to set_flash[:alert].to "You can not leave the \"#{project.human_name}\" project. Transfer or delete the project." - expect(project.users).to include user + expect(response.status).to eq(403) end end @@ -190,7 +186,7 @@ describe Projects::ProjectMembersController do project_id: project expect(response).to set_flash.to 'Your access request to the project has been withdrawn.' - expect(response).to redirect_to(dashboard_projects_path) + expect(response).to redirect_to(namespace_project_path(project.namespace, project)) expect(project.members.request).to be_empty expect(project.users).not_to include user end diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index fba545560c7..146b2c2e131 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -237,4 +237,24 @@ describe ProjectsController do expect(response.status).to eq(401) end end + + describe "GET refs" do + it "should get a list of branches and tags" do + get :refs, namespace_id: public_project.namespace.path, id: public_project.path + + parsed_body = JSON.parse(response.body) + expect(parsed_body["Branches"]).to include("master") + expect(parsed_body["Tags"]).to include("v1.0.0") + expect(parsed_body["Commits"]).to be_nil + end + + it "should get a list of branches, tags and commits" do + get :refs, namespace_id: public_project.namespace.path, id: public_project.path, ref: "123456" + + parsed_body = JSON.parse(response.body) + expect(parsed_body["Branches"]).to include("master") + expect(parsed_body["Tags"]).to include("v1.0.0") + expect(parsed_body["Commits"]).to include("123456") + end + end end diff --git a/spec/factories/ci/commits.rb b/spec/factories/ci/pipelines.rb index a039bef6f3c..a039bef6f3c 100644 --- a/spec/factories/ci/commits.rb +++ b/spec/factories/ci/pipelines.rb diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb index 9499cd4e025..2d297776cb0 100644 --- a/spec/features/admin/admin_runners_spec.rb +++ b/spec/features/admin/admin_runners_spec.rb @@ -60,6 +60,40 @@ describe "Admin Runners" do it { expect(page).to have_content(@project1.name_with_namespace) } it { expect(page).not_to have_content(@project2.name_with_namespace) } end + + describe 'enable/create' do + before do + @project1.runners << runner + visit admin_runner_path(runner) + end + + it 'enables specific runner for project' do + within '.unassigned-projects' do + click_on 'Enable' + end + + assigned_project = page.find('.assigned-projects') + + expect(assigned_project).to have_content(@project2.path) + end + end + + describe 'disable/destroy' do + before do + @project1.runners << runner + visit admin_runner_path(runner) + end + + it 'enables specific runner for project' do + within '.assigned-projects' do + click_on 'Disable' + end + + new_runner_project = page.find('.unassigned-projects') + + expect(new_runner_project).to have_content(@project1.path) + end + end end describe 'runners registration token' do diff --git a/spec/features/container_registry_spec.rb b/spec/features/container_registry_spec.rb index 53b4f027117..203e55a36f2 100644 --- a/spec/features/container_registry_spec.rb +++ b/spec/features/container_registry_spec.rb @@ -26,7 +26,8 @@ describe "Container Registry" do end context 'when there are tags' do - it { expect(page).to have_content(tag_name)} + it { expect(page).to have_content(tag_name) } + it { expect(page).to have_content('d7a513a66') } end end diff --git a/spec/features/environments_spec.rb b/spec/features/environments_spec.rb index 40fea5211e9..7fb28f4174b 100644 --- a/spec/features/environments_spec.rb +++ b/spec/features/environments_spec.rb @@ -20,7 +20,7 @@ feature 'Environments', feature: true do context 'without environments' do scenario 'does show no environments' do - expect(page).to have_content('No environments to show') + expect(page).to have_content('You don\'t have any environments right now.') end end @@ -61,7 +61,7 @@ feature 'Environments', feature: true do context 'without deployments' do scenario 'does show no deployments' do - expect(page).to have_content('No deployments for') + expect(page).to have_content('You don\'t have any deployments right now.') end end @@ -108,7 +108,7 @@ feature 'Environments', feature: true do end scenario 'does create a new pipeline' do - expect(page).to have_content('production') + expect(page).to have_content('Production') end end diff --git a/spec/features/groups/members/last_owner_cannot_leave_group_spec.rb b/spec/features/groups/members/last_owner_cannot_leave_group_spec.rb new file mode 100644 index 00000000000..33bf6d3752f --- /dev/null +++ b/spec/features/groups/members/last_owner_cannot_leave_group_spec.rb @@ -0,0 +1,16 @@ +require 'spec_helper' + +feature 'Groups > Members > Last owner cannot leave group', feature: true do + let(:owner) { create(:user) } + let(:group) { create(:group) } + + background do + group.add_owner(owner) + login_as(owner) + visit group_path(group) + end + + scenario 'user does not see a "Leave Group" link' do + expect(page).not_to have_content 'Leave Group' + end +end diff --git a/spec/features/groups/members/member_leaves_group_spec.rb b/spec/features/groups/members/member_leaves_group_spec.rb new file mode 100644 index 00000000000..3185ff924b9 --- /dev/null +++ b/spec/features/groups/members/member_leaves_group_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +feature 'Groups > Members > Member leaves group', feature: true do + let(:user) { create(:user) } + let(:owner) { create(:user) } + let(:group) { create(:group, :public) } + + background do + group.add_owner(owner) + group.add_developer(user) + login_as(user) + visit group_path(group) + end + + scenario 'user leaves group' do + click_link 'Leave Group' + + expect(current_path).to eq(dashboard_groups_path) + expect(group.users.exists?(user.id)).to be_falsey + end +end diff --git a/spec/features/groups/members/owner_manages_access_requests_spec.rb b/spec/features/groups/members/owner_manages_access_requests_spec.rb index 22525ce530b..321c9bad7d0 100644 --- a/spec/features/groups/members/owner_manages_access_requests_spec.rb +++ b/spec/features/groups/members/owner_manages_access_requests_spec.rb @@ -42,7 +42,7 @@ feature 'Groups > Members > Owner manages access requests', feature: true do def expect_visible_access_request(group, user) expect(group.members.request.exists?(user_id: user)).to be_truthy - expect(page).to have_content "#{group.name} access requests (1)" + expect(page).to have_content "#{group.name} access requests 1" expect(page).to have_content user.name end end diff --git a/spec/features/groups/members/user_requests_access_spec.rb b/spec/features/groups/members/user_requests_access_spec.rb index a878a96b6ee..1ea607cbca0 100644 --- a/spec/features/groups/members/user_requests_access_spec.rb +++ b/spec/features/groups/members/user_requests_access_spec.rb @@ -21,6 +21,7 @@ feature 'Groups > Members > User requests access', feature: true do expect(page).to have_content 'Your request for access has been queued for review.' expect(page).to have_content 'Withdraw Access Request' + expect(page).not_to have_content 'Leave Group' end scenario 'user is not listed in the group members page' do diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index c3cb3379440..5065dfb849c 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -22,7 +22,7 @@ describe 'Issues', feature: true do before do visit edit_namespace_project_issue_path(project.namespace, project, issue) - click_button "Go full screen" + find('.js-zen-enter').click end it 'should open new issue popup' do diff --git a/spec/features/projects/badges/list_spec.rb b/spec/features/projects/badges/list_spec.rb index 51be81d634c..01e90618a98 100644 --- a/spec/features/projects/badges/list_spec.rb +++ b/spec/features/projects/badges/list_spec.rb @@ -1,8 +1,6 @@ require 'spec_helper' feature 'list of badges' do - include Select2Helper - background do user = create(:user) project = create(:project) @@ -24,7 +22,11 @@ feature 'list of badges' do end scenario 'user changes current ref on badges list page', js: true do - select2('improve/awesome', from: '#ref') + first('.js-project-refs-dropdown').click + + page.within '.project-refs-form' do + click_link 'improve/awesome' + end expect(page).to have_content 'badges/improve/awesome/build.svg' end diff --git a/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb b/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb new file mode 100644 index 00000000000..d516e8ce55a --- /dev/null +++ b/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' + +feature 'User wants to add a .gitlab-ci.yml file', feature: true do + include WaitForAjax + + before do + user = create(:user) + project = create(:project) + project.team << [user, :master] + login_as user + visit namespace_project_new_blob_path(project.namespace, project, 'master', file_name: '.gitlab-ci.yml') + end + + scenario 'user can see .gitlab-ci.yml dropdown' do + expect(page).to have_css('.gitlab-ci-yml-selector') + end + + scenario 'user can pick a template from the dropdown', js: true do + find('.js-gitlab-ci-yml-selector').click + wait_for_ajax + within '.gitlab-ci-yml-selector' do + find('.dropdown-input-field').set('jekyll') + find('.dropdown-content li', text: 'jekyll').click + end + wait_for_ajax + + expect(page).to have_content('This file is a template, and might need editing before it works on your project') + expect(page).to have_content('jekyll build -d test') + end +end diff --git a/spec/features/projects/members/master_manages_access_requests_spec.rb b/spec/features/projects/members/master_manages_access_requests_spec.rb index 5fe4caa12f0..aa2d906fa2e 100644 --- a/spec/features/projects/members/master_manages_access_requests_spec.rb +++ b/spec/features/projects/members/master_manages_access_requests_spec.rb @@ -41,7 +41,7 @@ feature 'Projects > Members > Master manages access requests', feature: true do def expect_visible_access_request(project, user) expect(project.members.request.exists?(user_id: user)).to be_truthy - expect(page).to have_content "#{project.name} access requests (1)" + expect(page).to have_content "#{project.name} access requests 1" expect(page).to have_content user.name end end diff --git a/spec/features/projects/members/member_leaves_project_spec.rb b/spec/features/projects/members/member_leaves_project_spec.rb new file mode 100644 index 00000000000..79dec442818 --- /dev/null +++ b/spec/features/projects/members/member_leaves_project_spec.rb @@ -0,0 +1,19 @@ +require 'spec_helper' + +feature 'Projects > Members > Member leaves project', feature: true do + let(:user) { create(:user) } + let(:project) { create(:project) } + + background do + project.team << [user, :developer] + login_as(user) + visit namespace_project_path(project.namespace, project) + end + + scenario 'user leaves project' do + click_link 'Leave Project' + + expect(current_path).to eq(dashboard_projects_path) + expect(project.users.exists?(user.id)).to be_falsey + end +end diff --git a/spec/features/projects/members/owner_cannot_leave_project_spec.rb b/spec/features/projects/members/owner_cannot_leave_project_spec.rb new file mode 100644 index 00000000000..67811b1048e --- /dev/null +++ b/spec/features/projects/members/owner_cannot_leave_project_spec.rb @@ -0,0 +1,16 @@ +require 'spec_helper' + +feature 'Projects > Members > Owner cannot leave project', feature: true do + let(:owner) { create(:user) } + let(:project) { create(:project) } + + background do + project.team << [owner, :owner] + login_as(owner) + visit namespace_project_path(project.namespace, project) + end + + scenario 'user does not see a "Leave Project" link' do + expect(page).not_to have_content 'Leave Project' + end +end diff --git a/spec/features/projects/members/user_requests_access_spec.rb b/spec/features/projects/members/user_requests_access_spec.rb index fd92a3a2f0c..af420c170ef 100644 --- a/spec/features/projects/members/user_requests_access_spec.rb +++ b/spec/features/projects/members/user_requests_access_spec.rb @@ -21,6 +21,7 @@ feature 'Projects > Members > User requests access', feature: true do expect(page).to have_content 'Your request for access has been queued for review.' expect(page).to have_content 'Withdraw Access Request' + expect(page).not_to have_content 'Leave Project' end scenario 'user is not listed in the project members page' do diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb index 9dd0378d165..6fa8298d489 100644 --- a/spec/features/projects_spec.rb +++ b/spec/features/projects_spec.rb @@ -70,22 +70,6 @@ feature 'Project', feature: true do end end - describe 'leave project link' do - let(:user) { create(:user) } - let(:project) { create(:project, namespace: user.namespace) } - - before do - login_with(user) - project.team.add_user(user, Gitlab::Access::MASTER) - visit namespace_project_path(project.namespace, project) - end - - it 'click project-settings and find leave project' do - find('#project-settings-button').click - expect(page).to have_link('Leave Project') - end - end - describe 'project title' do include WaitForAjax diff --git a/spec/fixtures/container_registry/tag_manifest.json b/spec/fixtures/container_registry/tag_manifest.json index 1b6008e2872..8d1b874c29b 100644 --- a/spec/fixtures/container_registry/tag_manifest.json +++ b/spec/fixtures/container_registry/tag_manifest.json @@ -1 +1,16 @@ -{"schemaVersion":2,"mediaType":"application/vnd.docker.distribution.manifest.v2+json","config":{"mediaType":"application/octet-stream","size":1145,"digest":"sha256:d7a513a663c1a6dcdba9ed832ca53c02ac2af0c333322cd6ca92936d1d9917ac"},"layers":[{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","size":2319870,"digest":"sha256:420890c9e918b6668faaedd9000e220190f2493b0693ee563ebd7b4cc754a57d"}]} +{ + "schemaVersion": 2, + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "config": { + "mediaType": "application/octet-stream", + "size": 1145, + "digest": "sha256:d7a513a663c1a6dcdba9ed832ca53c02ac2af0c333322cd6ca92936d1d9917ac" + }, + "layers": [ + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 2319870, + "digest": "sha256:420890c9e918b6668faaedd9000e220190f2493b0693ee563ebd7b4cc754a57d" + } + ] +} diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index f6c1005d265..bb28866f010 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -174,51 +174,6 @@ describe ApplicationHelper do end end - describe 'grouped_options_refs' do - let(:options) { helper.grouped_options_refs } - let(:project) { create(:project) } - - before do - assign(:project, project) - - # Override Rails' grouped_options_for_select helper to just return the - # first argument (`options`), since it's easier to work with than the - # generated HTML. - allow(helper).to receive(:grouped_options_for_select). - and_wrap_original { |_, *args| args.first } - end - - it 'includes a list of branch names' do - expect(options[0][0]).to eq('Branches') - expect(options[0][1]).to include('master', 'feature') - end - - it 'includes a list of tag names' do - expect(options[1][0]).to eq('Tags') - expect(options[1][1]).to include('v1.0.0', 'v1.1.0') - end - - it 'includes a specific commit ref if defined' do - # Must be an instance variable - ref = '2ed06dc41dbb5936af845b87d79e05bbf24c73b8' - assign(:ref, ref) - - expect(options[2][0]).to eq('Commit') - expect(options[2][1]).to eq([ref]) - end - - it 'sorts tags in a natural order' do - # Stub repository.tag_names to make sure we get some valid testing data - expect(project.repository).to receive(:tag_names). - and_return(['v1.0.9', 'v1.0.10', 'v2.0', 'v3.1.4.2', 'v2.0rc1¿', - 'v1.0.9a', 'v2.0-rc1', 'v2.0rc2']) - - expect(options[1][1]). - to eq(['v3.1.4.2', 'v2.0', 'v2.0rc2', 'v2.0rc1¿', 'v2.0-rc1', 'v1.0.10', - 'v1.0.9', 'v1.0.9a']) - end - end - describe 'simple_sanitize' do let(:a_tag) { '<a href="#">Foo</a>' } diff --git a/spec/javascripts/issue_spec.js.coffee b/spec/javascripts/issue_spec.js.coffee index ea27f36e9b5..71f0c1076c5 100644 --- a/spec/javascripts/issue_spec.js.coffee +++ b/spec/javascripts/issue_spec.js.coffee @@ -1,3 +1,4 @@ +#= require lib/text_utility #= require issue describe 'Issue', -> @@ -38,7 +39,7 @@ describe 'reopen/close issue', -> expect(typeof $btnClose.prop('disabled')).toBe('undefined') $btnClose.trigger('click') - + expect($btnReopen).toBeVisible() expect($btnClose).toBeHidden() expect($('div.status-box-closed')).toBeVisible() @@ -50,7 +51,7 @@ describe 'reopen/close issue', -> expect(req.type).toBe('PUT') expect(req.url).toBe('http://goesnowhere.nothing/whereami') req.success saved: false - + $btnClose = $('a.btn-close') $btnReopen = $('a.btn-reopen') $btnClose.attr('href','http://goesnowhere.nothing/whereami') @@ -59,7 +60,7 @@ describe 'reopen/close issue', -> expect(typeof $btnClose.prop('disabled')).toBe('undefined') $btnClose.trigger('click') - + expect($btnReopen).toBeHidden() expect($btnClose).toBeVisible() expect($('div.status-box-closed')).toBeHidden() @@ -73,7 +74,7 @@ describe 'reopen/close issue', -> expect(req.type).toBe('PUT') expect(req.url).toBe('http://goesnowhere.nothing/whereami') req.error() - + $btnClose = $('a.btn-close') $btnReopen = $('a.btn-reopen') $btnClose.attr('href','http://goesnowhere.nothing/whereami') @@ -82,7 +83,7 @@ describe 'reopen/close issue', -> expect(typeof $btnClose.prop('disabled')).toBe('undefined') $btnClose.trigger('click') - + expect($btnReopen).toBeHidden() expect($btnClose).toBeVisible() expect($('div.status-box-closed')).toBeHidden() @@ -105,4 +106,4 @@ describe 'reopen/close issue', -> expect($btnReopen).toBeHidden() expect($btnClose).toBeVisible() expect($('div.status-box-open')).toBeVisible() - expect($('div.status-box-closed')).toBeHidden()
\ No newline at end of file + expect($('div.status-box-closed')).toBeHidden() diff --git a/spec/lib/banzai/filter/relative_link_filter_spec.rb b/spec/lib/banzai/filter/relative_link_filter_spec.rb index 0e6685f0ffb..b9e4a4eaf0e 100644 --- a/spec/lib/banzai/filter/relative_link_filter_spec.rb +++ b/spec/lib/banzai/filter/relative_link_filter_spec.rb @@ -132,11 +132,8 @@ describe Banzai::Filter::RelativeLinkFilter, lib: true do path = 'files/images/한글.png' escaped = Addressable::URI.escape(path) - # Stub these methods so the file doesn't actually need to be in the repo - allow_any_instance_of(described_class). - to receive(:file_exists?).and_return(true) - allow_any_instance_of(described_class). - to receive(:image?).with(path).and_return(true) + # Stub this method so the file doesn't actually need to be in the repo + allow_any_instance_of(described_class).to receive(:uri_type).and_return(:raw) doc = filter(image(escaped)) expect(doc.at_css('img')['src']).to match '/raw/' diff --git a/spec/lib/gitlab/lfs/lfs_router_spec.rb b/spec/lib/gitlab/lfs/lfs_router_spec.rb index 88814bc474d..659facd6c19 100644 --- a/spec/lib/gitlab/lfs/lfs_router_spec.rb +++ b/spec/lib/gitlab/lfs/lfs_router_spec.rb @@ -17,12 +17,15 @@ describe Gitlab::Lfs::Router, lib: true do } end - let(:lfs_router_auth) { new_lfs_router(project, user) } - let(:lfs_router_noauth) { new_lfs_router(project, nil) } - let(:lfs_router_public_auth) { new_lfs_router(public_project, user) } - let(:lfs_router_public_noauth) { new_lfs_router(public_project, nil) } - let(:lfs_router_forked_noauth) { new_lfs_router(forked_project, nil) } - let(:lfs_router_forked_auth) { new_lfs_router(forked_project, user_two) } + let(:lfs_router_auth) { new_lfs_router(project, user: user) } + let(:lfs_router_ci_auth) { new_lfs_router(project, ci: true) } + let(:lfs_router_noauth) { new_lfs_router(project) } + let(:lfs_router_public_auth) { new_lfs_router(public_project, user: user) } + let(:lfs_router_public_ci_auth) { new_lfs_router(public_project, ci: true) } + let(:lfs_router_public_noauth) { new_lfs_router(public_project) } + let(:lfs_router_forked_noauth) { new_lfs_router(forked_project) } + let(:lfs_router_forked_auth) { new_lfs_router(forked_project, user: user_two) } + let(:lfs_router_forked_ci_auth) { new_lfs_router(forked_project, ci: true) } let(:sample_oid) { "b68143e6463773b1b6c6fd009a76c32aeec041faff32ba2ed42fd7f708a17f80" } let(:sample_size) { 499013 } @@ -80,6 +83,7 @@ describe Gitlab::Lfs::Router, lib: true do context 'with required headers' do before do + project.lfs_objects << lfs_object env['HTTP_X_SENDFILE_TYPE'] = "X-Sendfile" end @@ -91,7 +95,6 @@ describe Gitlab::Lfs::Router, lib: true do context 'when user has project access' do before do - project.lfs_objects << lfs_object project.team << [user, :master] end @@ -104,6 +107,17 @@ describe Gitlab::Lfs::Router, lib: true do expect(lfs_router_auth.try_call[1]['X-Sendfile']).to eq(lfs_object.file.path) end end + + context 'when CI is authorized' do + it "responds with status 200" do + expect(lfs_router_ci_auth.try_call.first).to eq(200) + end + + it "responds with the file location" do + expect(lfs_router_ci_auth.try_call[1]['Content-Type']).to eq("application/octet-stream") + expect(lfs_router_ci_auth.try_call[1]['X-Sendfile']).to eq(lfs_object.file.path) + end + end end context 'without required headers' do @@ -134,143 +148,145 @@ describe Gitlab::Lfs::Router, lib: true do end describe 'download' do - describe 'when user is authenticated' do - before do - body = { 'operation' => 'download', - 'objects' => [ - { 'oid' => sample_oid, - 'size' => sample_size - }] - }.to_json - env['rack.input'] = StringIO.new(body) - end + before do + body = { 'operation' => 'download', + 'objects' => [ + { 'oid' => sample_oid, + 'size' => sample_size + }] + }.to_json + env['rack.input'] = StringIO.new(body) + end - describe 'when user has download access' do + shared_examples 'an authorized requests' do + context 'when downloading an lfs object that is assigned to our project' do before do - @auth = authorize(user) - env["HTTP_AUTHORIZATION"] = @auth - project.team << [user, :reporter] + project.lfs_objects << lfs_object end - context 'when downloading an lfs object that is assigned to our project' do - before do - project.lfs_objects << lfs_object - end - - it 'responds with status 200 and href to download' do - response = lfs_router_auth.try_call - expect(response.first).to eq(200) - response_body = ActiveSupport::JSON.decode(response.last.first) + it 'responds with status 200 and href to download' do + response = router.try_call + expect(response.first).to eq(200) + response_body = ActiveSupport::JSON.decode(response.last.first) - expect(response_body).to eq('objects' => [ - { 'oid' => sample_oid, - 'size' => sample_size, - 'actions' => { - 'download' => { - 'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", - 'header' => { 'Authorization' => @auth } - } + expect(response_body).to eq('objects' => [ + { 'oid' => sample_oid, + 'size' => sample_size, + 'actions' => { + 'download' => { + 'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", + 'header' => { 'Authorization' => auth } } - }]) - end + } + }]) end + end - context 'when downloading an lfs object that is assigned to other project' do - before do - public_project.lfs_objects << lfs_object - end + context 'when downloading an lfs object that is assigned to other project' do + before do + public_project.lfs_objects << lfs_object + end - it 'responds with status 200 and error message' do - response = lfs_router_auth.try_call - expect(response.first).to eq(200) - response_body = ActiveSupport::JSON.decode(response.last.first) + it 'responds with status 200 and error message' do + response = router.try_call + expect(response.first).to eq(200) + response_body = ActiveSupport::JSON.decode(response.last.first) - expect(response_body).to eq('objects' => [ - { 'oid' => sample_oid, - 'size' => sample_size, - 'error' => { - 'code' => 404, - 'message' => "Object does not exist on the server or you don't have permissions to access it", - } - }]) - end + expect(response_body).to eq('objects' => [ + { 'oid' => sample_oid, + 'size' => sample_size, + 'error' => { + 'code' => 404, + 'message' => "Object does not exist on the server or you don't have permissions to access it", + } + }]) end + end - context 'when downloading a lfs object that does not exist' do - before do - body = { 'operation' => 'download', - 'objects' => [ - { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', - 'size' => 1575078 - }] - }.to_json - env['rack.input'] = StringIO.new(body) - end + context 'when downloading a lfs object that does not exist' do + before do + body = { 'operation' => 'download', + 'objects' => [ + { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', + 'size' => 1575078 + }] + }.to_json + env['rack.input'] = StringIO.new(body) + end - it "responds with status 200 and error message" do - response = lfs_router_auth.try_call - expect(response.first).to eq(200) - response_body = ActiveSupport::JSON.decode(response.last.first) + it "responds with status 200 and error message" do + response = router.try_call + expect(response.first).to eq(200) + response_body = ActiveSupport::JSON.decode(response.last.first) - expect(response_body).to eq('objects' => [ - { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', - 'size' => 1575078, - 'error' => { - 'code' => 404, - 'message' => "Object does not exist on the server or you don't have permissions to access it", - } - }]) - end + expect(response_body).to eq('objects' => [ + { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', + 'size' => 1575078, + 'error' => { + 'code' => 404, + 'message' => "Object does not exist on the server or you don't have permissions to access it", + } + }]) end + end - context 'when downloading one new and one existing lfs object' do - before do - body = { 'operation' => 'download', - 'objects' => [ - { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', - 'size' => 1575078 - }, - { 'oid' => sample_oid, - 'size' => sample_size - } - ] - }.to_json - env['rack.input'] = StringIO.new(body) - project.lfs_objects << lfs_object - end + context 'when downloading one new and one existing lfs object' do + before do + body = { 'operation' => 'download', + 'objects' => [ + { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', + 'size' => 1575078 + }, + { 'oid' => sample_oid, + 'size' => sample_size + } + ] + }.to_json + env['rack.input'] = StringIO.new(body) + project.lfs_objects << lfs_object + end - it "responds with status 200 with upload hypermedia link for the new object" do - response = lfs_router_auth.try_call - expect(response.first).to eq(200) - response_body = ActiveSupport::JSON.decode(response.last.first) + it "responds with status 200 with upload hypermedia link for the new object" do + response = router.try_call + expect(response.first).to eq(200) + response_body = ActiveSupport::JSON.decode(response.last.first) - expect(response_body).to eq('objects' => [ - { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', - 'size' => 1575078, - 'error' => { - 'code' => 404, - 'message' => "Object does not exist on the server or you don't have permissions to access it", - } - }, - { 'oid' => sample_oid, - 'size' => sample_size, - 'actions' => { - 'download' => { - 'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", - 'header' => { 'Authorization' => @auth } - } + expect(response_body).to eq('objects' => [ + { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', + 'size' => 1575078, + 'error' => { + 'code' => 404, + 'message' => "Object does not exist on the server or you don't have permissions to access it", + } + }, + { 'oid' => sample_oid, + 'size' => sample_size, + 'actions' => { + 'download' => { + 'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", + 'header' => { 'Authorization' => auth } } - }]) - end + } + }]) end end + end + + context 'when user is authenticated' do + let(:auth) { authorize(user) } + + before do + env["HTTP_AUTHORIZATION"] = auth + project.team << [user, role] + end + + it_behaves_like 'an authorized requests' do + let(:role) { :reporter } + let(:router) { lfs_router_auth } + end context 'when user does is not member of the project' do - before do - @auth = authorize(user) - env["HTTP_AUTHORIZATION"] = @auth - project.team << [user, :guest] - end + let(:role) { :guest } it 'responds with 403' do expect(lfs_router_auth.try_call.first).to eq(403) @@ -278,11 +294,7 @@ describe Gitlab::Lfs::Router, lib: true do end context 'when user does not have download access' do - before do - @auth = authorize(user) - env["HTTP_AUTHORIZATION"] = @auth - project.team << [user, :guest] - end + let(:role) { :guest } it 'responds with 403' do expect(lfs_router_auth.try_call.first).to eq(403) @@ -290,18 +302,19 @@ describe Gitlab::Lfs::Router, lib: true do end end - context 'when user is not authenticated' do + context 'when CI is authorized' do + let(:auth) { 'gitlab-ci-token:password' } + before do - body = { 'operation' => 'download', - 'objects' => [ - { 'oid' => sample_oid, - 'size' => sample_size - }], + env["HTTP_AUTHORIZATION"] = auth + end - }.to_json - env['rack.input'] = StringIO.new(body) + it_behaves_like 'an authorized requests' do + let(:router) { lfs_router_ci_auth } end + end + context 'when user is not authenticated' do describe 'is accessing public project' do before do public_project.lfs_objects << lfs_object @@ -338,17 +351,17 @@ describe Gitlab::Lfs::Router, lib: true do end describe 'upload' do - describe 'when user is authenticated' do - before do - body = { 'operation' => 'upload', - 'objects' => [ - { 'oid' => sample_oid, - 'size' => sample_size - }] - }.to_json - env['rack.input'] = StringIO.new(body) - end + before do + body = { 'operation' => 'upload', + 'objects' => [ + { 'oid' => sample_oid, + 'size' => sample_size + }] + }.to_json + env['rack.input'] = StringIO.new(body) + end + describe 'when request is authenticated' do describe 'when user has project push access' do before do @auth = authorize(user) @@ -440,15 +453,15 @@ describe Gitlab::Lfs::Router, lib: true do expect(lfs_router_auth.try_call.first).to eq(403) end end - end - context 'when user is not authenticated' do - before do - env['rack.input'] = StringIO.new( - { 'objects' => [], 'operation' => 'upload' }.to_json - ) + context 'when CI is authorized' do + it 'responds with 401' do + expect(lfs_router_ci_auth.try_call.first).to eq(401) + end end + end + context 'when user is not authenticated' do context 'when user has push access' do before do project.team << [user, :master] @@ -465,6 +478,18 @@ describe Gitlab::Lfs::Router, lib: true do end end end + + context 'when CI is authorized' do + let(:auth) { 'gitlab-ci-token:password' } + + before do + env["HTTP_AUTHORIZATION"] = auth + end + + it "responds with status 403" do + expect(lfs_router_public_ci_auth.try_call.first).to eq(401) + end + end end describe 'unsupported' do @@ -490,13 +515,68 @@ describe Gitlab::Lfs::Router, lib: true do env['REQUEST_METHOD'] = 'PUT' end - describe 'to one project' do - describe 'when user has push access to the project' do + shared_examples 'unauthorized' do + context 'and request is sent by gitlab-workhorse to authorize the request' do before do - project.team << [user, :master] + header_for_upload_authorize(router.project) + end + + it 'responds with status 401' do + expect(router.try_call.first).to eq(401) + end + end + + context 'and request is sent by gitlab-workhorse to finalize the upload' do + before do + headers_for_upload_finalize(router.project) + end + + it 'responds with status 401' do + expect(router.try_call.first).to eq(401) + end + end + + context 'and request is sent with a malformed headers' do + before do + env["PATH_INFO"] = "#{router.project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}" + env["HTTP_X_GITLAB_LFS_TMP"] = "cat /etc/passwd" + end + + it 'does not recognize it as a valid lfs command' do + expect(router.try_call).to eq(nil) + end + end + end + + shared_examples 'forbidden' do + context 'and request is sent by gitlab-workhorse to authorize the request' do + before do + header_for_upload_authorize(router.project) + end + + it 'responds with 403' do + expect(router.try_call.first).to eq(403) + end + end + + context 'and request is sent by gitlab-workhorse to finalize the upload' do + before do + headers_for_upload_finalize(router.project) + end + + it 'responds with 403' do + expect(router.try_call.first).to eq(403) end + end + end + + describe 'to one project' do + describe 'when user is authenticated' do + describe 'when user has push access to the project' do + before do + project.team << [user, :developer] + end - describe 'when user is authenticated' do context 'and request is sent by gitlab-workhorse to authorize the request' do before do header_for_upload_authorize(project) @@ -524,100 +604,35 @@ describe Gitlab::Lfs::Router, lib: true do end end - describe 'when user is unauthenticated' do - let(:lfs_router_noauth) { new_lfs_router(project, nil) } + describe 'and user does not have push access' do + let(:router) { lfs_router_auth } - context 'and request is sent by gitlab-workhorse to authorize the request' do - before do - header_for_upload_authorize(project) - end - - it 'responds with status 401' do - expect(lfs_router_noauth.try_call.first).to eq(401) - end - end - - context 'and request is sent by gitlab-workhorse to finalize the upload' do - before do - headers_for_upload_finalize(project) - end - - it 'responds with status 401' do - expect(lfs_router_noauth.try_call.first).to eq(401) - end - end - - context 'and request is sent with a malformed headers' do - before do - env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}" - env["HTTP_X_GITLAB_LFS_TMP"] = "cat /etc/passwd" - end - - it 'does not recognize it as a valid lfs command' do - expect(lfs_router_noauth.try_call).to eq(nil) - end - end + it_behaves_like 'forbidden' end end - describe 'and user does not have push access' do - describe 'when user is authenticated' do - context 'and request is sent by gitlab-workhorse to authorize the request' do - before do - header_for_upload_authorize(project) - end - - it 'responds with 403' do - expect(lfs_router_auth.try_call.first).to eq(403) - end - end - - context 'and request is sent by gitlab-workhorse to finalize the upload' do - before do - headers_for_upload_finalize(project) - end - - it 'responds with 403' do - expect(lfs_router_auth.try_call.first).to eq(403) - end - end - end + context 'when CI is authenticated' do + let(:router) { lfs_router_ci_auth } - describe 'when user is unauthenticated' do - let(:lfs_router_noauth) { new_lfs_router(project, nil) } - - context 'and request is sent by gitlab-workhorse to authorize the request' do - before do - header_for_upload_authorize(project) - end + it_behaves_like 'unauthorized' + end - it 'responds with 401' do - expect(lfs_router_noauth.try_call.first).to eq(401) - end - end + context 'for unauthenticated' do + let(:router) { new_lfs_router(project) } - context 'and request is sent by gitlab-workhorse to finalize the upload' do - before do - headers_for_upload_finalize(project) - end - - it 'responds with 401' do - expect(lfs_router_noauth.try_call.first).to eq(401) - end - end - end + it_behaves_like 'unauthorized' end end - describe "to a forked project" do + describe 'to a forked project' do let(:forked_project) { fork_project(public_project, user) } - describe 'when user has push access to the project' do - before do - forked_project.team << [user_two, :master] - end + describe 'when user is authenticated' do + describe 'when user has push access to the project' do + before do + forked_project.team << [user_two, :developer] + end - describe 'when user is authenticated' do context 'and request is sent by gitlab-workhorse to authorize the request' do before do header_for_upload_authorize(forked_project) @@ -645,78 +660,28 @@ describe Gitlab::Lfs::Router, lib: true do end end - describe 'when user is unauthenticated' do - context 'and request is sent by gitlab-workhorse to authorize the request' do - before do - header_for_upload_authorize(forked_project) - end - - it 'responds with status 401' do - expect(lfs_router_forked_noauth.try_call.first).to eq(401) - end - end - - context 'and request is sent by gitlab-workhorse to finalize the upload' do - before do - headers_for_upload_finalize(forked_project) - end + describe 'and user does not have push access' do + let(:router) { lfs_router_forked_auth } - it 'responds with status 401' do - expect(lfs_router_forked_noauth.try_call.first).to eq(401) - end - end + it_behaves_like 'forbidden' end end - describe 'and user does not have push access' do - describe 'when user is authenticated' do - context 'and request is sent by gitlab-workhorse to authorize the request' do - before do - header_for_upload_authorize(forked_project) - end + context 'when CI is authenticated' do + let(:router) { lfs_router_forked_ci_auth } - it 'responds with 403' do - expect(lfs_router_forked_auth.try_call.first).to eq(403) - end - end - - context 'and request is sent by gitlab-workhorse to finalize the upload' do - before do - headers_for_upload_finalize(forked_project) - end - - it 'responds with 403' do - expect(lfs_router_forked_auth.try_call.first).to eq(403) - end - end - end - - describe 'when user is unauthenticated' do - context 'and request is sent by gitlab-workhorse to authorize the request' do - before do - header_for_upload_authorize(forked_project) - end - - it 'responds with 401' do - expect(lfs_router_forked_noauth.try_call.first).to eq(401) - end - end + it_behaves_like 'unauthorized' + end - context 'and request is sent by gitlab-workhorse to finalize the upload' do - before do - headers_for_upload_finalize(forked_project) - end + context 'for unauthenticated' do + let(:router) { lfs_router_forked_noauth } - it 'responds with 401' do - expect(lfs_router_forked_noauth.try_call.first).to eq(401) - end - end - end + it_behaves_like 'unauthorized' end describe 'and second project not related to fork or a source project' do let(:second_project) { create(:project) } - let(:lfs_router_second_project) { new_lfs_router(second_project, user) } + let(:lfs_router_second_project) { new_lfs_router(second_project, user: user) } before do public_project.lfs_objects << lfs_object @@ -745,8 +710,8 @@ describe Gitlab::Lfs::Router, lib: true do ActionController::HttpAuthentication::Basic.encode_credentials(user.username, user.password) end - def new_lfs_router(project, user) - Gitlab::Lfs::Router.new(project, user, request) + def new_lfs_router(project, user: nil, ci: false) + Gitlab::Lfs::Router.new(project, user, ci, request) end def header_for_upload_authorize(project) diff --git a/spec/lib/gitlab/gitignore_spec.rb b/spec/lib/gitlab/template/gitignore_spec.rb index 72baa516cc4..bc0ec9325cc 100644 --- a/spec/lib/gitlab/gitignore_spec.rb +++ b/spec/lib/gitlab/template/gitignore_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' -describe Gitlab::Gitignore do - subject { Gitlab::Gitignore } +describe Gitlab::Template::Gitignore do + subject { described_class } describe '.all' do it 'strips the gitignore suffix' do @@ -24,7 +24,7 @@ describe Gitlab::Gitignore do it 'returns the Gitignore object of a valid file' do ruby = subject.find('Ruby') - expect(ruby).to be_a Gitlab::Gitignore + expect(ruby).to be_a Gitlab::Template::Gitignore expect(ruby.name).to eq('Ruby') end end diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 1e6eb20ab39..ae55a01ebea 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -401,23 +401,56 @@ describe Notify do end describe 'project access requested' do - let(:project) { create(:project) } - let(:user) { create(:user) } - let(:project_member) do - project.request_access(user) - project.members.request.find_by(user_id: user.id) + context 'for a project in a user namespace' do + let(:project) { create(:project).tap { |p| p.team << [p.owner, :master, p.owner] } } + let(:user) { create(:user) } + let(:project_member) do + project.request_access(user) + project.members.request.find_by(user_id: user.id) + end + subject { Notify.member_access_requested_email('project', project_member.id) } + + it_behaves_like 'an email sent from GitLab' + it_behaves_like 'it should not have Gmail Actions links' + it_behaves_like "a user cannot unsubscribe through footer link" + + it 'contains all the useful information' do + to_emails = subject.header[:to].addrs + expect(to_emails.size).to eq(1) + expect(to_emails[0].address).to eq(project.members.owners_and_masters.first.user.notification_email) + + is_expected.to have_subject "Request to join the #{project.name_with_namespace} project" + is_expected.to have_body_text /#{project.name_with_namespace}/ + is_expected.to have_body_text /#{namespace_project_project_members_url(project.namespace, project)}/ + is_expected.to have_body_text /#{project_member.human_access}/ + end end - subject { Notify.member_access_requested_email('project', project_member.id) } - it_behaves_like 'an email sent from GitLab' - it_behaves_like 'it should not have Gmail Actions links' - it_behaves_like "a user cannot unsubscribe through footer link" + context 'for a project in a group' do + let(:group_owner) { create(:user) } + let(:group) { create(:group).tap { |g| g.add_owner(group_owner) } } + let(:project) { create(:project, namespace: group) } + let(:user) { create(:user) } + let(:project_member) do + project.request_access(user) + project.members.request.find_by(user_id: user.id) + end + subject { Notify.member_access_requested_email('project', project_member.id) } - it 'contains all the useful information' do - is_expected.to have_subject "Request to join the #{project.name_with_namespace} project" - is_expected.to have_body_text /#{project.name_with_namespace}/ - is_expected.to have_body_text /#{namespace_project_project_members_url(project.namespace, project)}/ - is_expected.to have_body_text /#{project_member.human_access}/ + it_behaves_like 'an email sent from GitLab' + it_behaves_like 'it should not have Gmail Actions links' + it_behaves_like "a user cannot unsubscribe through footer link" + + it 'contains all the useful information' do + to_emails = subject.header[:to].addrs + expect(to_emails.size).to eq(1) + expect(to_emails[0].address).to eq(group.members.owners_and_masters.first.user.notification_email) + + is_expected.to have_subject "Request to join the #{project.name_with_namespace} project" + is_expected.to have_body_text /#{project.name_with_namespace}/ + is_expected.to have_body_text /#{namespace_project_project_members_url(project.namespace, project)}/ + is_expected.to have_body_text /#{project_member.human_access}/ + end end end diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index 5d1fa8226e5..8154001cf46 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -2,7 +2,12 @@ require 'spec_helper' describe Ci::Build, models: true do let(:project) { create(:project) } - let(:pipeline) { create(:ci_pipeline, project: project) } + + let(:pipeline) do + create(:ci_pipeline, project: project, + sha: project.commit.id) + end + let(:build) { create(:ci_build, pipeline: pipeline) } it { is_expected.to validate_presence_of :ref } @@ -36,32 +41,44 @@ describe Ci::Build, models: true do subject { build.ignored? } context 'if build is not allowed to fail' do - before { build.allow_failure = false } + before do + build.allow_failure = false + end context 'and build.status is success' do - before { build.status = 'success' } + before do + build.status = 'success' + end it { is_expected.to be_falsey } end context 'and build.status is failed' do - before { build.status = 'failed' } + before do + build.status = 'failed' + end it { is_expected.to be_falsey } end end context 'if build is allowed to fail' do - before { build.allow_failure = true } + before do + build.allow_failure = true + end context 'and build.status is success' do - before { build.status = 'success' } + before do + build.status = 'success' + end it { is_expected.to be_falsey } end context 'and build.status is failed' do - before { build.status = 'failed' } + before do + build.status = 'failed' + end it { is_expected.to be_truthy } end @@ -75,7 +92,9 @@ describe Ci::Build, models: true do context 'if build.trace contains text' do let(:text) { 'example output' } - before { build.trace = text } + before do + build.trace = text + end it { is_expected.to include(text) } it { expect(subject.length).to be >= text.length } @@ -188,7 +207,9 @@ describe Ci::Build, models: true do ] end - before { build.update_attributes(stage: 'stage') } + before do + build.update_attributes(stage: 'stage') + end it { is_expected.to eq(predefined_variables + yaml_variables) } @@ -199,7 +220,9 @@ describe Ci::Build, models: true do ] end - before { build.update_attributes(tag: true) } + before do + build.update_attributes(tag: true) + end it { is_expected.to eq(tag_variable + predefined_variables + yaml_variables) } end @@ -257,57 +280,6 @@ describe Ci::Build, models: true do end end - describe '#can_be_served?' do - let(:runner) { create(:ci_runner) } - - before { build.project.runners << runner } - - context 'when runner does not have tags' do - it 'can handle builds without tags' do - expect(build.can_be_served?(runner)).to be_truthy - end - - it 'cannot handle build with tags' do - build.tag_list = ['aa'] - expect(build.can_be_served?(runner)).to be_falsey - end - end - - context 'when runner has tags' do - before { runner.tag_list = ['bb', 'cc'] } - - shared_examples 'tagged build picker' do - it 'can handle build with matching tags' do - build.tag_list = ['bb'] - expect(build.can_be_served?(runner)).to be_truthy - end - - it 'cannot handle build without matching tags' do - build.tag_list = ['aa'] - expect(build.can_be_served?(runner)).to be_falsey - end - end - - context 'when runner can pick untagged jobs' do - it 'can handle builds without tags' do - expect(build.can_be_served?(runner)).to be_truthy - end - - it_behaves_like 'tagged build picker' - end - - context 'when runner can not pick untagged jobs' do - before { runner.run_untagged = false } - - it 'can not handle builds without tags' do - expect(build.can_be_served?(runner)).to be_falsey - end - - it_behaves_like 'tagged build picker' - end - end - end - describe '#has_tags?' do context 'when build has tags' do subject { create(:ci_build, tag_list: ['tag']) } @@ -348,7 +320,7 @@ describe Ci::Build, models: true do end it 'that cannot handle build' do - expect_any_instance_of(Ci::Build).to receive(:can_be_served?).and_return(false) + expect_any_instance_of(Ci::Runner).to receive(:can_pick?).and_return(false) is_expected.to be_falsey end @@ -360,7 +332,9 @@ describe Ci::Build, models: true do %w(pending).each do |state| context "if commit_status.status is #{state}" do - before { build.status = state } + before do + build.status = state + end it { is_expected.to be_truthy } @@ -379,7 +353,9 @@ describe Ci::Build, models: true do %w(success failed canceled running).each do |state| context "if commit_status.status is #{state}" do - before { build.status = state } + before do + build.status = state + end it { is_expected.to be_falsey } end @@ -390,7 +366,10 @@ describe Ci::Build, models: true do subject { build.artifacts? } context 'artifacts archive does not exist' do - before { build.update_attributes(artifacts_file: nil) } + before do + build.update_attributes(artifacts_file: nil) + end + it { is_expected.to be_falsy } end @@ -623,7 +602,9 @@ describe Ci::Build, models: true do let!(:build) { create(:ci_build, :trace, :success, :artifacts) } describe '#erase' do - before { build.erase(erased_by: user) } + before do + build.erase(erased_by: user) + end context 'erased by user' do let!(:user) { create(:user, username: 'eraser') } @@ -660,7 +641,9 @@ describe Ci::Build, models: true do end context 'build has been erased' do - before { build.erase } + before do + build.erase + end it { is_expected.to be true } end @@ -668,7 +651,9 @@ describe Ci::Build, models: true do context 'metadata and build trace are not available' do let!(:build) { create(:ci_build, :success, :artifacts) } - before { build.remove_artifacts_metadata! } + before do + build.remove_artifacts_metadata! + end describe '#erase' do it 'should not raise error' do @@ -678,4 +663,10 @@ describe Ci::Build, models: true do end end end + + describe '#commit' do + it 'returns commit pipeline has been created for' do + expect(build.commit).to eq project.commit + end + end end diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb index 5d04d8ffcff..ef65eb99328 100644 --- a/spec/models/ci/runner_spec.rb +++ b/spec/models/ci/runner_spec.rb @@ -20,34 +20,36 @@ describe Ci::Runner, models: true do end describe '#display_name' do - it 'should return the description if it has a value' do + it 'returns the description if it has a value' do runner = FactoryGirl.build(:ci_runner, description: 'Linux/Ruby-1.9.3-p448') expect(runner.display_name).to eq 'Linux/Ruby-1.9.3-p448' end - it 'should return the token if it does not have a description' do + it 'returns the token if it does not have a description' do runner = FactoryGirl.create(:ci_runner) expect(runner.display_name).to eq runner.description end - it 'should return the token if the description is an empty string' do + it 'returns the token if the description is an empty string' do runner = FactoryGirl.build(:ci_runner, description: '', token: 'token') expect(runner.display_name).to eq runner.token end end - describe :assign_to do + describe '#assign_to' do let!(:project) { FactoryGirl.create :empty_project } let!(:shared_runner) { FactoryGirl.create(:ci_runner, :shared) } - before { shared_runner.assign_to(project) } + before do + shared_runner.assign_to(project) + end it { expect(shared_runner).to be_specific } it { expect(shared_runner.projects).to eq([project]) } it { expect(shared_runner.only_for?(project)).to be_truthy } end - describe :online do + describe '.online' do subject { Ci::Runner.online } before do @@ -58,60 +60,269 @@ describe Ci::Runner, models: true do it { is_expected.to eq([@runner2])} end - describe :online? do + describe '#online?' do let(:runner) { FactoryGirl.create(:ci_runner, :shared) } subject { runner.online? } context 'never contacted' do - before { runner.contacted_at = nil } + before do + runner.contacted_at = nil + end it { is_expected.to be_falsey } end context 'contacted long time ago time' do - before { runner.contacted_at = 1.year.ago } + before do + runner.contacted_at = 1.year.ago + end it { is_expected.to be_falsey } end context 'contacted 1s ago' do - before { runner.contacted_at = 1.second.ago } + before do + runner.contacted_at = 1.second.ago + end it { is_expected.to be_truthy } end end - describe :status do + describe '#can_pick?' do + let(:project) { create(:project) } + let(:pipeline) { create(:ci_pipeline, project: project) } + let(:build) { create(:ci_build, pipeline: pipeline) } + let(:runner) { create(:ci_runner) } + + before do + build.project.runners << runner + end + + context 'when runner does not have tags' do + it 'can handle builds without tags' do + expect(runner.can_pick?(build)).to be_truthy + end + + it 'cannot handle build with tags' do + build.tag_list = ['aa'] + + expect(runner.can_pick?(build)).to be_falsey + end + end + + context 'when runner has tags' do + before do + runner.tag_list = ['bb', 'cc'] + end + + shared_examples 'tagged build picker' do + it 'can handle build with matching tags' do + build.tag_list = ['bb'] + + expect(runner.can_pick?(build)).to be_truthy + end + + it 'cannot handle build without matching tags' do + build.tag_list = ['aa'] + + expect(runner.can_pick?(build)).to be_falsey + end + end + + context 'when runner can pick untagged jobs' do + it 'can handle builds without tags' do + expect(runner.can_pick?(build)).to be_truthy + end + + it_behaves_like 'tagged build picker' + end + + context 'when runner cannot pick untagged jobs' do + before do + runner.run_untagged = false + end + + it 'cannot handle builds without tags' do + expect(runner.can_pick?(build)).to be_falsey + end + + it_behaves_like 'tagged build picker' + end + end + + context 'when runner is locked' do + before do + runner.locked = true + end + + shared_examples 'locked build picker' do + context 'when runner cannot pick untagged jobs' do + before do + runner.run_untagged = false + end + + it 'cannot handle builds without tags' do + expect(runner.can_pick?(build)).to be_falsey + end + end + + context 'when having runner tags' do + before do + runner.tag_list = ['bb', 'cc'] + end + + it 'cannot handle it for builds without matching tags' do + build.tag_list = ['aa'] + + expect(runner.can_pick?(build)).to be_falsey + end + end + end + + context 'when serving the same project' do + it 'can handle it' do + expect(runner.can_pick?(build)).to be_truthy + end + + it_behaves_like 'locked build picker' + + context 'when having runner tags' do + before do + runner.tag_list = ['bb', 'cc'] + build.tag_list = ['bb'] + end + + it 'can handle it for matching tags' do + expect(runner.can_pick?(build)).to be_truthy + end + end + end + + context 'serving a different project' do + before do + runner.runner_projects.destroy_all + end + + it 'cannot handle it' do + expect(runner.can_pick?(build)).to be_falsey + end + + it_behaves_like 'locked build picker' + + context 'when having runner tags' do + before do + runner.tag_list = ['bb', 'cc'] + build.tag_list = ['bb'] + end + + it 'cannot handle it for matching tags' do + expect(runner.can_pick?(build)).to be_falsey + end + end + end + end + end + + describe '#status' do let(:runner) { FactoryGirl.create(:ci_runner, :shared, contacted_at: 1.second.ago) } subject { runner.status } context 'never connected' do - before { runner.contacted_at = nil } + before do + runner.contacted_at = nil + end it { is_expected.to eq(:not_connected) } end context 'contacted 1s ago' do - before { runner.contacted_at = 1.second.ago } + before do + runner.contacted_at = 1.second.ago + end it { is_expected.to eq(:online) } end context 'contacted long time ago' do - before { runner.contacted_at = 1.year.ago } + before do + runner.contacted_at = 1.year.ago + end it { is_expected.to eq(:offline) } end context 'inactive' do - before { runner.active = false } + before do + runner.active = false + end it { is_expected.to eq(:paused) } end end + describe '.assignable_for' do + let(:runner) { create(:ci_runner) } + let(:project) { create(:project) } + let(:another_project) { create(:project) } + + before do + project.runners << runner + end + + context 'with shared runners' do + before do + runner.update(is_shared: true) + end + + context 'does not give owned runner' do + subject { Ci::Runner.assignable_for(project) } + + it { is_expected.to be_empty } + end + + context 'does not give shared runner' do + subject { Ci::Runner.assignable_for(another_project) } + + it { is_expected.to be_empty } + end + end + + context 'with unlocked runner' do + context 'does not give owned runner' do + subject { Ci::Runner.assignable_for(project) } + + it { is_expected.to be_empty } + end + + context 'does give a specific runner' do + subject { Ci::Runner.assignable_for(another_project) } + + it { is_expected.to contain_exactly(runner) } + end + end + + context 'with locked runner' do + before do + runner.update(locked: true) + end + + context 'does not give owned runner' do + subject { Ci::Runner.assignable_for(project) } + + it { is_expected.to be_empty } + end + + context 'does not give a locked runner' do + subject { Ci::Runner.assignable_for(another_project) } + + it { is_expected.to be_empty } + end + end + end + describe "belongs_to_one_project?" do it "returns false if there are two projects runner assigned to" do runner = FactoryGirl.create(:ci_runner) diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index beca8708c9d..ba02d5fe977 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -207,4 +207,16 @@ eos expect(commit.participants).to include(note1.author, note2.author) end end + + describe '#uri_type' do + it 'returns the URI type at the given path' do + expect(commit.uri_type('files/html')).to be(:tree) + expect(commit.uri_type('files/images/logo-black.png')).to be(:raw) + expect(commit.uri_type('files/js/application.js')).to be(:blob) + end + + it "returns nil if the path doesn't exists" do + expect(commit.uri_type('this/path/doesnt/exist')).to be_nil + end + end end diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb index 8fb605fff8a..96397d7c8a9 100644 --- a/spec/models/commit_status_spec.rb +++ b/spec/models/commit_status_spec.rb @@ -1,8 +1,13 @@ require 'spec_helper' describe CommitStatus, models: true do - let(:pipeline) { FactoryGirl.create :ci_pipeline } - let(:commit_status) { FactoryGirl.create :commit_status, pipeline: pipeline } + let(:project) { create(:project) } + + let(:pipeline) do + create(:ci_pipeline, project: project, sha: project.commit.id) + end + + let(:commit_status) { create(:commit_status, pipeline: pipeline) } it { is_expected.to belong_to(:pipeline) } it { is_expected.to belong_to(:user) } @@ -13,7 +18,7 @@ describe CommitStatus, models: true do it { is_expected.to delegate_method(:sha).to(:pipeline) } it { is_expected.to delegate_method(:short_sha).to(:pipeline) } - + it { is_expected.to respond_to :success? } it { is_expected.to respond_to :failed? } it { is_expected.to respond_to :running? } @@ -116,7 +121,7 @@ describe CommitStatus, models: true do it { is_expected.to be > 0.0 } end end - + describe :latest do subject { CommitStatus.latest.order(:id) } @@ -198,4 +203,10 @@ describe CommitStatus, models: true do end end end + + describe '#commit' do + it 'returns commit pipeline has been created for' do + expect(commit_status.commit).to eq project.commit + end + end end diff --git a/spec/models/concerns/participable_spec.rb b/spec/models/concerns/participable_spec.rb index 7e4ea0f2d66..a9f4ef9ee5e 100644 --- a/spec/models/concerns/participable_spec.rb +++ b/spec/models/concerns/participable_spec.rb @@ -37,6 +37,16 @@ describe Participable, models: true do expect(participants).to include(user3) end + it 'caches the raw list of participants' do + instance = model.new + user1 = build(:user) + + expect(instance).to receive(:raw_participants).once + + instance.participants(user1) + instance.participants(user1) + end + it 'supports attributes returning another Participable' do other_model = Class.new { include Participable } diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb index 3ed3202ac6c..e9134a3d283 100644 --- a/spec/models/member_spec.rb +++ b/spec/models/member_spec.rb @@ -134,18 +134,6 @@ describe Member, models: true do it { is_expected.to respond_to(:user_email) } end - describe 'Callbacks' do - describe 'after_destroy :post_decline_request, if: :request?' do - let(:member) { create(:project_member, requested_at: Time.now.utc) } - - it 'calls #post_decline_request' do - expect(member).to receive(:post_decline_request) - - member.destroy - end - end - end - describe ".add_user" do let!(:user) { create(:user) } let(:project) { create(:project) } diff --git a/spec/models/members/group_member_spec.rb b/spec/models/members/group_member_spec.rb index eeb74a462ac..18439cac2a4 100644 --- a/spec/models/members/group_member_spec.rb +++ b/spec/models/members/group_member_spec.rb @@ -61,16 +61,6 @@ describe GroupMember, models: true do end end - describe '#post_decline_request' do - it 'calls NotificationService.decline_group_access_request' do - member = create(:group_member, user: build_stubbed(:user), requested_at: Time.now) - - expect_any_instance_of(NotificationService).to receive(:decline_group_access_request) - - member.__send__(:post_decline_request) - end - end - describe '#real_source_type' do subject { create(:group_member).real_source_type } diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb index 1e466f9c620..bbf65edb27c 100644 --- a/spec/models/members/project_member_spec.rb +++ b/spec/models/members/project_member_spec.rb @@ -152,15 +152,5 @@ describe ProjectMember, models: true do member.__send__(:after_accept_request) end end - - describe '#post_decline_request' do - it 'calls NotificationService.decline_project_access_request' do - member = create(:project_member, user: build_stubbed(:user), requested_at: Time.now) - - expect_any_instance_of(NotificationService).to receive(:decline_project_access_request) - - member.__send__(:post_decline_request) - end - end end end diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb index ac85f340922..47e9253a10c 100644 --- a/spec/requests/api/builds_spec.rb +++ b/spec/requests/api/builds_spec.rb @@ -9,8 +9,8 @@ describe API::API, api: true do let!(:project) { create(:project, creator_id: user.id) } let!(:developer) { create(:project_member, :developer, user: user, project: project) } let!(:reporter) { create(:project_member, :reporter, user: user2, project: project) } - let(:pipeline) { create(:ci_pipeline, project: project)} - let(:build) { create(:ci_build, pipeline: pipeline) } + let!(:pipeline) { create(:ci_pipeline, project: project, sha: project.commit.id) } + let!(:build) { create(:ci_build, pipeline: pipeline) } describe 'GET /projects/:id/builds ' do let(:query) { '' } @@ -23,6 +23,11 @@ describe API::API, api: true do expect(json_response).to be_an Array end + it 'returns correct values' do + expect(json_response).not_to be_empty + expect(json_response.first['commit']['id']).to eq project.commit.id + end + context 'filter project with one scope element' do let(:query) { 'scope=pending' } @@ -132,7 +137,7 @@ describe API::API, api: true do describe 'GET /projects/:id/builds/:build_id/trace' do let(:build) { create(:ci_build, :trace, pipeline: pipeline) } - + before { get api("/projects/#{project.id}/builds/#{build.id}/trace", api_user) } context 'authorized user' do diff --git a/spec/requests/api/gitignores_spec.rb b/spec/requests/api/gitignores_spec.rb deleted file mode 100644 index aab2d8c81b9..00000000000 --- a/spec/requests/api/gitignores_spec.rb +++ /dev/null @@ -1,29 +0,0 @@ -require 'spec_helper' - -describe API::Gitignores, api: true do - include ApiHelpers - - describe 'Entity Gitignore' do - before { get api('/gitignores/Ruby') } - - it { expect(json_response['name']).to eq('Ruby') } - it { expect(json_response['content']).to include('*.gem') } - end - - describe 'Entity GitignoresList' do - before { get api('/gitignores') } - - it { expect(json_response.first['name']).not_to be_nil } - it { expect(json_response.first['content']).to be_nil } - end - - describe 'GET /gitignores' do - it 'returns a list of available license templates' do - get api('/gitignores') - - expect(response.status).to eq(200) - expect(json_response).to be_an Array - expect(json_response.size).to be > 15 - end - end -end diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb index 73ae8ef631c..b4c826522a5 100644 --- a/spec/requests/api/runners_spec.rb +++ b/spec/requests/api/runners_spec.rb @@ -187,14 +187,16 @@ describe API::Runners, api: true do update_runner(shared_runner.id, admin, description: "#{description}_updated", active: !active, tag_list: ['ruby2.1', 'pgsql', 'mysql'], - run_untagged: 'false') + run_untagged: 'false', + locked: 'true') shared_runner.reload expect(response.status).to eq(200) expect(shared_runner.description).to eq("#{description}_updated") expect(shared_runner.active).to eq(!active) expect(shared_runner.tag_list).to include('ruby2.1', 'pgsql', 'mysql') - expect(shared_runner.run_untagged?).to be false + expect(shared_runner.run_untagged?).to be(false) + expect(shared_runner.locked?).to be(true) end end @@ -360,11 +362,13 @@ describe API::Runners, api: true do describe 'POST /projects/:id/runners' do context 'authorized user' do - it 'should enable specific runner' do - specific_runner2 = create(:ci_runner).tap do |runner| + let(:specific_runner2) do + create(:ci_runner).tap do |runner| create(:ci_runner_project, runner: runner, project: project2) end + end + it 'should enable specific runner' do expect do post api("/projects/#{project.id}/runners", user), runner_id: specific_runner2.id end.to change{ project.runners.count }.by(+1) @@ -375,7 +379,17 @@ describe API::Runners, api: true do expect do post api("/projects/#{project.id}/runners", user), runner_id: specific_runner.id end.to change{ project.runners.count }.by(0) - expect(response.status).to eq(201) + expect(response.status).to eq(409) + end + + it 'should not enable locked runner' do + specific_runner2.update(locked: true) + + expect do + post api("/projects/#{project.id}/runners", user), runner_id: specific_runner2.id + end.to change{ project.runners.count }.by(0) + + expect(response.status).to eq(403) end it 'should not enable shared runner' do diff --git a/spec/requests/api/templates_spec.rb b/spec/requests/api/templates_spec.rb new file mode 100644 index 00000000000..a6d5ade3013 --- /dev/null +++ b/spec/requests/api/templates_spec.rb @@ -0,0 +1,52 @@ +require 'spec_helper' + +describe API::Templates, api: true do + include ApiHelpers + + describe 'the Template Entity' do + before { get api('/gitignores/Ruby') } + + it { expect(json_response['name']).to eq('Ruby') } + it { expect(json_response['content']).to include('*.gem') } + end + + describe 'the TemplateList Entity' do + before { get api('/gitignores') } + + it { expect(json_response.first['name']).not_to be_nil } + it { expect(json_response.first['content']).to be_nil } + end + + context 'requesting gitignores' do + describe 'GET /gitignores' do + it 'returns a list of available gitignore templates' do + get api('/gitignores') + + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.size).to be > 15 + end + end + end + + context 'requesting gitlab-ci-ymls' do + describe 'GET /gitlab_ci_ymls' do + it 'returns a list of available gitlab_ci_ymls' do + get api('/gitlab_ci_ymls') + + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.first['name']).not_to be_nil + end + end + end + + describe 'GET /gitlab_ci_ymls/Ruby' do + it 'adds a disclaimer on the top' do + get api('/gitlab_ci_ymls/Ruby') + + expect(response.status).to eq(200) + expect(json_response['content']).to start_with("# This file is a template,") + end + end +end diff --git a/spec/services/members/destroy_service_spec.rb b/spec/services/members/destroy_service_spec.rb new file mode 100644 index 00000000000..2395445e7fd --- /dev/null +++ b/spec/services/members/destroy_service_spec.rb @@ -0,0 +1,71 @@ +require 'spec_helper' + +describe Members::DestroyService, services: true do + let(:user) { create(:user) } + let(:project) { create(:project) } + let!(:member) { create(:project_member, source: project) } + + context 'when member is nil' do + before do + project.team << [user, :developer] + end + + it 'does not destroy the member' do + expect { destroy_member(nil, user) }.to raise_error(Gitlab::Access::AccessDeniedError) + end + end + + context 'when current user cannot destroy the given member' do + before do + project.team << [user, :developer] + end + + it 'does not destroy the member' do + expect { destroy_member(member, user) }.to raise_error(Gitlab::Access::AccessDeniedError) + end + end + + context 'when current user can destroy the given member' do + before do + project.team << [user, :master] + end + + it 'destroys the member' do + destroy_member(member, user) + + expect(member).to be_destroyed + end + + context 'when the given member is a requester' do + before do + member.update_column(:requested_at, Time.now) + end + + it 'calls Member#after_decline_request' do + expect_any_instance_of(NotificationService).to receive(:decline_access_request).with(member) + + destroy_member(member, user) + end + + context 'when current user is the member' do + it 'does not call Member#after_decline_request' do + expect_any_instance_of(NotificationService).not_to receive(:decline_access_request).with(member) + + destroy_member(member, member.user) + end + end + + context 'when current user is the member and ' do + it 'does not call Member#after_decline_request' do + expect_any_instance_of(NotificationService).not_to receive(:decline_access_request).with(member) + + destroy_member(member, member.user) + end + end + end + end + + def destroy_member(member, user) + Members::DestroyService.new(member, user).execute + end +end diff --git a/spec/workers/merge_worker_spec.rb b/spec/workers/merge_worker_spec.rb index 1abd87d7d33..b5e1fdb8ded 100644 --- a/spec/workers/merge_worker_spec.rb +++ b/spec/workers/merge_worker_spec.rb @@ -9,7 +9,7 @@ describe MergeWorker do before do source_project.team << [author, :master] - source_project.repository.expire_branch_names + source_project.repository.expire_branches_cache end it 'clears cache of source repo after removing source branch' do diff --git a/vendor/gitignore/Android.gitignore b/vendor/gitignore/Android.gitignore index a8368751267..f6b286cea98 100644 --- a/vendor/gitignore/Android.gitignore +++ b/vendor/gitignore/Android.gitignore @@ -2,7 +2,7 @@ *.apk *.ap_ -# Files for the Dalvik VM +# Files for the ART/Dalvik VM *.dex # Java class files @@ -34,6 +34,7 @@ captures/ # Intellij *.iml +.idea/workspace.xml # Keystore files *.jks diff --git a/vendor/gitignore/C++.gitignore b/vendor/gitignore/C++.gitignore index b8bd0267bdf..4581ef2eeef 100644 --- a/vendor/gitignore/C++.gitignore +++ b/vendor/gitignore/C++.gitignore @@ -15,6 +15,7 @@ # Fortran module files *.mod +*.smod # Compiled Static libraries *.lai diff --git a/vendor/gitignore/CMake.gitignore b/vendor/gitignore/CMake.gitignore index b558e9afa6d..0cc7e4b5275 100644 --- a/vendor/gitignore/CMake.gitignore +++ b/vendor/gitignore/CMake.gitignore @@ -4,3 +4,4 @@ CMakeScripts Makefile cmake_install.cmake install_manifest.txt +CTestTestfile.cmake diff --git a/vendor/gitignore/D.gitignore b/vendor/gitignore/D.gitignore index b4433f8a512..74b926fc901 100644 --- a/vendor/gitignore/D.gitignore +++ b/vendor/gitignore/D.gitignore @@ -18,3 +18,7 @@ .dub docs.json __dummy.html +docs/ + +# Code coverage +*.lst diff --git a/vendor/gitignore/Global/Bazaar.gitignore b/vendor/gitignore/Global/Bazaar.gitignore new file mode 100644 index 00000000000..3cbbcbd11ec --- /dev/null +++ b/vendor/gitignore/Global/Bazaar.gitignore @@ -0,0 +1,2 @@ +.bzr/ +.bzrignore diff --git a/vendor/gitignore/Global/OSX.gitignore b/vendor/gitignore/Global/OSX.gitignore index 660b31353e8..5972fe50f66 100644 --- a/vendor/gitignore/Global/OSX.gitignore +++ b/vendor/gitignore/Global/OSX.gitignore @@ -1,4 +1,4 @@ -.DS_Store +*.DS_Store .AppleDouble .LSOverride @@ -15,6 +15,7 @@ Icon .TemporaryItems .Trashes .VolumeIcon.icns +.com.apple.timemachine.donotpresent # Directories potentially created on remote AFP share .AppleDB diff --git a/vendor/gitignore/Global/README.md b/vendor/gitignore/Global/README.md new file mode 100644 index 00000000000..06b6649bd9a --- /dev/null +++ b/vendor/gitignore/Global/README.md @@ -0,0 +1,10 @@ +## Globally Useful gitignores + +This directory contains globally useful gitignores, +e.g. OS-specific and editor specific. + +For more on global gitignores: +<https://help.github.com/articles/ignoring-files/#create-a-global-gitignore> + +And a good blog post about 'em: +<http://augustl.com/blog/2009/global_gitignores> diff --git a/vendor/gitignore/Global/SublimeText.gitignore b/vendor/gitignore/Global/SublimeText.gitignore index 1d4e6137591..69c8c2b29ce 100644 --- a/vendor/gitignore/Global/SublimeText.gitignore +++ b/vendor/gitignore/Global/SublimeText.gitignore @@ -12,3 +12,16 @@ # sftp configuration file sftp-config.json + +# Package control specific files +Package Control.last-run +Package Control.ca-list +Package Control.ca-bundle +Package Control.system-ca-bundle +Package Control.cache/ +Package Control.ca-certs/ +bh_unicode_properties.cache + +# Sublime-github package stores a github token in this file +# https://packagecontrol.io/packages/sublime-github +GitHub.sublime-settings diff --git a/vendor/gitignore/Haskell.gitignore b/vendor/gitignore/Haskell.gitignore index 096abdd90b3..a4ee41ab62b 100644 --- a/vendor/gitignore/Haskell.gitignore +++ b/vendor/gitignore/Haskell.gitignore @@ -16,3 +16,4 @@ cabal.sandbox.config *.hp *.eventlog .stack-work/ +cabal.project.local diff --git a/vendor/gitignore/Julia.gitignore b/vendor/gitignore/Julia.gitignore new file mode 100644 index 00000000000..381e0b6d252 --- /dev/null +++ b/vendor/gitignore/Julia.gitignore @@ -0,0 +1,4 @@ +*.jl.cov +*.jl.*.cov +*.jl.mem +deps/deps.jl diff --git a/vendor/gitignore/LICENSE b/vendor/gitignore/LICENSE new file mode 100644 index 00000000000..b8a103ac9b1 --- /dev/null +++ b/vendor/gitignore/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2016 GitHub, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/vendor/gitignore/Laravel.gitignore b/vendor/gitignore/Laravel.gitignore index c491fa2bc6f..1cd717b6921 100644 --- a/vendor/gitignore/Laravel.gitignore +++ b/vendor/gitignore/Laravel.gitignore @@ -7,7 +7,6 @@ app/storage/ # Laravel 5 & Lumen specific bootstrap/cache/ -storage/ .env.*.php .env.php .env diff --git a/vendor/gitignore/Objective-C.gitignore b/vendor/gitignore/Objective-C.gitignore index 3020bc327a7..86f21d8e0ff 100644 --- a/vendor/gitignore/Objective-C.gitignore +++ b/vendor/gitignore/Objective-C.gitignore @@ -24,6 +24,8 @@ xcuserdata/ ## Obj-C/Swift specific *.hmap *.ipa +*.dSYM.zip +*.dSYM # CocoaPods # @@ -49,3 +51,10 @@ Carthage/Build fastlane/report.xml fastlane/screenshots + +#Code Injection +# +# After new code Injection tools there's a generated folder /iOSInjectionProject +# https://github.com/johnno1962/injectionforxcode + +iOSInjectionProject/ diff --git a/vendor/gitignore/Qt.gitignore b/vendor/gitignore/Qt.gitignore index fa24b2efee8..c7659c24f38 100644 --- a/vendor/gitignore/Qt.gitignore +++ b/vendor/gitignore/Qt.gitignore @@ -34,5 +34,5 @@ Makefile* *.qmlproject.user.* # QtCtreator CMake -CMakeLists.txt.user +CMakeLists.txt.user* diff --git a/vendor/gitignore/README.md b/vendor/gitignore/README.md deleted file mode 100644 index 43131e815cc..00000000000 --- a/vendor/gitignore/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# .gitignore templates - -This directory contains language-specific .gitignore templates that are used by GitLab. - -These files were automatically pulled from [this repository](https://github.com/github/gitignore). -Please submit pull requests to that repository. There is no need to edit the files in this directory. - -## Bulk Update - -To update this directory with the latest changes in the repository, run: - -```sh -bundle exec rake gitlab:update_gitignore -``` diff --git a/vendor/gitignore/Rails.gitignore b/vendor/gitignore/Rails.gitignore index 2121e0a8038..d8c256c1925 100644 --- a/vendor/gitignore/Rails.gitignore +++ b/vendor/gitignore/Rails.gitignore @@ -16,6 +16,10 @@ pickle-email-*.html config/initializers/secret_token.rb config/secrets.yml +# dotenv +# TODO Comment out this rule if environment variables can be committed +.env + ## Environment normalization: /.bundle /vendor/bundle diff --git a/vendor/gitignore/Swift.gitignore b/vendor/gitignore/Swift.gitignore index 8a29fa52af4..2c22487b5e3 100644 --- a/vendor/gitignore/Swift.gitignore +++ b/vendor/gitignore/Swift.gitignore @@ -24,6 +24,8 @@ xcuserdata/ ## Obj-C/Swift specific *.hmap *.ipa +*.dSYM.zip +*.dSYM ## Playgrounds timeline.xctimeline diff --git a/vendor/gitignore/UnrealEngine.gitignore b/vendor/gitignore/UnrealEngine.gitignore index 75b1186b0af..be0e4913c3a 100644 --- a/vendor/gitignore/UnrealEngine.gitignore +++ b/vendor/gitignore/UnrealEngine.gitignore @@ -37,6 +37,7 @@ *.suo *.opensdf *.sdf +*.VC.db *.VC.opendb # Precompiled Assets diff --git a/vendor/gitignore/VisualStudio.gitignore b/vendor/gitignore/VisualStudio.gitignore index f1e3d20e056..67acbf42f5e 100644 --- a/vendor/gitignore/VisualStudio.gitignore +++ b/vendor/gitignore/VisualStudio.gitignore @@ -42,6 +42,7 @@ dlldata.c # DNX project.lock.json +project.fragment.lock.json artifacts/ *_i.c diff --git a/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml b/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml new file mode 100644 index 00000000000..396d3f1b042 --- /dev/null +++ b/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml @@ -0,0 +1,7 @@ +# Official docker image. +image: docker:latest + +build: + stage: build + script: + - docker build -t test . diff --git a/vendor/gitlab-ci-yml/Elixir.gitlab-ci.yml b/vendor/gitlab-ci-yml/Elixir.gitlab-ci.yml new file mode 100644 index 00000000000..0b329aaf1c4 --- /dev/null +++ b/vendor/gitlab-ci-yml/Elixir.gitlab-ci.yml @@ -0,0 +1,18 @@ +# This template uses the non default language docker image +# The image already has Hex installed. You might want to consider to use `elixir:latest` +image: trenpixster/elixir:latest + +# Pic zero or more services to be used on all builds. +# Only needed when using a docker container to run your tests in. +# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-service +services: + - mysql:latest + - redis:latest + - postgres:latest + +before_script: + - mix deps.get + +mix: + script: + - mix test diff --git a/vendor/gitlab-ci-yml/LICENSE b/vendor/gitlab-ci-yml/LICENSE new file mode 100644 index 00000000000..80f7b87b6c0 --- /dev/null +++ b/vendor/gitlab-ci-yml/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 GitLab.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/gitlab-ci-yml/Nodejs.gitlab-ci.yml b/vendor/gitlab-ci-yml/Nodejs.gitlab-ci.yml new file mode 100644 index 00000000000..e5bce3503f3 --- /dev/null +++ b/vendor/gitlab-ci-yml/Nodejs.gitlab-ci.yml @@ -0,0 +1,27 @@ +# Official framework image. Look for the different tagged releases at: +# https://hub.docker.com/r/library/node/tags/ +image: node:latest + +# Pick zero or more services to be used on all builds. +# Only needed when using a docker container to run your tests in. +# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-service +services: + - mysql:latest + - redis:latest + - postgres:latest + +# This folder is cached between builds +# http://docs.gitlab.com/ce/ci/yaml/README.html#cache +cache: + paths: + - node_modules/ + +test_async: + script: + - npm install + - node ./specs/start.js ./specs/async.spec.js + +test_db: + script: + - npm install + - node ./specs/start.js ./specs/db-postgres.spec.js diff --git a/vendor/gitlab-ci-yml/Pages/brunch.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/brunch.gitlab-ci.yml new file mode 100644 index 00000000000..7fcc0b436b5 --- /dev/null +++ b/vendor/gitlab-ci-yml/Pages/brunch.gitlab-ci.yml @@ -0,0 +1,16 @@ +# Full project: https://gitlab.com/pages/brunch +image: node:4.2.2 + +pages: + cache: + paths: + - node_modules/ + + script: + - npm install -g brunch + - brunch build --production + artifacts: + paths: + - public + only: + - master diff --git a/vendor/gitlab-ci-yml/Pages/doxygen.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/doxygen.gitlab-ci.yml new file mode 100644 index 00000000000..791afdd23f1 --- /dev/null +++ b/vendor/gitlab-ci-yml/Pages/doxygen.gitlab-ci.yml @@ -0,0 +1,13 @@ +# Full project: https://gitlab.com/pages/doxygen +image: alpine + +pages: + script: + - apk update && apk add doxygen + - doxygen doxygen/Doxyfile + - mv doxygen/documentation/html/ public/ + artifacts: + paths: + - public + only: + - master diff --git a/vendor/gitlab-ci-yml/Pages/harp.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/harp.gitlab-ci.yml new file mode 100644 index 00000000000..dd3ef149668 --- /dev/null +++ b/vendor/gitlab-ci-yml/Pages/harp.gitlab-ci.yml @@ -0,0 +1,16 @@ +# Full project: https://gitlab.com/pages/harp +image: node:4.2.2 + +pages: + cache: + paths: + - node_modules + + script: + - npm install -g harp + - harp compile ./ public + artifacts: + paths: + - public + only: + - master diff --git a/vendor/gitlab-ci-yml/Pages/hexo.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/hexo.gitlab-ci.yml new file mode 100644 index 00000000000..b468d79bcad --- /dev/null +++ b/vendor/gitlab-ci-yml/Pages/hexo.gitlab-ci.yml @@ -0,0 +1,25 @@ +# Full project: https://gitlab.com/pages/hexo +image: python:2.7 + +cache: + paths: + - vendor/ + +test: + stage: test + script: + - pip install hyde + - hyde gen + except: + - master + +pages: + stage: deploy + script: + - pip install hyde + - hyde gen -d public + artifacts: + paths: + - public + only: + - master diff --git a/vendor/gitlab-ci-yml/Pages/html.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/html.gitlab-ci.yml new file mode 100644 index 00000000000..249a168aa33 --- /dev/null +++ b/vendor/gitlab-ci-yml/Pages/html.gitlab-ci.yml @@ -0,0 +1,12 @@ +# Full project: https://gitlab.com/pages/plain-html +pages: + stage: deploy + script: + - mkdir .public + - cp -r * .public + - mv .public public + artifacts: + paths: + - public + only: + - master diff --git a/vendor/gitlab-ci-yml/Pages/hugo.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/hugo.gitlab-ci.yml new file mode 100644 index 00000000000..45df6975259 --- /dev/null +++ b/vendor/gitlab-ci-yml/Pages/hugo.gitlab-ci.yml @@ -0,0 +1,11 @@ +# Full project: https://gitlab.com/pages/hugo +image: publysher/hugo + +pages: + script: + - hugo + artifacts: + paths: + - public + only: + - master diff --git a/vendor/gitlab-ci-yml/Pages/hyde.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/hyde.gitlab-ci.yml new file mode 100644 index 00000000000..f5b40f2b9f1 --- /dev/null +++ b/vendor/gitlab-ci-yml/Pages/hyde.gitlab-ci.yml @@ -0,0 +1,25 @@ +# Full project: https://gitlab.com/pages/hyde +image: python:2.7 + +cache: + paths: + - vendor/ + +test: + stage: test + script: + - pip install hyde + - hyde gen + except: + - master + +pages: + stage: deploy + script: + - pip install hyde + - hyde gen -d public + artifacts: + paths: + - public + only: + - master diff --git a/vendor/gitlab-ci-yml/Pages/jekyll.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/jekyll.gitlab-ci.yml new file mode 100644 index 00000000000..36918fc005a --- /dev/null +++ b/vendor/gitlab-ci-yml/Pages/jekyll.gitlab-ci.yml @@ -0,0 +1,24 @@ +# Full project: https://gitlab.com/pages/jekyll +image: ruby:2.3 + +test: + stage: test + script: + - gem install jekyll + - jekyll build -d test + artifacts: + paths: + - test + except: + - master + +pages: + stage: deploy + script: + - gem install jekyll + - jekyll build -d public + artifacts: + paths: + - public + only: + - master diff --git a/vendor/gitlab-ci-yml/Pages/lektor.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/lektor.gitlab-ci.yml new file mode 100644 index 00000000000..c5c44a5d86c --- /dev/null +++ b/vendor/gitlab-ci-yml/Pages/lektor.gitlab-ci.yml @@ -0,0 +1,12 @@ +# Full project: https://gitlab.com/pages/hyde +image: python:2.7 + +pages: + script: + - pip install lektor + - lektor build --output-path public + artifacts: + paths: + - public + only: + - master diff --git a/vendor/gitlab-ci-yml/Pages/metalsmith.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/metalsmith.gitlab-ci.yml new file mode 100644 index 00000000000..50e8b7ccd46 --- /dev/null +++ b/vendor/gitlab-ci-yml/Pages/metalsmith.gitlab-ci.yml @@ -0,0 +1,17 @@ +# Full project: https://gitlab.com/pages/metalsmith +image: node:4.2.2 + +pages: + cache: + paths: + - node_modules/ + + script: + - npm install -g metalsmith + - npm install + - make build + artifacts: + paths: + - public + only: + - master diff --git a/vendor/gitlab-ci-yml/Pages/middleman.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/middleman.gitlab-ci.yml new file mode 100644 index 00000000000..9f4cc0574d6 --- /dev/null +++ b/vendor/gitlab-ci-yml/Pages/middleman.gitlab-ci.yml @@ -0,0 +1,27 @@ +# Full project: https://gitlab.com/pages/middleman +image: ruby:2.3 + +cache: + paths: + - vendor + +test: + script: + - apt-get update -yqqq + - apt-get install -y nodejs + - bundle install --path vendor + - bundle exec middleman build + except: + - master + +pages: + script: + - apt-get update -yqqq + - apt-get install -y nodejs + - bundle install --path vendor + - bundle exec middleman build + artifacts: + paths: + - public + only: + - master diff --git a/vendor/gitlab-ci-yml/Pages/nanoc.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/nanoc.gitlab-ci.yml new file mode 100644 index 00000000000..b469b316ba5 --- /dev/null +++ b/vendor/gitlab-ci-yml/Pages/nanoc.gitlab-ci.yml @@ -0,0 +1,12 @@ +# Full project: https://gitlab.com/pages/nanoc +image: ruby:2.3 + +pages: + script: + - bundle install -j4 + - nanoc + artifacts: + paths: + - public + only: + - master diff --git a/vendor/gitlab-ci-yml/Pages/octopress.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/octopress.gitlab-ci.yml new file mode 100644 index 00000000000..4762ec9acfd --- /dev/null +++ b/vendor/gitlab-ci-yml/Pages/octopress.gitlab-ci.yml @@ -0,0 +1,15 @@ +# Full project: https://gitlab.com/pages/octopress +image: ruby:2.3 + +pages: + script: + - apt-get update -qq && apt-get install -qq nodejs + - bundle install -j4 + - bundle exec rake generate + - mv public .public + - mv .public/octopress public + artifacts: + paths: + - public + only: + - master diff --git a/vendor/gitlab-ci-yml/Pages/pelican.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/pelican.gitlab-ci.yml new file mode 100644 index 00000000000..c5f3154f587 --- /dev/null +++ b/vendor/gitlab-ci-yml/Pages/pelican.gitlab-ci.yml @@ -0,0 +1,10 @@ +# Full project: https://gitlab.com/pages/pelican +image: python:2.7-alpine + +pages: + script: + - pip install -r requirements.txt + - pelican -s publishconf.py + artifacts: + paths: + - public/ diff --git a/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml b/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml new file mode 100644 index 00000000000..78f3e39949f --- /dev/null +++ b/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml @@ -0,0 +1,30 @@ +# Official language image. Look for the different tagged releases at: +# https://hub.docker.com/r/library/ruby/tags/ +image: "ruby:2.3" + +# Pick zero or more services to be used on all builds. +# Only needed when using a docker container to run your tests in. +# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-service +services: + - mysql:latest + - redis:latest + - postgres:latest + +# This is a basic example for a gem or script which doesn't use +# services such as redis or postgres +before_script: + - gem install bundler # Bundler is not installed with the image + - bundle install -j $(nproc) # Install dependencies + +rubocop: + script: + - rubocop + +rspec: + script: + - rspec spec + +rails: + script: + - rake db:migrate + - rspec spec |