diff options
403 files changed, 6600 insertions, 4060 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3c357c489f8..c7528029c89 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -229,7 +229,6 @@ rake ee_compat_check: <<: *exec only: - branches@gitlab-org/gitlab-ce - - branches@gitlab/gitlabhq except: - master - tags diff --git a/.scss-lint.yml b/.scss-lint.yml index aae8d9b6dbe..83c68309fa8 100644 --- a/.scss-lint.yml +++ b/.scss-lint.yml @@ -30,7 +30,7 @@ linters: # variable declarations. They should be referred to via variables everywhere # else. ColorVariable: - enabled: false + enabled: true # Which form of comments to prefer in CSS. Comment: diff --git a/CHANGELOG.md b/CHANGELOG.md index 12a3e63ed2e..86b30d2832d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,26 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 8.14.2 (2016-12-01) + +- Remove caching of events data. !6578 +- Rephrase some system notes to be compatible with new system note style. !7692 +- Pass tag SHA to post-receive hook when tag is created via UI. !7700 +- Prevent error when submitting a merge request and pipeline is not defined. !7707 +- Fixes system note style in commit discussion. !7721 +- Use a Redis lease for updating authorized projects. !7733 +- Refactor JiraService by moving code out of JiraService#execute method. !7756 +- Update GitLab Workhorse to v1.0.1. !7759 +- Fix pipelines info being hidden in merge request widget. !7808 +- Fixed commit timeago not rendering after initial page. +- Fix for error thrown in cycle analytics events if build has not started. +- Fixed issue boards issue sorting when dragging issue into list. +- Allow access to the wiki with git when repository feature disabled. +- Fixed timeago not rendering when resolving a discussion. +- Update Sidekiq-cron to fix compatibility issues with Sidekiq 4.2.1. +- Timeout creating and viewing merge request for binary file. +- Gracefully recover from Redis connection failures in Sidekiq initializer. + ## 8.14.1 (2016-11-28) - Fix deselecting calendar days on contribution graph. !6453 (ClemMakesApps) diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION index 7dea76edb3d..9084fa2f716 100644 --- a/GITLAB_WORKHORSE_VERSION +++ b/GITLAB_WORKHORSE_VERSION @@ -1 +1 @@ -1.0.1 +1.1.0 diff --git a/Gemfile.lock b/Gemfile.lock index 1db0e466164..5a14ed6fede 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -614,8 +614,7 @@ GEM rubyntlm (0.5.2) rubypants (0.2.0) rubyzip (1.2.0) - rufus-scheduler (3.3.0) - tzinfo + rufus-scheduler (3.1.10) rugged (0.24.0) safe_yaml (1.0.4) sanitize (2.1.0) diff --git a/app/assets/fonts/OFL.txt b/app/assets/fonts/OFL.txt deleted file mode 100644 index df187637e18..00000000000 --- a/app/assets/fonts/OFL.txt +++ /dev/null @@ -1,93 +0,0 @@ -Copyright 2010, 2012, 2014 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries. - -This Font Software is licensed under the SIL Open Font License, Version 1.1. - -This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL - - ------------------------------------------------------------ -SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ------------------------------------------------------------ - -PREAMBLE -The goals of the Open Font License (OFL) are to stimulate worldwide -development of collaborative font projects, to support the font creation -efforts of academic and linguistic communities, and to provide a free and -open framework in which fonts may be shared and improved in partnership -with others. - -The OFL allows the licensed fonts to be used, studied, modified and -redistributed freely as long as they are not sold by themselves. The -fonts, including any derivative works, can be bundled, embedded, -redistributed and/or sold with any software provided that any reserved -names are not used by derivative works. The fonts and derivatives, -however, cannot be released under any other type of license. The -requirement for fonts to remain under this license does not apply -to any document created using the fonts or their derivatives. - -DEFINITIONS -"Font Software" refers to the set of files released by the Copyright -Holder(s) under this license and clearly marked as such. This may -include source files, build scripts and documentation. - -"Reserved Font Name" refers to any names specified as such after the -copyright statement(s). - -"Original Version" refers to the collection of Font Software components as -distributed by the Copyright Holder(s). - -"Modified Version" refers to any derivative made by adding to, deleting, -or substituting -- in part or in whole -- any of the components of the -Original Version, by changing formats or by porting the Font Software to a -new environment. - -"Author" refers to any designer, engineer, programmer, technical -writer or other person who contributed to the Font Software. - -PERMISSION & CONDITIONS -Permission is hereby granted, free of charge, to any person obtaining -a copy of the Font Software, to use, study, copy, merge, embed, modify, -redistribute, and sell modified and unmodified copies of the Font -Software, subject to the following conditions: - -1) Neither the Font Software nor any of its individual components, -in Original or Modified Versions, may be sold by itself. - -2) Original or Modified Versions of the Font Software may be bundled, -redistributed and/or sold with any software, provided that each copy -contains the above copyright notice and this license. These can be -included either as stand-alone text files, human-readable headers or -in the appropriate machine-readable metadata fields within text or -binary files as long as those fields can be easily viewed by the user. - -3) No Modified Version of the Font Software may use the Reserved Font -Name(s) unless explicit written permission is granted by the corresponding -Copyright Holder. This restriction only applies to the primary font name as -presented to the users. - -4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font -Software shall not be used to promote, endorse or advertise any -Modified Version, except to acknowledge the contribution(s) of the -Copyright Holder(s) and the Author(s) or with their explicit written -permission. - -5) The Font Software, modified or unmodified, in part or in whole, -must be distributed entirely under this license, and must not be -distributed under any other license. The requirement for fonts to -remain under this license does not apply to any document created -using the Font Software. - -TERMINATION -This license becomes null and void if any of the above conditions are -not met. - -DISCLAIMER -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE -COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL -DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM -OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/app/assets/fonts/SourceSansPro-Black.ttf.woff b/app/assets/fonts/SourceSansPro-Black.ttf.woff Binary files differdeleted file mode 100644 index b7e86200927..00000000000 --- a/app/assets/fonts/SourceSansPro-Black.ttf.woff +++ /dev/null diff --git a/app/assets/fonts/SourceSansPro-Black.ttf.woff2 b/app/assets/fonts/SourceSansPro-Black.ttf.woff2 Binary files differdeleted file mode 100644 index c90d078406c..00000000000 --- a/app/assets/fonts/SourceSansPro-Black.ttf.woff2 +++ /dev/null diff --git a/app/assets/fonts/SourceSansPro-BlackIt.ttf.woff b/app/assets/fonts/SourceSansPro-BlackIt.ttf.woff Binary files differdeleted file mode 100644 index c3314b1ef06..00000000000 --- a/app/assets/fonts/SourceSansPro-BlackIt.ttf.woff +++ /dev/null diff --git a/app/assets/fonts/SourceSansPro-BlackIt.ttf.woff2 b/app/assets/fonts/SourceSansPro-BlackIt.ttf.woff2 Binary files differdeleted file mode 100644 index b87e22c41b5..00000000000 --- a/app/assets/fonts/SourceSansPro-BlackIt.ttf.woff2 +++ /dev/null diff --git a/app/assets/fonts/SourceSansPro-Bold.ttf.woff b/app/assets/fonts/SourceSansPro-Bold.ttf.woff Binary files differdeleted file mode 100644 index d1d40f840f8..00000000000 --- a/app/assets/fonts/SourceSansPro-Bold.ttf.woff +++ /dev/null diff --git a/app/assets/fonts/SourceSansPro-Bold.ttf.woff2 b/app/assets/fonts/SourceSansPro-Bold.ttf.woff2 Binary files differdeleted file mode 100644 index 0f46f3e833a..00000000000 --- a/app/assets/fonts/SourceSansPro-Bold.ttf.woff2 +++ /dev/null diff --git a/app/assets/fonts/SourceSansPro-BoldIt.ttf.woff b/app/assets/fonts/SourceSansPro-BoldIt.ttf.woff Binary files differdeleted file mode 100644 index ef6ff514d3a..00000000000 --- a/app/assets/fonts/SourceSansPro-BoldIt.ttf.woff +++ /dev/null diff --git a/app/assets/fonts/SourceSansPro-BoldIt.ttf.woff2 b/app/assets/fonts/SourceSansPro-BoldIt.ttf.woff2 Binary files differdeleted file mode 100644 index 8007df6df32..00000000000 --- a/app/assets/fonts/SourceSansPro-BoldIt.ttf.woff2 +++ /dev/null diff --git a/app/assets/fonts/SourceSansPro-ExtraLight.ttf.woff b/app/assets/fonts/SourceSansPro-ExtraLight.ttf.woff Binary files differdeleted file mode 100644 index 1e6c94d9eb3..00000000000 --- a/app/assets/fonts/SourceSansPro-ExtraLight.ttf.woff +++ /dev/null diff --git a/app/assets/fonts/SourceSansPro-ExtraLight.ttf.woff2 b/app/assets/fonts/SourceSansPro-ExtraLight.ttf.woff2 Binary files differdeleted file mode 100644 index b715f274082..00000000000 --- a/app/assets/fonts/SourceSansPro-ExtraLight.ttf.woff2 +++ /dev/null diff --git a/app/assets/fonts/SourceSansPro-ExtraLightIt.ttf.woff b/app/assets/fonts/SourceSansPro-ExtraLightIt.ttf.woff Binary files differdeleted file mode 100644 index 7a408b1ec73..00000000000 --- a/app/assets/fonts/SourceSansPro-ExtraLightIt.ttf.woff +++ /dev/null diff --git a/app/assets/fonts/SourceSansPro-ExtraLightIt.ttf.woff2 b/app/assets/fonts/SourceSansPro-ExtraLightIt.ttf.woff2 Binary files differdeleted file mode 100644 index d8f9d29d4aa..00000000000 --- a/app/assets/fonts/SourceSansPro-ExtraLightIt.ttf.woff2 +++ /dev/null diff --git a/app/assets/fonts/SourceSansPro-It.ttf.woff b/app/assets/fonts/SourceSansPro-It.ttf.woff Binary files differdeleted file mode 100644 index 4d54bc95718..00000000000 --- a/app/assets/fonts/SourceSansPro-It.ttf.woff +++ /dev/null diff --git a/app/assets/fonts/SourceSansPro-It.ttf.woff2 b/app/assets/fonts/SourceSansPro-It.ttf.woff2 Binary files differdeleted file mode 100644 index a00852641f8..00000000000 --- a/app/assets/fonts/SourceSansPro-It.ttf.woff2 +++ /dev/null diff --git a/app/assets/fonts/SourceSansPro-Light.ttf.woff b/app/assets/fonts/SourceSansPro-Light.ttf.woff Binary files differdeleted file mode 100644 index 1706d57d3c5..00000000000 --- a/app/assets/fonts/SourceSansPro-Light.ttf.woff +++ /dev/null diff --git a/app/assets/fonts/SourceSansPro-Light.ttf.woff2 b/app/assets/fonts/SourceSansPro-Light.ttf.woff2 Binary files differdeleted file mode 100644 index d8b610ad76e..00000000000 --- a/app/assets/fonts/SourceSansPro-Light.ttf.woff2 +++ /dev/null diff --git a/app/assets/fonts/SourceSansPro-LightIt.ttf.woff b/app/assets/fonts/SourceSansPro-LightIt.ttf.woff Binary files differdeleted file mode 100644 index 87378d6c609..00000000000 --- a/app/assets/fonts/SourceSansPro-LightIt.ttf.woff +++ /dev/null diff --git a/app/assets/fonts/SourceSansPro-LightIt.ttf.woff2 b/app/assets/fonts/SourceSansPro-LightIt.ttf.woff2 Binary files differdeleted file mode 100644 index e0eebac8273..00000000000 --- a/app/assets/fonts/SourceSansPro-LightIt.ttf.woff2 +++ /dev/null diff --git a/app/assets/fonts/SourceSansPro-Regular.ttf.woff b/app/assets/fonts/SourceSansPro-Regular.ttf.woff Binary files differdeleted file mode 100644 index 460ab12a638..00000000000 --- a/app/assets/fonts/SourceSansPro-Regular.ttf.woff +++ /dev/null diff --git a/app/assets/fonts/SourceSansPro-Regular.ttf.woff2 b/app/assets/fonts/SourceSansPro-Regular.ttf.woff2 Binary files differdeleted file mode 100644 index 0dd3464c74b..00000000000 --- a/app/assets/fonts/SourceSansPro-Regular.ttf.woff2 +++ /dev/null diff --git a/app/assets/fonts/SourceSansPro-Semibold.ttf.woff b/app/assets/fonts/SourceSansPro-Semibold.ttf.woff Binary files differdeleted file mode 100644 index 43379631b2d..00000000000 --- a/app/assets/fonts/SourceSansPro-Semibold.ttf.woff +++ /dev/null diff --git a/app/assets/fonts/SourceSansPro-Semibold.ttf.woff2 b/app/assets/fonts/SourceSansPro-Semibold.ttf.woff2 Binary files differdeleted file mode 100644 index 2526d2e1b60..00000000000 --- a/app/assets/fonts/SourceSansPro-Semibold.ttf.woff2 +++ /dev/null diff --git a/app/assets/fonts/SourceSansPro-SemiboldIt.ttf.woff b/app/assets/fonts/SourceSansPro-SemiboldIt.ttf.woff Binary files differdeleted file mode 100644 index 232c2048ae7..00000000000 --- a/app/assets/fonts/SourceSansPro-SemiboldIt.ttf.woff +++ /dev/null diff --git a/app/assets/fonts/SourceSansPro-SemiboldIt.ttf.woff2 b/app/assets/fonts/SourceSansPro-SemiboldIt.ttf.woff2 Binary files differdeleted file mode 100644 index 606935af089..00000000000 --- a/app/assets/fonts/SourceSansPro-SemiboldIt.ttf.woff2 +++ /dev/null diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 76f3c6506ed..b7c4673c8e3 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -56,33 +56,18 @@ /*= require es6-promise.auto */ (function () { - document.addEventListener('page:fetch', gl.utils.cleanupBeforeFetch); - window.addEventListener('hashchange', gl.utils.shiftWindow); - - // automatically adjust scroll position for hash urls taking the height of the navbar into account - // https://github.com/twitter/bootstrap/issues/1768 - window.adjustScroll = function() { - var navbar = document.querySelector('.navbar-gitlab'); - var subnav = document.querySelector('.layout-nav'); - var fixedTabs = document.querySelector('.js-tabs-affix'); - - adjustment = 0; - if (navbar) adjustment -= navbar.offsetHeight; - if (subnav) adjustment -= subnav.offsetHeight; - if (fixedTabs) adjustment -= fixedTabs.offsetHeight; - - return scrollBy(0, adjustment); - }; + document.addEventListener('page:fetch', function () { + // Unbind scroll events + $(document).off('scroll'); + // Close any open tooltips + $('.has-tooltip, [data-toggle="tooltip"]').tooltip('destroy'); + }); - window.addEventListener("hashchange", adjustScroll); - - window.onload = function () { - // Scroll the window to avoid the topnav bar - // https://github.com/twitter/bootstrap/issues/1768 - if (location.hash) { - return setTimeout(adjustScroll, 100); - } - }; + window.addEventListener('hashchange', gl.utils.handleLocationHash); + window.addEventListener('load', function onLoad() { + window.removeEventListener('load', onLoad, false); + gl.utils.handleLocationHash(); + }, false); $(function () { var $body = $('body'); @@ -97,7 +82,15 @@ // Set the default path for all cookies to GitLab's root directory Cookies.defaults.path = gon.relative_url_root || '/'; - gl.utils.preventDisabledButtons(); + // prevent default action for disabled buttons + $('.btn').click(function(e) { + if ($(this).hasClass('disabled')) { + e.preventDefault(); + e.stopImmediatePropagation(); + return false; + } + }); + $('.nav-sidebar').niceScroll({ cursoropacitymax: '0.4', cursorcolor: '#FFF', diff --git a/app/assets/javascripts/boards/components/board_sidebar.js.es6 b/app/assets/javascripts/boards/components/board_sidebar.js.es6 index d5cb6164e0b..1644a772737 100644 --- a/app/assets/javascripts/boards/components/board_sidebar.js.es6 +++ b/app/assets/javascripts/boards/components/board_sidebar.js.es6 @@ -47,7 +47,7 @@ new gl.DueDateSelectors(); new LabelsSelect(); new Sidebar(); - new Subscription('.subscription'); + gl.Subscription.bindAll('.subscription'); } }); })(); diff --git a/app/assets/javascripts/diff.js b/app/assets/javascripts/diff.js deleted file mode 100644 index 00da5f17f9f..00000000000 --- a/app/assets/javascripts/diff.js +++ /dev/null @@ -1,73 +0,0 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, max-len, one-var, camelcase, one-var-declaration-per-line, no-unused-vars, no-unused-expressions, no-sequences, object-shorthand, comma-dangle, prefer-arrow-callback, semi, radix, padded-blocks, max-len */ -(function() { - this.Diff = (function() { - var UNFOLD_COUNT; - - UNFOLD_COUNT = 20; - - function Diff() { - $('.files .diff-file').singleFileDiff(); - this.filesCommentButton = $('.files .diff-file').filesCommentButton(); - if (this.diffViewType() === 'parallel') { - $('.content-wrapper .container-fluid').removeClass('container-limited'); - } - $(document).off('click', '.js-unfold'); - $(document).on('click', '.js-unfold', (function(_this) { - return function(event) { - var line_number, link, file, offset, old_line, params, prev_new_line, prev_old_line, ref, ref1, since, target, to, unfold, unfoldBottom; - target = $(event.target); - unfoldBottom = target.hasClass('js-unfold-bottom'); - unfold = true; - ref = _this.lineNumbers(target.parent()), old_line = ref[0], line_number = ref[1]; - offset = line_number - old_line; - if (unfoldBottom) { - line_number += 1; - since = line_number; - to = line_number + UNFOLD_COUNT; - } else { - ref1 = _this.lineNumbers(target.parent().prev()), prev_old_line = ref1[0], prev_new_line = ref1[1]; - line_number -= 1; - to = line_number; - if (line_number - UNFOLD_COUNT > prev_new_line + 1) { - since = line_number - UNFOLD_COUNT; - } else { - since = prev_new_line + 1; - unfold = false; - } - } - file = target.parents('.diff-file'); - link = file.data('blob-diff-path'); - params = { - since: since, - to: to, - bottom: unfoldBottom, - offset: offset, - unfold: unfold, - view: file.data('view') - }; - return $.get(link, params, function(response) { - return target.parent().replaceWith(response); - }); - }; - })(this)); - } - - Diff.prototype.diffViewType = function() { - return $('.inline-parallel-buttons a.active').data('view-type'); - } - - Diff.prototype.lineNumbers = function(line) { - if (!line.children().length) { - return [0, 0]; - } - - return line.find('.diff-line-num').map(function() { - return parseInt($(this).data('linenumber')); - }); - }; - - return Diff; - - })(); - -}).call(this); diff --git a/app/assets/javascripts/diff.js.es6 b/app/assets/javascripts/diff.js.es6 new file mode 100644 index 00000000000..ecf9d1de81c --- /dev/null +++ b/app/assets/javascripts/diff.js.es6 @@ -0,0 +1,109 @@ +/* eslint-disable class-methods-use-this */ + +(() => { + const UNFOLD_COUNT = 20; + + class Diff { + constructor() { + $('.files .diff-file').singleFileDiff(); + $('.files .diff-file').filesCommentButton(); + + if (this.diffViewType() === 'parallel') { + $('.content-wrapper .container-fluid').removeClass('container-limited'); + } + + $(document) + .off('click', '.js-unfold, .diff-line-num a') + .on('click', '.js-unfold', this.handleClickUnfold.bind(this)) + .on('click', '.diff-line-num a', this.handleClickLineNum.bind(this)); + + this.highlighSelectedLine(); + } + + handleClickUnfold(e) { + const $target = $(e.target); + // current babel config relies on iterators implementation, so we cannot simply do: + // const [oldLineNumber, newLineNumber] = this.lineNumbers($target.parent()); + const ref = this.lineNumbers($target.parent()); + const oldLineNumber = ref[0]; + const newLineNumber = ref[1]; + const offset = newLineNumber - oldLineNumber; + const bottom = $target.hasClass('js-unfold-bottom'); + let since; + let to; + let unfold = true; + + if (bottom) { + const lineNumber = newLineNumber + 1; + since = lineNumber; + to = lineNumber + UNFOLD_COUNT; + } else { + const lineNumber = newLineNumber - 1; + since = lineNumber - UNFOLD_COUNT; + to = lineNumber; + + // make sure we aren't loading more than we need + const prevNewLine = this.lineNumbers($target.parent().prev())[1]; + if (since <= prevNewLine + 1) { + since = prevNewLine + 1; + unfold = false; + } + } + + const file = $target.parents('.diff-file'); + const link = file.data('blob-diff-path'); + const view = file.data('view'); + + const params = { since, to, bottom, offset, unfold, view }; + $.get(link, params, response => $target.parent().replaceWith(response)); + } + + openAnchoredDiff(anchoredDiff, cb) { + const diffTitle = $(`#file-path-${anchoredDiff}`); + const diffFile = diffTitle.closest('.diff-file'); + const nothingHereBlock = $('.nothing-here-block:visible', diffFile); + if (nothingHereBlock.length) { + diffFile.singleFileDiff(true, cb); + } else { + cb(); + } + } + + handleClickLineNum(e) { + const hash = $(e.currentTarget).attr('href'); + e.preventDefault(); + if (window.history.pushState) { + window.history.pushState(null, null, hash); + } else { + window.location.hash = hash; + } + this.highlighSelectedLine(); + } + + diffViewType() { + return $('.inline-parallel-buttons a.active').data('view-type'); + } + + lineNumbers(line) { + if (!line.children().length) { + return [0, 0]; + } + return line.find('.diff-line-num').map((i, elm) => parseInt($(elm).data('linenumber'), 10)); + } + + highlighSelectedLine() { + const $diffFiles = $('.diff-file'); + $diffFiles.find('.hll').removeClass('hll'); + + if (window.location.hash !== '') { + const hash = window.location.hash.replace('#', ''); + $diffFiles + .find(`tr#${hash}:not(.match) td, td#${hash}, td[data-line-code="${hash}"]`) + .addClass('hll'); + } + } + } + + window.gl = window.gl || {}; + window.gl.Diff = Diff; +})(); diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6 index 16df4b0b005..3a7c5ff3681 100644 --- a/app/assets/javascripts/dispatcher.js.es6 +++ b/app/assets/javascripts/dispatcher.js.es6 @@ -61,7 +61,7 @@ new ZenMode(); break; case 'projects:compare:show': - new Diff(); + new gl.Diff(); break; case 'projects:issues:new': case 'projects:issues:edit': @@ -74,7 +74,7 @@ break; case 'projects:merge_requests:new': case 'projects:merge_requests:edit': - new Diff(); + new gl.Diff(); shortcut_handler = new ShortcutsNavigation(); new GLForm($('.merge-request-form')); new IssuableForm($('.merge-request-form')); @@ -91,7 +91,7 @@ new GLForm($('.release-form')); break; case 'projects:merge_requests:show': - new Diff(); + new gl.Diff(); shortcut_handler = new ShortcutsIssuable(true); new ZenMode(); new MergedButtons(); @@ -101,7 +101,7 @@ new MergedButtons(); break; case "projects:merge_requests:diffs": - new Diff(); + new gl.Diff(); new ZenMode(); new MergedButtons(); break; @@ -117,7 +117,7 @@ break; case 'projects:commit:show': new Commit(); - new Diff(); + new gl.Diff(); new ZenMode(); shortcut_handler = new ShortcutsNavigation(); break; @@ -135,8 +135,18 @@ new TreeView(); } break; + case 'projects:pipelines:builds': case 'projects:pipelines:show': - new gl.Pipelines(); + const { controllerAction } = document.querySelector('.js-pipeline-container').dataset; + + new gl.Pipelines({ + initTabs: true, + tabsOptions: { + action: controllerAction, + defaultAction: 'pipelines', + parentEl: '.pipelines-tabs', + }, + }); break; case 'groups:activity': new gl.Activities(); @@ -262,7 +272,7 @@ new NotificationsDropdown(); break; case 'wikis': - new Wikis(); + new gl.Wikis(); shortcut_handler = new ShortcutsNavigation(); new ZenMode(); new GLForm($('.wiki-form')); diff --git a/app/assets/javascripts/environments/components/environment_item.js.es6 b/app/assets/javascripts/environments/components/environment_item.js.es6 index 07f49cce3dc..6ed14261fc3 100644 --- a/app/assets/javascripts/environments/components/environment_item.js.es6 +++ b/app/assets/javascripts/environments/components/environment_item.js.es6 @@ -23,6 +23,7 @@ window.gl = window.gl || {}; window.gl.environmentsList = window.gl.environmentsList || {}; + window.gl.environmentsList.timeagoInstance = new timeago(); // eslint-disable-line gl.environmentsList.EnvironmentItem = Vue.component('environment-item', { @@ -148,14 +149,25 @@ }, /** + * Verifies if the date to be shown is present. + * + * @returns {Boolean|Undefined} + */ + canShowDate() { + return this.model.last_deployment && + this.model.last_deployment.deployable && + this.model.last_deployment.deployable !== undefined; + }, + + /** * Human readable date. * * @returns {String} */ createdDate() { - const timeagoInstance = new timeago(); // eslint-disable-line - - return timeagoInstance.format(this.model.created_at); + return window.gl.environmentsList.timeagoInstance.format( + this.model.last_deployment.deployable.created_at, + ); }, /** @@ -439,7 +451,7 @@ <div v-if="!isFolder && hasLastDeploymentKey" class="js-commit-component"> <commit-component :tag="commitTag" - :ref="commitRef" + :commit_ref="commitRef" :commit_url="commitUrl" :short_sha="commitShortSha" :title="commitTitle" @@ -453,7 +465,7 @@ <td> <span - v-if="!isFolder && model.last_deployment" + v-if="!isFolder && canShowDate" class="environment-created-date-timeago"> {{createdDate}} </span> diff --git a/app/assets/javascripts/extensions/element.js.es6 b/app/assets/javascripts/extensions/element.js.es6 index 6d9b0c4bc3e..3f12ad9ff9f 100644 --- a/app/assets/javascripts/extensions/element.js.es6 +++ b/app/assets/javascripts/extensions/element.js.es6 @@ -1,9 +1,20 @@ /* global Element */ -/* eslint-disable consistent-return, max-len */ - -Element.prototype.matches = Element.prototype.matches || Element.prototype.msMatchesSelector; +/* eslint-disable consistent-return, max-len, no-empty, no-plusplus, func-names */ Element.prototype.closest = Element.prototype.closest || function closest(selector, selectedElement = this) { if (!selectedElement) return; return selectedElement.matches(selector) ? selectedElement : Element.prototype.closest(selector, selectedElement.parentElement); }; + +Element.prototype.matches = Element.prototype.matches || + Element.prototype.matchesSelector || + Element.prototype.mozMatchesSelector || + Element.prototype.msMatchesSelector || + Element.prototype.oMatchesSelector || + Element.prototype.webkitMatchesSelector || + function (s) { + const matches = (this.document || this.ownerDocument).querySelectorAll(s); + let i = matches.length; + while (--i >= 0 && matches.item(i) !== this) {} + return i > -1; + }; diff --git a/app/assets/javascripts/gfm_auto_complete.js.es6 b/app/assets/javascripts/gfm_auto_complete.js.es6 index 89fe13b7a45..6f9d6283071 100644 --- a/app/assets/javascripts/gfm_auto_complete.js.es6 +++ b/app/assets/javascripts/gfm_auto_complete.js.es6 @@ -5,6 +5,10 @@ window.GitLab = {}; } + function sanitize(str) { + return str.replace(/<(?:.|\n)*?>/gm, ''); + } + GitLab.GfmAutoComplete = { dataLoading: false, dataLoaded: false, @@ -59,12 +63,13 @@ // Tweaked to commands to start without a space only if char before is a non-word character // https://github.com/ichord/At.js var _a, _y, regexp, match; + subtext = subtext.split(' ').pop(); flag = flag.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); _a = decodeURI("%C3%80"); _y = decodeURI("%C3%BF"); - regexp = new RegExp("(?:\\B|\\W|\\s)" + flag + "([A-Za-z" + _a + "-" + _y + "0-9_\'\.\+\-]*)|([^\\x00-\\xff]*)$", 'gi'); + regexp = new RegExp("(?:\\B|\\W|\\s)" + flag + "(?!\\W)([A-Za-z" + _a + "-" + _y + "0-9_\'\.\+\-]*)|([^\\x00-\\xff]*)$", 'gi'); match = regexp.exec(subtext); @@ -159,8 +164,8 @@ return { username: m.username, avatarTag: autoCompleteAvatar.length === 1 ? txtAvatar : imgAvatar, - title: gl.utils.sanitize(title), - search: gl.utils.sanitize(m.username + " " + m.name) + title: sanitize(title), + search: sanitize(m.username + " " + m.name) }; }); } @@ -194,7 +199,7 @@ } return { id: i.iid, - title: gl.utils.sanitize(i.title), + title: sanitize(i.title), search: i.iid + " " + i.title }; }); @@ -227,7 +232,7 @@ } return { id: m.iid, - title: gl.utils.sanitize(m.title), + title: sanitize(m.title), search: "" + m.title }; }); @@ -262,7 +267,7 @@ } return { id: m.iid, - title: gl.utils.sanitize(m.title), + title: sanitize(m.title), search: m.iid + " " + m.title }; }); @@ -283,9 +288,9 @@ var sanitizeLabelTitle; sanitizeLabelTitle = function(title) { if (/[\w\?&]+\s+[\w\?&]+/g.test(title)) { - return "\"" + (gl.utils.sanitize(title)) + "\""; + return "\"" + (sanitize(title)) + "\""; } else { - return gl.utils.sanitize(title); + return sanitize(title); } }; return $.map(merges, function(m) { diff --git a/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js.es6 b/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js.es6 new file mode 100644 index 00000000000..e810ee85bd3 --- /dev/null +++ b/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js.es6 @@ -0,0 +1,113 @@ +/** + * Linked Tabs + * + * Handles persisting and restores the current tab selection and content. + * Reusable component for static content. + * + * ### Example Markup + * + * <ul class="nav-links tab-links"> + * <li class="active"> + * <a data-action="tab1" data-target="#tab1" data-toggle="tab" href="/path/tab1"> + * Tab 1 + * </a> + * </li> + * <li class="groups-tab"> + * <a data-action="tab2" data-target="#tab2" data-toggle="tab" href="/path/tab2"> + * Tab 2 + * </a> + * </li> + * + * + * <div class="tab-content"> + * <div class="tab-pane" id="tab1"> + * Tab 1 Content + * </div> + * <div class="tab-pane" id="tab2"> + * Tab 2 Content + * </div> + * </div> + * + * + * ### How to use + * + * new window.gl.LinkedTabs({ + * action: "#{controller.action_name}", + * defaultAction: 'tab1', + * parentEl: '.tab-links' + * }); + */ + +(() => { + window.gl = window.gl || {}; + + window.gl.LinkedTabs = class LinkedTabs { + /** + * Binds the events and activates de default tab. + * + * @param {Object} options + */ + constructor(options) { + this.options = options || {}; + + this.defaultAction = this.options.defaultAction; + this.action = this.options.action || this.defaultAction; + + if (this.action === 'show') { + this.action = this.defaultAction; + } + + this.currentLocation = window.location; + + const tabSelector = `${this.options.parentEl} a[data-toggle="tab"]`; + + // since this is a custom event we need jQuery :( + $(document) + .off('shown.bs.tab', tabSelector) + .on('shown.bs.tab', tabSelector, e => this.tabShown(e)); + + this.activateTab(this.action); + } + + /** + * Handles the `shown.bs.tab` event to set the currect url action. + * + * @param {type} evt + * @return {Function} + */ + tabShown(evt) { + const source = evt.target.getAttribute('href'); + + return this.setCurrentAction(source); + } + + /** + * Updates the URL with the path that matched the given action. + * + * @param {String} source + * @return {String} + */ + setCurrentAction(source) { + const copySource = source; + + copySource.replace(/\/+$/, ''); + + const newState = `${copySource}${this.currentLocation.search}${this.currentLocation.hash}`; + + history.replaceState({ + turbolinks: true, + url: newState, + }, document.title, newState); + return newState; + } + + /** + * Given the current action activates the correct tab. + * http://getbootstrap.com/javascript/#tab-show + * Note: Will trigger `shown.bs.tab` + */ + activateTab() { + return $(`${this.options.parentEl} a[data-action='${this.action}']`).tab('show'); + } + }; +})(); diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js index d83c41fae9d..29cba1a49dd 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js +++ b/app/assets/javascripts/lib/utils/common_utils.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-unused-expressions, no-param-reassign, no-else-return, quotes, object-shorthand, comma-dangle, camelcase, one-var, vars-on-top, one-var-declaration-per-line, no-return-assign, consistent-return, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-unused-expressions, no-param-reassign, no-else-return, quotes, object-shorthand, comma-dangle, camelcase, one-var, vars-on-top, one-var-declaration-per-line, no-return-assign, consistent-return, padded-blocks, max-len, prefer-template */ (function() { (function(w) { var base; @@ -33,10 +33,6 @@ }); }; - w.gl.utils.split = function(val) { - return val.split(/,\s*/); - }; - w.gl.utils.extractLast = function(term) { return this.split(term).pop(); }; @@ -67,64 +63,39 @@ }); }; - w.gl.utils.disableButtonIfAnyEmptyField = function(form, form_selector, button_selector) { - var closest_submit, updateButtons; - closest_submit = form.find(button_selector); - updateButtons = function() { - var filled; - filled = true; - form.find('input').filter(form_selector).each(function() { - return filled = this.rstrip($(this).val()) !== "" || !$(this).attr('required'); - }); - if (filled) { - return closest_submit.enable(); - } else { - return closest_submit.disable(); - } - }; - updateButtons(); - return form.keyup(updateButtons); - }; - - w.gl.utils.sanitize = function(str) { - return str.replace(/<(?:.|\n)*?>/gm, ''); - }; - - w.gl.utils.unbindEvents = function() { - return $(document).off('scroll'); - }; + // automatically adjust scroll position for hash urls taking the height of the navbar into account + // https://github.com/twitter/bootstrap/issues/1768 + w.gl.utils.handleLocationHash = function() { + var hash = w.gl.utils.getLocationHash(); + if (!hash) return; - w.gl.utils.shiftWindow = function() { - return w.scrollBy(0, -100); - }; + var navbar = document.querySelector('.navbar-gitlab'); + var subnav = document.querySelector('.layout-nav'); + var fixedTabs = document.querySelector('.js-tabs-affix'); + var adjustment = 0; + if (navbar) adjustment -= navbar.offsetHeight; + if (subnav) adjustment -= subnav.offsetHeight; - gl.utils.updateTooltipTitle = function($tooltipEl, newTitle) { - return $tooltipEl.tooltip('destroy').attr('title', newTitle).tooltip('fixTitle'); - }; - gl.utils.preventDisabledButtons = function() { - return $('.btn').click(function(e) { - if ($(this).hasClass('disabled')) { - e.preventDefault(); - e.stopImmediatePropagation(); - return false; + // scroll to user-generated markdown anchor if we cannot find a match + if (document.getElementById(hash) === null) { + var target = document.getElementById('user-content-' + hash); + if (target && target.scrollIntoView) { + target.scrollIntoView(true); + window.scrollBy(0, adjustment); } - }); + } else { + // only adjust for fixedTabs when not targeting user-generated content + if (fixedTabs) { + adjustment -= fixedTabs.offsetHeight; + } + window.scrollBy(0, adjustment); + } }; + gl.utils.getPagePath = function() { return $('body').data('page').split(':')[0]; }; - gl.utils.parseUrl = function (url) { - var parser = document.createElement('a'); - parser.href = url; - return parser; - }; - gl.utils.cleanupBeforeFetch = function() { - // Unbind scroll events - $(document).off('scroll'); - // Close any open tooltips - $('.has-tooltip, [data-toggle="tooltip"]').tooltip('destroy'); - }; gl.utils.isMetaKey = function(e) { return e.metaKey || e.ctrlKey || e.altKey || e.shiftKey; diff --git a/app/assets/javascripts/merge_request.js b/app/assets/javascripts/merge_request.js index a4b4db14db8..88c3636be6c 100644 --- a/app/assets/javascripts/merge_request.js +++ b/app/assets/javascripts/merge_request.js @@ -40,7 +40,7 @@ if (window.mrTabs) { window.mrTabs.unbindEvents(); } - window.mrTabs = new MergeRequestTabs(this.opts); + window.mrTabs = new gl.MergeRequestTabs(this.opts); }; MergeRequest.prototype.showAllCommits = function() { diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js deleted file mode 100644 index b1928f8d279..00000000000 --- a/app/assets/javascripts/merge_request_tabs.js +++ /dev/null @@ -1,442 +0,0 @@ -/* eslint-disable max-len, func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, no-use-before-define, no-underscore-dangle, no-undef, one-var, one-var-declaration-per-line, quotes, comma-dangle, consistent-return, prefer-template, no-param-reassign, camelcase, vars-on-top, space-in-parens, curly, prefer-arrow-callback, no-unused-vars, no-return-assign, semi, object-shorthand, operator-assignment, padded-blocks, max-len */ -// MergeRequestTabs -// -// Handles persisting and restoring the current tab selection and lazily-loading -// content on the MergeRequests#show page. -// -/*= require js.cookie */ - -// -// ### Example Markup -// -// <ul class="nav-links merge-request-tabs"> -// <li class="notes-tab active"> -// <a data-action="notes" data-target="#notes" data-toggle="tab" href="/foo/bar/merge_requests/1"> -// Discussion -// </a> -// </li> -// <li class="commits-tab"> -// <a data-action="commits" data-target="#commits" data-toggle="tab" href="/foo/bar/merge_requests/1/commits"> -// Commits -// </a> -// </li> -// <li class="diffs-tab"> -// <a data-action="diffs" data-target="#diffs" data-toggle="tab" href="/foo/bar/merge_requests/1/diffs"> -// Diffs -// </a> -// </li> -// </ul> -// -// <div class="tab-content"> -// <div class="notes tab-pane active" id="notes"> -// Notes Content -// </div> -// <div class="commits tab-pane" id="commits"> -// Commits Content -// </div> -// <div class="diffs tab-pane" id="diffs"> -// Diffs Content -// </div> -// </div> -// -// <div class="mr-loading-status"> -// <div class="loading"> -// Loading Animation -// </div> -// </div> -// -(function() { - var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; - - this.MergeRequestTabs = (function() { - MergeRequestTabs.prototype.diffsLoaded = false; - - MergeRequestTabs.prototype.buildsLoaded = false; - - MergeRequestTabs.prototype.pipelinesLoaded = false; - - MergeRequestTabs.prototype.commitsLoaded = false; - - MergeRequestTabs.prototype.fixedLayoutPref = null; - - function MergeRequestTabs(opts) { - this.opts = opts != null ? opts : {}; - this.opts.setUrl = this.opts.setUrl !== undefined ? this.opts.setUrl : true; - - this.buildsLoaded = this.opts.buildsLoaded || false; - - this.setCurrentAction = bind(this.setCurrentAction, this); - this.tabShown = bind(this.tabShown, this); - this.showTab = bind(this.showTab, this); - // Store the `location` object, allowing for easier stubbing in tests - this._location = location; - this.bindEvents(); - this.activateTab(this.opts.action); - this.initAffix(); - } - - MergeRequestTabs.prototype.bindEvents = function() { - $(document).on('shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', this.tabShown); - $(document).on('click', '.js-show-tab', this.showTab); - }; - - MergeRequestTabs.prototype.unbindEvents = function() { - $(document).off('shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', this.tabShown); - $(document).off('click', '.js-show-tab', this.showTab); - }; - - MergeRequestTabs.prototype.showTab = function(event) { - event.preventDefault(); - return this.activateTab($(event.target).data('action')); - }; - - MergeRequestTabs.prototype.tabShown = function(event) { - var $target, action, navBarHeight; - $target = $(event.target); - action = $target.data('action'); - if (action === 'commits') { - this.loadCommits($target.attr('href')); - this.expandView(); - this.resetViewContainer(); - } else if (this.isDiffAction(action)) { - this.loadDiff($target.attr('href')); - if ((typeof bp !== "undefined" && bp !== null) && bp.getBreakpointSize() !== 'lg') { - this.shrinkView(); - } - if (this.diffViewType() === 'parallel') { - this.expandViewContainer(); - } - navBarHeight = $('.navbar-gitlab').outerHeight(); - $.scrollTo(".merge-request-details .merge-request-tabs", { - offset: -navBarHeight - }); - } else if (action === 'builds') { - this.loadBuilds($target.attr('href')); - this.expandView(); - this.resetViewContainer(); - } else if (action === 'pipelines') { - this.loadPipelines($target.attr('href')); - this.expandView(); - this.resetViewContainer(); - } else { - this.expandView(); - this.resetViewContainer(); - } - if (this.opts.setUrl) { - this.setCurrentAction(action); - } - }; - - MergeRequestTabs.prototype.scrollToElement = function(container) { - var $el, navBarHeight; - if (window.location.hash) { - navBarHeight = $('.navbar-gitlab').outerHeight() + $('.layout-nav').outerHeight() + document.querySelector('.js-tabs-affix').offsetHeight; - $el = $(container + " " + window.location.hash + ":not(.match)"); - if ($el.length) { - return $.scrollTo(container + " " + window.location.hash + ":not(.match)", { - offset: -navBarHeight - }); - } - } - }; - - // Activate a tab based on the current action - MergeRequestTabs.prototype.activateTab = function(action) { - if (action === 'show') { - action = 'notes'; - } - // important note: the .tab('show') method triggers 'shown.bs.tab' event itself - $(".merge-request-tabs a[data-action='" + action + "']").tab('show'); - }; - - // Replaces the current Merge Request-specific action in the URL with a new one - // - // If the action is "notes", the URL is reset to the standard - // `MergeRequests#show` route. - // - // Examples: - // - // location.pathname # => "/namespace/project/merge_requests/1" - // setCurrentAction('diffs') - // location.pathname # => "/namespace/project/merge_requests/1/diffs" - // - // location.pathname # => "/namespace/project/merge_requests/1/diffs" - // setCurrentAction('notes') - // location.pathname # => "/namespace/project/merge_requests/1" - // - // location.pathname # => "/namespace/project/merge_requests/1/diffs" - // setCurrentAction('commits') - // location.pathname # => "/namespace/project/merge_requests/1/commits" - // - // Returns the new URL String - MergeRequestTabs.prototype.setCurrentAction = function(action) { - var new_state; - // Normalize action, just to be safe - if (action === 'show') { - action = 'notes'; - } - this.currentAction = action; - // Remove a trailing '/commits' '/diffs' '/builds' '/pipelines' '/new' '/new/diffs' - new_state = this._location.pathname.replace(/\/(commits|diffs|builds|pipelines|new|new\/diffs)(\.html)?\/?$/, ''); - - // Append the new action if we're on a tab other than 'notes' - if (action !== 'notes') { - new_state += "/" + action; - } - // Ensure parameters and hash come along for the ride - new_state += this._location.search + this._location.hash; - history.replaceState({ - turbolinks: true, - url: new_state - // Replace the current history state with the new one without breaking - // Turbolinks' history. - // - // See https://github.com/rails/turbolinks/issues/363 - }, document.title, new_state); - return new_state; - }; - - MergeRequestTabs.prototype.loadCommits = function(source) { - if (this.commitsLoaded) { - return; - } - return this._get({ - url: source + ".json", - success: (function(_this) { - return function(data) { - document.querySelector("div#commits").innerHTML = data.html; - gl.utils.localTimeAgo($('.js-timeago', 'div#commits')); - _this.commitsLoaded = true; - return _this.scrollToElement("#commits"); - }; - })(this) - }); - }; - - MergeRequestTabs.prototype.loadDiff = function(source) { - if (this.diffsLoaded) { - return; - } - - // We extract pathname for the current Changes tab anchor href - // some pages like MergeRequestsController#new has query parameters on that anchor - var url = gl.utils.parseUrl(source); - - return this._get({ - url: (url.pathname + ".json") + this._location.search, - success: (function(_this) { - return function(data) { - $('#diffs').html(data.html); - - if (typeof gl.diffNotesCompileComponents !== 'undefined') { - gl.diffNotesCompileComponents(); - } - - gl.utils.localTimeAgo($('.js-timeago', 'div#diffs')); - $('#diffs .js-syntax-highlight').syntaxHighlight(); - $('#diffs .diff-file').singleFileDiff(); - if (_this.diffViewType() === 'parallel' && (_this.isDiffAction(_this.currentAction)) ) { - _this.expandViewContainer(); - } - _this.diffsLoaded = true; - var anchoredDiff = gl.utils.getLocationHash(); - if (anchoredDiff) _this.openAnchoredDiff(anchoredDiff, function() { - _this.scrollToElement("#diffs"); - _this.highlighSelectedLine(); - }); - _this.filesCommentButton = $('.files .diff-file').filesCommentButton(); - return $(document).off('click', '.diff-line-num a').on('click', '.diff-line-num a', function(e) { - e.preventDefault(); - window.location.hash = $(e.currentTarget).attr('href'); - _this.highlighSelectedLine(); - return _this.scrollToElement("#diffs"); - }); - }; - })(this) - }); - }; - - MergeRequestTabs.prototype.openAnchoredDiff = function(anchoredDiff, cb) { - var diffTitle = $('#file-path-' + anchoredDiff); - var diffFile = diffTitle.closest('.diff-file'); - var nothingHereBlock = $('.nothing-here-block:visible', diffFile); - if (nothingHereBlock.length) { - diffFile.singleFileDiff(true, cb); - } else { - cb(); - } - }; - - MergeRequestTabs.prototype.highlighSelectedLine = function() { - var $diffLine, diffLineTop, hashClassString, locationHash, navBarHeight; - $('.hll').removeClass('hll'); - locationHash = window.location.hash; - if (locationHash !== '') { - dataLineString = '[data-line-code="' + locationHash.replace('#', '') + '"]'; - $diffLine = $(locationHash + ":not(.match)", $('#diffs')); - if (!$diffLine.is('tr')) { - $diffLine = $('#diffs').find("td" + locationHash + ", td" + dataLineString); - } else { - $diffLine = $diffLine.find('td'); - } - if ($diffLine.length) { - $diffLine.addClass('hll'); - diffLineTop = $diffLine.offset().top; - return navBarHeight = $('.navbar-gitlab').outerHeight(); - } - } - }; - - MergeRequestTabs.prototype.loadBuilds = function(source) { - if (this.buildsLoaded) { - return; - } - return this._get({ - url: source + ".json", - success: (function(_this) { - return function(data) { - document.querySelector("div#builds").innerHTML = data.html; - gl.utils.localTimeAgo($('.js-timeago', 'div#builds')); - _this.buildsLoaded = true; - if (!this.pipelines) this.pipelines = new gl.Pipelines(); - return _this.scrollToElement("#builds"); - }; - })(this) - }); - }; - - MergeRequestTabs.prototype.loadPipelines = function(source) { - if (this.pipelinesLoaded) { - return; - } - return this._get({ - url: source + ".json", - success: function(data) { - $('#pipelines').html(data.html); - gl.utils.localTimeAgo($('.js-timeago', '#pipelines')); - this.pipelinesLoaded = true; - return this.scrollToElement("#pipelines"); - }.bind(this) - }); - }; - - // Show or hide the loading spinner - // - // status - Boolean, true to show, false to hide - MergeRequestTabs.prototype.toggleLoading = function(status) { - return $('.mr-loading-status .loading').toggle(status); - }; - - MergeRequestTabs.prototype._get = function(options) { - var defaults; - defaults = { - beforeSend: (function(_this) { - return function() { - return _this.toggleLoading(true); - }; - })(this), - complete: (function(_this) { - return function() { - return _this.toggleLoading(false); - }; - })(this), - dataType: 'json', - type: 'GET' - }; - options = $.extend({}, defaults, options); - return $.ajax(options); - }; - - MergeRequestTabs.prototype.diffViewType = function() { - return $('.inline-parallel-buttons a.active').data('view-type'); - }; - - MergeRequestTabs.prototype.isDiffAction = function(action) { - return action === 'diffs' || action === 'new/diffs' - }; - - MergeRequestTabs.prototype.expandViewContainer = function() { - var $wrapper = $('.content-wrapper .container-fluid'); - if (this.fixedLayoutPref === null) { - this.fixedLayoutPref = $wrapper.hasClass('container-limited'); - } - $wrapper.removeClass('container-limited'); - }; - - MergeRequestTabs.prototype.resetViewContainer = function() { - if (this.fixedLayoutPref !== null) { - $('.content-wrapper .container-fluid') - .toggleClass('container-limited', this.fixedLayoutPref); - } - }; - - MergeRequestTabs.prototype.shrinkView = function() { - var $gutterIcon; - $gutterIcon = $('.js-sidebar-toggle i:visible'); - return setTimeout(function() { - if ($gutterIcon.is('.fa-angle-double-right')) { - return $gutterIcon.closest('a').trigger('click', [true]); - } - // Wait until listeners are set - // Only when sidebar is expanded - }, 0); - }; - - MergeRequestTabs.prototype.expandView = function() { - var $gutterIcon; - if (Cookies.get('collapsed_gutter') === 'true') { - return; - } - $gutterIcon = $('.js-sidebar-toggle i:visible'); - return setTimeout(function() { - if ($gutterIcon.is('.fa-angle-double-left')) { - return $gutterIcon.closest('a').trigger('click', [true]); - } - }, 0); - // Expand the issuable sidebar unless the user explicitly collapsed it - // Wait until listeners are set - // Only when sidebar is collapsed - }; - - MergeRequestTabs.prototype.initAffix = function () { - var $tabs = $('.js-tabs-affix'); - - // Screen space on small screens is usually very sparse - // So we dont affix the tabs on these - if (Breakpoints.get().getBreakpointSize() === 'xs' || !$tabs.length) return; - - var $diffTabs = $('#diff-notes-app'), - $fixedNav = $('.navbar-fixed-top'), - $layoutNav = $('.layout-nav'); - - $tabs.off('affix.bs.affix affix-top.bs.affix') - .affix({ - offset: { - top: function () { - var tabsTop = $diffTabs.offset().top - $tabs.height(); - tabsTop = tabsTop - ($fixedNav.height() + $layoutNav.height()); - - return tabsTop; - } - } - }).on('affix.bs.affix', function () { - $diffTabs.css({ - marginTop: $tabs.height() - }); - }).on('affix-top.bs.affix', function () { - $diffTabs.css({ - marginTop: '' - }); - }); - - // Fix bug when reloading the page already scrolling - if ($tabs.hasClass('affix')) { - $tabs.trigger('affix.bs.affix'); - } - }; - - return MergeRequestTabs; - - })(); - -}).call(this); diff --git a/app/assets/javascripts/merge_request_tabs.js.es6 b/app/assets/javascripts/merge_request_tabs.js.es6 new file mode 100644 index 00000000000..583fb9fc03d --- /dev/null +++ b/app/assets/javascripts/merge_request_tabs.js.es6 @@ -0,0 +1,390 @@ +/* eslint-disable no-new, class-methods-use-this */ +/* global Breakpoints */ +/* global Cookies */ +/* global DiffNotesApp */ +/* global Flash */ + +/*= require js.cookie */ +/*= require breakpoints */ + +/* eslint-disable max-len */ +// MergeRequestTabs +// +// Handles persisting and restoring the current tab selection and lazily-loading +// content on the MergeRequests#show page. +// +// ### Example Markup +// +// <ul class="nav-links merge-request-tabs"> +// <li class="notes-tab active"> +// <a data-action="notes" data-target="#notes" data-toggle="tab" href="/foo/bar/merge_requests/1"> +// Discussion +// </a> +// </li> +// <li class="commits-tab"> +// <a data-action="commits" data-target="#commits" data-toggle="tab" href="/foo/bar/merge_requests/1/commits"> +// Commits +// </a> +// </li> +// <li class="diffs-tab"> +// <a data-action="diffs" data-target="#diffs" data-toggle="tab" href="/foo/bar/merge_requests/1/diffs"> +// Diffs +// </a> +// </li> +// </ul> +// +// <div class="tab-content"> +// <div class="notes tab-pane active" id="notes"> +// Notes Content +// </div> +// <div class="commits tab-pane" id="commits"> +// Commits Content +// </div> +// <div class="diffs tab-pane" id="diffs"> +// Diffs Content +// </div> +// </div> +// +// <div class="mr-loading-status"> +// <div class="loading"> +// Loading Animation +// </div> +// </div> +// +/* eslint-enable max-len */ + +(() => { + // Store the `location` object, allowing for easier stubbing in tests + let location = window.location; + + class MergeRequestTabs { + + constructor({ action, setUrl, buildsLoaded, stubLocation } = {}) { + this.diffsLoaded = false; + this.buildsLoaded = false; + this.pipelinesLoaded = false; + this.commitsLoaded = false; + this.fixedLayoutPref = null; + + this.setUrl = setUrl !== undefined ? setUrl : true; + this.buildsLoaded = buildsLoaded || false; + + this.setCurrentAction = this.setCurrentAction.bind(this); + this.tabShown = this.tabShown.bind(this); + this.showTab = this.showTab.bind(this); + + if (stubLocation) { + location = stubLocation; + } + + this.bindEvents(); + this.activateTab(action); + this.initAffix(); + } + + bindEvents() { + $(document) + .on('shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', this.tabShown) + .on('click', '.js-show-tab', this.showTab); + } + + unbindEvents() { + $(document) + .off('shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', this.tabShown) + .off('click', '.js-show-tab', this.showTab); + } + + showTab(e) { + e.preventDefault(); + this.activateTab($(e.target).data('action')); + } + + tabShown(e) { + const $target = $(e.target); + const action = $target.data('action'); + + if (action === 'commits') { + this.loadCommits($target.attr('href')); + this.expandView(); + this.resetViewContainer(); + } else if (this.isDiffAction(action)) { + this.loadDiff($target.attr('href')); + if (Breakpoints.get().getBreakpointSize() !== 'lg') { + this.shrinkView(); + } + if (this.diffViewType() === 'parallel') { + this.expandViewContainer(); + } + const navBarHeight = $('.navbar-gitlab').outerHeight(); + $.scrollTo('.merge-request-details .merge-request-tabs', { + offset: -navBarHeight, + }); + } else if (action === 'builds') { + this.loadBuilds($target.attr('href')); + this.expandView(); + this.resetViewContainer(); + } else if (action === 'pipelines') { + this.loadPipelines($target.attr('href')); + this.expandView(); + this.resetViewContainer(); + } else { + this.expandView(); + this.resetViewContainer(); + } + if (this.setUrl) { + this.setCurrentAction(action); + } + } + + scrollToElement(container) { + if (location.hash) { + const offset = 0 - ( + $('.navbar-gitlab').outerHeight() + + $('.layout-nav').outerHeight() + + $('.js-tabs-affix').outerHeight() + ); + const $el = $(`${container} ${location.hash}:not(.match)`); + if ($el.length) { + $.scrollTo($el[0], { offset }); + } + } + } + + // Activate a tab based on the current action + activateTab(action) { + const activate = action === 'show' ? 'notes' : action; + // important note: the .tab('show') method triggers 'shown.bs.tab' event itself + $(`.merge-request-tabs a[data-action='${activate}']`).tab('show'); + } + + // Replaces the current Merge Request-specific action in the URL with a new one + // + // If the action is "notes", the URL is reset to the standard + // `MergeRequests#show` route. + // + // Examples: + // + // location.pathname # => "/namespace/project/merge_requests/1" + // setCurrentAction('diffs') + // location.pathname # => "/namespace/project/merge_requests/1/diffs" + // + // location.pathname # => "/namespace/project/merge_requests/1/diffs" + // setCurrentAction('notes') + // location.pathname # => "/namespace/project/merge_requests/1" + // + // location.pathname # => "/namespace/project/merge_requests/1/diffs" + // setCurrentAction('commits') + // location.pathname # => "/namespace/project/merge_requests/1/commits" + // + // Returns the new URL String + setCurrentAction(action) { + this.currentAction = action === 'show' ? 'notes' : action; + + // Remove a trailing '/commits' '/diffs' '/builds' '/pipelines' '/new' '/new/diffs' + let newState = location.pathname.replace(/\/(commits|diffs|builds|pipelines|new|new\/diffs)(\.html)?\/?$/, ''); + + // Append the new action if we're on a tab other than 'notes' + if (this.currentAction !== 'notes') { + newState += `/${this.currentAction}`; + } + + // Ensure parameters and hash come along for the ride + newState += location.search + location.hash; + + // Replace the current history state with the new one without breaking + // Turbolinks' history. + // + // See https://github.com/rails/turbolinks/issues/363 + window.history.replaceState({ + turbolinks: true, + url: newState, + }, document.title, newState); + + return newState; + } + + loadCommits(source) { + if (this.commitsLoaded) { + return; + } + this.ajaxGet({ + url: `${source}.json`, + success: (data) => { + document.querySelector('div#commits').innerHTML = data.html; + gl.utils.localTimeAgo($('.js-timeago', 'div#commits')); + this.commitsLoaded = true; + this.scrollToElement('#commits'); + }, + }); + } + + loadDiff(source) { + if (this.diffsLoaded) { + return; + } + + // We extract pathname for the current Changes tab anchor href + // some pages like MergeRequestsController#new has query parameters on that anchor + const url = document.createElement('a'); + url.href = source; + + this.ajaxGet({ + url: `${url.pathname}.json${location.search}`, + success: (data) => { + $('#diffs').html(data.html); + + if (typeof gl.diffNotesCompileComponents !== 'undefined') { + gl.diffNotesCompileComponents(); + } + + gl.utils.localTimeAgo($('.js-timeago', 'div#diffs')); + $('#diffs .js-syntax-highlight').syntaxHighlight(); + + if (this.diffViewType() === 'parallel' && this.isDiffAction(this.currentAction)) { + this.expandViewContainer(); + } + this.diffsLoaded = true; + + const diffPage = new gl.Diff(); + + const locationHash = gl.utils.getLocationHash(); + const anchoredDiff = locationHash && locationHash.split('_')[0]; + if (anchoredDiff) { + diffPage.openAnchoredDiff(anchoredDiff, () => this.scrollToElement('#diffs')); + } + }, + }); + } + + loadBuilds(source) { + if (this.buildsLoaded) { + return; + } + this.ajaxGet({ + url: `${source}.json`, + success: (data) => { + document.querySelector('div#builds').innerHTML = data.html; + gl.utils.localTimeAgo($('.js-timeago', 'div#builds')); + this.buildsLoaded = true; + new gl.Pipelines(); + this.scrollToElement('#builds'); + }, + }); + } + + loadPipelines(source) { + if (this.pipelinesLoaded) { + return; + } + this.ajaxGet({ + url: `${source}.json`, + success: (data) => { + $('#pipelines').html(data.html); + gl.utils.localTimeAgo($('.js-timeago', '#pipelines')); + this.pipelinesLoaded = true; + this.scrollToElement('#pipelines'); + }, + }); + } + + // Show or hide the loading spinner + // + // status - Boolean, true to show, false to hide + toggleLoading(status) { + $('.mr-loading-status .loading').toggle(status); + } + + ajaxGet(options) { + const defaults = { + beforeSend: () => this.toggleLoading(true), + error: () => new Flash('An error occurred while fetching this tab.', 'alert'), + complete: () => this.toggleLoading(false), + dataType: 'json', + type: 'GET', + }; + $.ajax($.extend({}, defaults, options)); + } + + diffViewType() { + return $('.inline-parallel-buttons a.active').data('view-type'); + } + + isDiffAction(action) { + return action === 'diffs' || action === 'new/diffs'; + } + + expandViewContainer() { + const $wrapper = $('.content-wrapper .container-fluid'); + if (this.fixedLayoutPref === null) { + this.fixedLayoutPref = $wrapper.hasClass('container-limited'); + } + $wrapper.removeClass('container-limited'); + } + + resetViewContainer() { + if (this.fixedLayoutPref !== null) { + $('.content-wrapper .container-fluid') + .toggleClass('container-limited', this.fixedLayoutPref); + } + } + + shrinkView() { + const $gutterIcon = $('.js-sidebar-toggle i:visible'); + + // Wait until listeners are set + setTimeout(() => { + // Only when sidebar is expanded + if ($gutterIcon.is('.fa-angle-double-right')) { + $gutterIcon.closest('a').trigger('click', [true]); + } + }, 0); + } + + // Expand the issuable sidebar unless the user explicitly collapsed it + expandView() { + if (Cookies.get('collapsed_gutter') === 'true') { + return; + } + const $gutterIcon = $('.js-sidebar-toggle i:visible'); + + // Wait until listeners are set + setTimeout(() => { + // Only when sidebar is collapsed + if ($gutterIcon.is('.fa-angle-double-left')) { + $gutterIcon.closest('a').trigger('click', [true]); + } + }, 0); + } + + initAffix() { + const $tabs = $('.js-tabs-affix'); + + // Screen space on small screens is usually very sparse + // So we dont affix the tabs on these + if (Breakpoints.get().getBreakpointSize() === 'xs' || !$tabs.length) return; + + const $diffTabs = $('#diff-notes-app'); + const $fixedNav = $('.navbar-fixed-top'); + const $layoutNav = $('.layout-nav'); + + $tabs.off('affix.bs.affix affix-top.bs.affix') + .affix({ + offset: { + top: () => ( + $diffTabs.offset().top - $tabs.height() - $fixedNav.height() - $layoutNav.height() + ), + }, + }) + .on('affix.bs.affix', () => $diffTabs.css({ marginTop: $tabs.height() })) + .on('affix-top.bs.affix', () => $diffTabs.css({ marginTop: '' })); + + // Fix bug when reloading the page already scrolling + if ($tabs.hasClass('affix')) { + $tabs.trigger('affix.bs.affix'); + } + } + } + + window.gl = window.gl || {}; + window.gl.MergeRequestTabs = MergeRequestTabs; +})(); diff --git a/app/assets/javascripts/pipelines.js.es6 b/app/assets/javascripts/pipelines.js.es6 index a84db9c0233..72c6c4a1fcd 100644 --- a/app/assets/javascripts/pipelines.js.es6 +++ b/app/assets/javascripts/pipelines.js.es6 @@ -1,8 +1,15 @@ +//= require lib/utils/bootstrap_linked_tabs + /* eslint-disable */ ((global) => { class Pipelines { - constructor() { + constructor(options) { + + if (options.initTabs && options.tabsOptions) { + new global.LinkedTabs(options.tabsOptions); + } + this.addMarginToBuildColumns(); } diff --git a/app/assets/javascripts/single_file_diff.js b/app/assets/javascripts/single_file_diff.js index 2767849e673..0d48e69cce9 100644 --- a/app/assets/javascripts/single_file_diff.js +++ b/app/assets/javascripts/single_file_diff.js @@ -14,6 +14,7 @@ COLLAPSED_HTML = '<div class="nothing-here-block diff-collapsed">This diff is collapsed. <a class="click-to-expand">Click to expand it.</a></div>'; function SingleFileDiff(file, forceLoad, cb) { + var clickTarget; this.file = file; this.toggleDiff = bind(this.toggleDiff, this); this.content = $('.diff-content', this.file); @@ -31,9 +32,9 @@ this.content.after(this.collapsedContent); this.$toggleIcon.addClass('fa-caret-down'); } - $('.file-title, .click-to-expand', this.file).on('click', this.toggleDiff); + clickTarget = $('.file-title, .click-to-expand', this.file).on('click', this.toggleDiff); if (forceLoad) { - this.toggleDiff(null, cb); + this.toggleDiff({ target: clickTarget }, cb); } } diff --git a/app/assets/javascripts/subscription.js b/app/assets/javascripts/subscription.js deleted file mode 100644 index 6d75688deeb..00000000000 --- a/app/assets/javascripts/subscription.js +++ /dev/null @@ -1,52 +0,0 @@ -/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, vars-on-top, no-unused-vars, one-var, one-var-declaration-per-line, camelcase, consistent-return, no-undef, padded-blocks, max-len */ -(function() { - var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; - - this.Subscription = (function() { - function Subscription(container) { - this.toggleSubscription = bind(this.toggleSubscription, this); - var $container; - this.$container = $(container); - this.url = this.$container.attr('data-url'); - this.subscribe_button = this.$container.find('.js-subscribe-button'); - this.subscription_status = this.$container.find('.subscription-status'); - this.subscribe_button.unbind('click').click(this.toggleSubscription); - } - - Subscription.prototype.toggleSubscription = function(event) { - var action, btn, current_status; - btn = $(event.currentTarget); - action = btn.find('span').text(); - current_status = this.subscription_status.attr('data-status'); - btn.addClass('disabled'); - - if ($('html').hasClass('issue-boards-page')) { - this.url = this.$container.attr('data-url'); - } - - return $.post(this.url, (function(_this) { - return function() { - var status; - btn.removeClass('disabled'); - - if ($('html').hasClass('issue-boards-page')) { - Vue.set(gl.issueBoards.BoardsStore.detail.issue, 'subscribed', !gl.issueBoards.BoardsStore.detail.issue.subscribed); - } else { - status = current_status === 'subscribed' ? 'unsubscribed' : 'subscribed'; - _this.subscription_status.attr('data-status', status); - action = status === 'subscribed' ? 'Unsubscribe' : 'Subscribe'; - btn.find('span').text(action); - _this.subscription_status.find('>div').toggleClass('hidden'); - if (btn.attr('data-original-title')) { - return btn.tooltip('hide').attr('data-original-title', action).tooltip('fixTitle'); - } - } - }; - })(this)); - }; - - return Subscription; - - })(); - -}).call(this); diff --git a/app/assets/javascripts/subscription.js.es6 b/app/assets/javascripts/subscription.js.es6 new file mode 100644 index 00000000000..62d1604fe9e --- /dev/null +++ b/app/assets/javascripts/subscription.js.es6 @@ -0,0 +1,50 @@ +/* global Vue */ + +(() => { + class Subscription { + constructor(containerElm) { + this.containerElm = containerElm; + + const subscribeButton = containerElm.querySelector('.js-subscribe-button'); + if (subscribeButton) { + // remove class so we don't bind twice + subscribeButton.classList.remove('js-subscribe-button'); + subscribeButton.addEventListener('click', this.toggleSubscription.bind(this)); + } + } + + toggleSubscription(event) { + const button = event.currentTarget; + const buttonSpan = button.querySelector('span'); + if (!buttonSpan || button.classList.contains('disabled')) { + return; + } + button.classList.add('disabled'); + + const isSubscribed = buttonSpan.innerHTML.trim().toLowerCase() !== 'subscribe'; + const toggleActionUrl = this.containerElm.dataset.url; + + $.post(toggleActionUrl, () => { + button.classList.remove('disabled'); + + // hack to allow this to work with the issue boards Vue object + if (document.querySelector('html').classList.contains('issue-boards-page')) { + Vue.set( + gl.issueBoards.BoardsStore.detail.issue, + 'subscribed', + !gl.issueBoards.BoardsStore.detail.issue.subscribed, + ); + } else { + buttonSpan.innerHTML = isSubscribed ? 'Subscribe' : 'Unsubscribe'; + } + }); + } + + static bindAll(selector) { + [].forEach.call(document.querySelectorAll(selector), elm => new Subscription(elm)); + } + } + + window.gl = window.gl || {}; + window.gl.Subscription = Subscription; +})(); diff --git a/app/assets/javascripts/vue_common_component/commit.js.es6 b/app/assets/javascripts/vue_common_component/commit.js.es6 index 1bc68c1ba2f..2ef2959cbf4 100644 --- a/app/assets/javascripts/vue_common_component/commit.js.es6 +++ b/app/assets/javascripts/vue_common_component/commit.js.es6 @@ -23,7 +23,7 @@ * name * ref_url */ - ref: { + commit_ref: { type: Object, required: false, default: () => ({}), @@ -79,8 +79,8 @@ * * @returns {Boolean} */ - hasRef() { - return this.ref && this.ref.name && this.ref.ref_url; + hasCommitRef() { + return this.commit_ref && this.commit_ref.name && this.commit_ref.ref_url; }, /** @@ -131,15 +131,15 @@ template: ` <div class="branch-commit"> - <div v-if="hasRef" class="icon-container"> + <div v-if="hasCommitRef" class="icon-container"> <i v-if="tag" class="fa fa-tag"></i> <i v-if="!tag" class="fa fa-code-fork"></i> </div> - <a v-if="hasRef" + <a v-if="hasCommitRef" class="monospace branch-name" - :href="ref.ref_url"> - {{ref.name}} + :href="commit_ref.ref_url"> + {{commit_ref.name}} </a> <div class="icon-container commit-icon commit-icon-container"></div> diff --git a/app/assets/javascripts/wikis.js b/app/assets/javascripts/wikis.js deleted file mode 100644 index 5dd853389c2..00000000000 --- a/app/assets/javascripts/wikis.js +++ /dev/null @@ -1,38 +0,0 @@ -/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, consistent-return, one-var, one-var-declaration-per-line, no-undef, prefer-template, padded-blocks, max-len */ - -/*= require latinise */ - -(function() { - var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; - - this.Wikis = (function() { - function Wikis() { - this.slugify = bind(this.slugify, this); - $('.new-wiki-page').on('submit', (function(_this) { - return function(e) { - var field, path, slug; - $('[data-error~=slug]').addClass('hidden'); - field = $('#new_wiki_path'); - slug = _this.slugify(field.val()); - if (slug.length > 0) { - path = field.attr('data-wikis-path'); - location.href = path + '/' + slug; - return e.preventDefault(); - } - }; - })(this)); - } - - Wikis.prototype.dasherize = function(value) { - return value.replace(/[_\s]+/g, '-'); - }; - - Wikis.prototype.slugify = function(value) { - return this.dasherize(value.trim().toLowerCase().latinise()); - }; - - return Wikis; - - })(); - -}).call(this); diff --git a/app/assets/javascripts/wikis.js.es6 b/app/assets/javascripts/wikis.js.es6 new file mode 100644 index 00000000000..ecff5fd5bf4 --- /dev/null +++ b/app/assets/javascripts/wikis.js.es6 @@ -0,0 +1,73 @@ +/* eslint-disable no-param-reassign */ +/* global Breakpoints */ + +/*= require latinise */ +/*= require breakpoints */ +/*= require jquery.nicescroll */ + +((global) => { + const dasherize = str => str.replace(/[_\s]+/g, '-'); + const slugify = str => dasherize(str.trim().toLowerCase().latinise()); + + class Wikis { + constructor() { + this.bp = Breakpoints.get(); + this.sidebarEl = document.querySelector('.js-wiki-sidebar'); + this.sidebarExpanded = false; + $(this.sidebarEl).niceScroll(); + + const sidebarToggles = document.querySelectorAll('.js-sidebar-wiki-toggle'); + for (let i = 0; i < sidebarToggles.length; i += 1) { + sidebarToggles[i].addEventListener('click', e => this.handleToggleSidebar(e)); + } + + this.newWikiForm = document.querySelector('form.new-wiki-page'); + if (this.newWikiForm) { + this.newWikiForm.addEventListener('submit', e => this.handleNewWikiSubmit(e)); + } + + window.addEventListener('resize', () => this.renderSidebar()); + this.renderSidebar(); + } + + handleNewWikiSubmit(e) { + if (!this.newWikiForm) return; + + const slugInput = this.newWikiForm.querySelector('#new_wiki_path'); + const slug = slugify(slugInput.value); + + if (slug.length > 0) { + const wikisPath = slugInput.getAttribute('data-wikis-path'); + window.location.href = `${wikisPath}/${slug}`; + e.preventDefault(); + } + } + + handleToggleSidebar(e) { + e.preventDefault(); + this.sidebarExpanded = !this.sidebarExpanded; + this.renderSidebar(); + } + + sidebarCanCollapse() { + const bootstrapBreakpoint = this.bp.getBreakpointSize(); + return bootstrapBreakpoint === 'xs' || bootstrapBreakpoint === 'sm'; + } + + renderSidebar() { + if (!this.sidebarEl) return; + const { classList } = this.sidebarEl; + if (this.sidebarExpanded || !this.sidebarCanCollapse()) { + if (!classList.contains('right-sidebar-expanded')) { + classList.remove('right-sidebar-collapsed'); + classList.add('right-sidebar-expanded'); + } + } else if (classList.contains('right-sidebar-expanded')) { + classList.add('right-sidebar-collapsed'); + classList.remove('right-sidebar-expanded'); + } + } + } + + global.Wikis = Wikis; +})(window.gl || (window.gl = {})); diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss index 7c7f991dd87..4aaff7d04f1 100644 --- a/app/assets/stylesheets/framework.scss +++ b/app/assets/stylesheets/framework.scss @@ -1,4 +1,3 @@ -@import "framework/fonts"; @import "framework/variables"; @import "framework/mixins"; @import 'framework/tw_bootstrap_variables'; diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss index c0dd1cb3667..000e591e09c 100644 --- a/app/assets/stylesheets/framework/avatar.scss +++ b/app/assets/stylesheets/framework/avatar.scss @@ -8,7 +8,7 @@ float: left; margin-right: 15px; border-radius: $avatar_radius; - border: 1px solid rgba(0, 0, 0, .1); + border: 1px solid $avatar-border; &.s16 { @include avatar-size(16px, 6px); } &.s20 { @include avatar-size(20px, 7px); } &.s24 { @include avatar-size(24px, 8px); } diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss index 77ae9e9a6e7..57db5eaa2b3 100644 --- a/app/assets/stylesheets/framework/blocks.scss +++ b/app/assets/stylesheets/framework/blocks.scss @@ -41,7 +41,7 @@ } &.white { - background-color: white; + background-color: $white-light; } &.top-block { @@ -158,7 +158,7 @@ p { padding: 0 $gl-padding; - color: #5c5d5e; + color: $gl-text-color-dark; } } diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index 36f530af685..8da3da2ad08 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -68,23 +68,23 @@ } @mixin btn-green { - @include btn-color($green-light, $border-green-light, $green-normal, $border-green-normal, $green-dark, $border-green-dark, #fff); + @include btn-color($green-light, $border-green-light, $green-normal, $border-green-normal, $green-dark, $border-green-dark, $white-light); } @mixin btn-blue { - @include btn-color($blue-light, $border-blue-light, $blue-normal, $border-blue-normal, $blue-dark, $border-blue-dark, #fff); + @include btn-color($blue-light, $border-blue-light, $blue-normal, $border-blue-normal, $blue-dark, $border-blue-dark, $white-light); } @mixin btn-blue-medium { - @include btn-color($blue-medium-light, $border-blue-light, $blue-medium, $border-blue-normal, $blue-medium-dark, $border-blue-dark, #fff); + @include btn-color($blue-medium-light, $border-blue-light, $blue-medium, $border-blue-normal, $blue-medium-dark, $border-blue-dark, $white-light); } @mixin btn-orange { - @include btn-color($orange-light, $border-orange-light, $orange-normal, $border-orange-normal, $orange-dark, $border-orange-dark, #fff); + @include btn-color($orange-light, $border-orange-light, $orange-normal, $border-orange-normal, $orange-dark, $border-orange-dark, $white-light); } @mixin btn-red { - @include btn-color($red-light, $border-red-light, $red-normal, $border-red-normal, $red-dark, $border-red-dark, #fff); + @include btn-color($red-light, $border-red-light, $red-normal, $border-red-normal, $red-dark, $border-red-dark, $white-light); } @mixin btn-gray { @@ -289,8 +289,8 @@ .active { box-shadow: $gl-btn-active-background; - border: 1px solid #c6cacf !important; - background-color: #e4e7ed !important; + border: 1px solid $border-white-dark !important; + background-color: $btn-active-gray-light !important; } } @@ -345,13 +345,13 @@ .btn-static { background-color: $background-color !important; - border: 1px solid lightgrey; + border: 1px solid $border-gray-light; cursor: default; &:active { - -moz-box-shadow: inset 0 0 0 white; - -webkit-box-shadow: inset 0 0 0 white; - box-shadow: inset 0 0 0 white; + -moz-box-shadow: inset 0 0 0 $white-light; + -webkit-box-shadow: inset 0 0 0 $white-light; + box-shadow: inset 0 0 0 $white-light; } } diff --git a/app/assets/stylesheets/framework/calendar.scss b/app/assets/stylesheets/framework/calendar.scss index 8642b7530e2..ef921a8c6a9 100644 --- a/app/assets/stylesheets/framework/calendar.scss +++ b/app/assets/stylesheets/framework/calendar.scss @@ -2,7 +2,7 @@ padding-left: 0; padding-right: 0; - @media (min-width: $screen-sm-min) and (max-width: $screen-lg-min) { + @media (min-width: $screen-sm-min) and (max-width: $screen-md-max) { overflow-x: scroll; } } @@ -28,13 +28,13 @@ .user-contrib-cell { &:hover { cursor: pointer; - stroke: #000; + stroke: $black; } } .user-contrib-text { font-size: 12px; - fill: #959494; + fill: $calendar-user-contrib-text; } .calendar-hint { diff --git a/app/assets/stylesheets/framework/callout.scss b/app/assets/stylesheets/framework/callout.scss index f3b6ad88ad6..2a100980aca 100644 --- a/app/assets/stylesheets/framework/callout.scss +++ b/app/assets/stylesheets/framework/callout.scss @@ -25,25 +25,25 @@ /* Variations */ .bs-callout-danger { - background-color: #fdf7f7; - border-color: #eed3d7; - color: #b94a48; + background-color: $callout-danger-bg; + border-color: $callout-danger-border; + color: $callout-danger-color; } .bs-callout-warning { - background-color: #faf8f0; - border-color: #faebcc; - color: #8a6d3b; + background-color: $callout-warning-bg; + border-color: $callout-warning-border; + color: $callout-warning-color; } .bs-callout-info { - background-color: #f4f8fa; - border-color: #bce8f1; - color: #34789a; + background-color: $callout-info-bg; + border-color: $callout-info-border; + color: $callout-info-color; } .bs-callout-success { - background-color: #dff0d8; - border-color: #5ca64d; - color: #3c763d; + background-color: $callout-success-bg; + border-color: $callout-success-border; + color: $callout-success-color; } diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index b24fce6f0c2..cdeef6fcc9e 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -1,9 +1,9 @@ /** COLORS **/ -.cgray { color: $gl-gray; } -.clgray { color: #bbb; } -.cred { color: $gl-text-red; } -.cgreen { color: $gl-text-green; } -.cdark { color: #444; } +.cgray { color: $common-gray; } +.clgray { color: $common-gray-light; } +.cred { color: $common-red; } +.cgreen { color: $common-green; } +.cdark { color: $common-gray-dark; } /** COMMON CLASSES **/ .prepend-top-0 { margin-top: 0; } @@ -28,11 +28,11 @@ .center { text-align: center; } .underlined-link { text-decoration: underline; } -.hint { font-style: italic; color: #999; } -.light { color: $gl-gray; } +.hint { font-style: italic; color: $hint-color; } +.light { color: $common-gray; } .slead { - color: $gl-gray; + color: $common-gray; font-size: 15px; margin-bottom: 12px; font-weight: normal; @@ -52,10 +52,10 @@ pre { } &.well-pre { - border: 1px solid #eee; + border: 1px solid $well-pre-bg; background: $gray-light; border-radius: 0; - color: #555; + color: $well-pre-color; } } @@ -87,14 +87,14 @@ table a code { .loading { margin: 20px auto; height: 40px; - color: #555; + color: $loading-color; font-size: 32px; text-align: center; } span.update-author { display: block; - color: #999; + color: $update-author-color; font-weight: normal; font-style: italic; @@ -105,7 +105,7 @@ span.update-author { } .user-mention { - color: #2fa0bb; + color: $user-mention-color; font-weight: bold; } @@ -114,7 +114,7 @@ span.update-author { } p.time { - color: #999; + color: $time-color; font-size: 90%; margin: 30px 3px 3px 2px; } @@ -150,7 +150,7 @@ li.note { .project_member_show { td:first-child { - color: #aaa; + color: $project-member-show-color; } } @@ -176,7 +176,7 @@ li.note { margin-top: 40px; pre { - background: white; + background: $white-light; border: none; font-size: 12px; } @@ -184,12 +184,12 @@ li.note { .error-message { padding: 10px; - background: #c67; + background: $error-bg; margin: 0; - color: #fff; + color: $white-light; a { - color: #fff; + color: $white-light; text-decoration: underline; } } @@ -197,22 +197,22 @@ li.note { .browser-alert { padding: 10px; text-align: center; - background: #c67; - color: #fff; + background: $error-bg; + color: $white-light; font-weight: bold; a { - color: #fff; + color: $white-light; text-decoration: underline; } } .warning_message { - border-left: 4px solid #ed9; - color: #b90; + border-left: 4px solid $warning-message-border; + color: $warning-message-color; padding: 10px; margin-bottom: 10px; - background: #ffffe6; + background: $warning-message-bg; padding-left: 20px; &.centered { @@ -222,7 +222,7 @@ li.note { .gitlab-promo { a { - color: #aaa; + color: $gl-promo-color; margin-right: 30px; } } @@ -245,7 +245,7 @@ li.note { position: relative; top: 2px; left: 5px; - color: #666; + color: $control-group-descr-color; } } } @@ -270,7 +270,7 @@ img.emoji { table { td.permission-x { - background: #d9edf7 !important; + background: $table-permission-x-bg !important; text-align: center; } } @@ -323,13 +323,13 @@ table { .username { font-size: 18px; - color: #666; + color: $username-color; margin-top: 8px; } .description { font-size: $gl-font-size; - color: #666; + color: $description-color; margin-top: 8px; } } @@ -339,7 +339,7 @@ table { .profiler-button, .profiler-controls { - border-color: #eee !important; + border-color: $profiler-border !important; } } diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 6d77aadd753..e6229a35b88 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -376,7 +376,7 @@ position: absolute; top: 10px; right: 20px; - color: #c7c7c7; + color: $dropdown-input-fa-color; font-size: 12px; pointer-events: none; } @@ -529,7 +529,7 @@ .ui-datepicker-calendar { .ui-state-hover, .ui-state-active { - color: #fff; + color: $white-light; border: 0; } } diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index f49d7b92a00..ab0b81f77f7 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -59,10 +59,10 @@ } .file-content { - background: #fff; + background: $white-light; &.image_file { - background: #eee; + background: $file-image-bg; text-align: center; img { @@ -84,8 +84,8 @@ } &.blob-no-preview { - background: #eee; - text-shadow: 0 1px 2px #fff; + background: $blob-bg; + text-shadow: 0 1px 2px $white-light; padding: 100px 0; } @@ -99,7 +99,7 @@ } tr { - border-bottom: 1px solid #eee; + border-bottom: 1px solid $blame-border; } td { @@ -120,7 +120,7 @@ td.line-numbers { float: none; - border-left: 1px solid #ddd; + border-left: 1px solid $blame-line-numbers-border; i { float: none; @@ -134,7 +134,7 @@ } &.logs { - background: #eee; + background: $logs-bg; max-height: 700px; overflow-y: auto; @@ -143,14 +143,14 @@ padding: 10px 0; border-left: 1px solid $border-color; margin-bottom: 0; - background: white; + background: $white-light; li { - color: #888; + color: $logs-li-color; p { margin: 0; - color: #333; + color: $logs-p-color; line-height: 24px; padding-left: 10px; } diff --git a/app/assets/stylesheets/framework/flash.scss b/app/assets/stylesheets/framework/flash.scss index a9006de6d3e..eadb9409fee 100644 --- a/app/assets/stylesheets/framework/flash.scss +++ b/app/assets/stylesheets/framework/flash.scss @@ -38,7 +38,7 @@ } } -@media (max-width: $screen-md-min) { +@media (max-width: $screen-sm-max) { ul.notes { .flash-container.timeline-content { margin-left: 0; diff --git a/app/assets/stylesheets/framework/fonts.scss b/app/assets/stylesheets/framework/fonts.scss deleted file mode 100644 index 5f9685bc71a..00000000000 --- a/app/assets/stylesheets/framework/fonts.scss +++ /dev/null @@ -1,45 +0,0 @@ -// Disabling "SpaceAfterPropertyColon" linter because the linter doesn't like -// the way the `src` property is formatted in this file. -// scss-lint:disable SpaceAfterPropertyColon - -/* latin-ext */ -@font-face { - font-family: 'Source Sans Pro'; - font-style: normal; - font-weight: 300; - src: - local('Source Sans Pro Light'), - local('SourceSansPro-Light'), - font-url('SourceSansPro-Light.ttf.woff2') format('woff2'), - font-url('SourceSansPro-Light.ttf.woff') format('woff'); -} -@font-face { - font-family: 'Source Sans Pro'; - font-style: normal; - font-weight: 400; - src: - local('Source Sans Pro'), - local('SourceSansPro-Regular'), - font-url('SourceSansPro-Regular.ttf.woff2') format('woff2'), - font-url('SourceSansPro-Regular.ttf.woff') format('woff'); -} -@font-face { - font-family: 'Source Sans Pro'; - font-style: normal; - font-weight: 600; - src: - local('Source Sans Pro Semibold'), - local('SourceSansPro-Semibold'), - font-url('SourceSansPro-Semibold.ttf.woff2') format('woff2'), - font-url('SourceSansPro-Semibold.ttf.woff') format('woff'); -} -@font-face { - font-family: 'Source Sans Pro'; - font-style: normal; - font-weight: 700; - src: - local('Source Sans Pro Bold'), - local('SourceSansPro-Bold'), - font-url('SourceSansPro-Bold.ttf.woff2') format('woff2'), - font-url('SourceSansPro-Bold.ttf.woff') format('woff'); -} diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss index e83a1f7ad68..25a2b38baaa 100644 --- a/app/assets/stylesheets/framework/forms.scss +++ b/app/assets/stylesheets/framework/forms.scss @@ -7,9 +7,9 @@ input { } input[type='text'].danger { - background: #f2dede!important; - border-color: #d66; - text-shadow: 0 1px 1px #fff; + background: $input-danger-bg !important; + border-color: $input-danger-border; + text-shadow: 0 1px 1px $white-light; } .datetime-controls { @@ -98,7 +98,7 @@ label { } } - @media(max-width: $screen-sm-min) { + @media(max-width: $screen-xs-max) { padding: 0 $gl-padding; .control-label, @@ -159,7 +159,7 @@ label { } .input-group-addon { - background-color: #f7f8fa; + background-color: $input-group-addon-bg; } .input-group-addon:not(:first-child):not(:last-child) { @@ -181,7 +181,7 @@ label { border: 1px solid $green-normal; &:focus { - box-shadow: 0 0 0 1px $green-normal inset, 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 4px 0 $green-normal; + box-shadow: 0 0 0 1px $green-normal inset, 0 1px 1px $gl-field-focus-shadow inset, 0 0 4px 0 $green-normal; border: 0 none; } } @@ -190,7 +190,7 @@ label { border: 1px solid $red-normal; &:focus { - box-shadow: 0 0 0 1px $red-normal inset, 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 4px 0 rgba(210, 40, 82, 0.6); + box-shadow: 0 0 0 1px $red-normal inset, 0 1px 1px $gl-field-focus-shadow inset, 0 0 4px 0 $gl-field-focus-shadow-error; border: 0 none; } } diff --git a/app/assets/stylesheets/framework/gitlab-theme.scss b/app/assets/stylesheets/framework/gitlab-theme.scss index 91ab1503439..5cd242af91d 100644 --- a/app/assets/stylesheets/framework/gitlab-theme.scss +++ b/app/assets/stylesheets/framework/gitlab-theme.scss @@ -21,7 +21,6 @@ background: $color-darker; } - .sidebar-header, .sidebar-action-buttons { color: $color-light; background-color: lighten($color-darker, 5%); @@ -86,37 +85,57 @@ } $theme-charcoal: #3d454d; +$theme-charcoal-light: #485157; $theme-charcoal-dark: #383f45; $theme-charcoal-text: #b9bbbe; +$theme-blue-light: #becde9; $theme-blue: #2980b9; +$theme-blue-dark: #1970a9; +$theme-blue-darker: #096099; + +$theme-graphite-lighter: #ccc; +$theme-graphite-light: #777; $theme-graphite: #666; +$theme-graphite-dark: #555; + +$theme-gray-light: #979797; $theme-gray: #373737; +$theme-gray-dark: #272727; +$theme-gray-darker: #222; + +$theme-green-light: #adc; $theme-green: #019875; +$theme-green-dark: #018865; +$theme-green-darker: #017855; + +$theme-violet-light: #98c; $theme-violet: #548; +$theme-violet-dark: #436; +$theme-violet-darker: #325; body { &.ui_blue { - @include gitlab-theme(#becde9, $theme-blue, #1970a9, #096099); + @include gitlab-theme($theme-blue-light, $theme-blue, $theme-blue-dark, $theme-blue-darker); } &.ui_charcoal { - @include gitlab-theme($theme-charcoal-text, #485157, $theme-charcoal, $theme-charcoal-dark); + @include gitlab-theme($theme-charcoal-text, $theme-charcoal-light, $theme-charcoal, $theme-charcoal-dark); } &.ui_graphite { - @include gitlab-theme(#ccc, #777, $theme-graphite, #555); + @include gitlab-theme($theme-graphite-lighter, $theme-graphite-light, $theme-graphite, $theme-graphite-dark); } &.ui_gray { - @include gitlab-theme(#979797, $theme-gray, #272727, #222); + @include gitlab-theme($theme-gray-light, $theme-gray, $theme-gray-dark, $theme-gray-darker); } &.ui_green { - @include gitlab-theme(#adc, $theme-green, #018865, #017855); + @include gitlab-theme($theme-green-light, $theme-green, $theme-green-dark, $theme-green-darker); } &.ui_violet { - @include gitlab-theme(#98c, $theme-violet, #436, #325); + @include gitlab-theme($theme-violet-light, $theme-violet, $theme-violet-dark, $theme-violet-darker); } } diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 16ecf466931..e40ff4d4688 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -8,7 +8,7 @@ header { &.navbar-empty { height: $header-height; - background: #fff; + background: $white-light; border-bottom: 1px solid $btn-gray-hover; .center-logo { @@ -76,7 +76,7 @@ header { } .navbar-toggle { - color: #666; + color: $nav-toggle-gray; margin: 6px 0; border-radius: 0; position: absolute; @@ -228,7 +228,7 @@ header { } .page-sidebar-pinned.right-sidebar-expanded { - @media (max-width: $screen-lg-min) { + @media (max-width: $screen-md-max) { .header-content .title { width: 300px; } diff --git a/app/assets/stylesheets/framework/issue_box.scss b/app/assets/stylesheets/framework/issue_box.scss index ff6f316d576..44834a84234 100644 --- a/app/assets/stylesheets/framework/issue_box.scss +++ b/app/assets/stylesheets/framework/issue_box.scss @@ -20,7 +20,7 @@ display: block; float: left; margin-right: 10px; - color: #fff; + color: $white-light; font-size: $gl-font-size; line-height: 25px; @@ -37,10 +37,10 @@ } &.status-box-expired { - background: #cea61b; + background-color: $issue-status-expired; } &.status-box-upcoming { - background: #8f8f8f; + background: $issue-box-upcoming-bg; } } diff --git a/app/assets/stylesheets/framework/jquery.scss b/app/assets/stylesheets/framework/jquery.scss index 30a5b837d69..18f2f316f02 100644 --- a/app/assets/stylesheets/framework/jquery.scss +++ b/app/assets/stylesheets/framework/jquery.scss @@ -4,13 +4,13 @@ &.ui-datepicker, &.ui-datepicker-inline { - border: 1px solid #ddd; + border: 1px solid $jq-ui-border; padding: 10px; width: 270px; .ui-datepicker-header { - background: #fff; - border-color: #ddd; + background: $white-light; + border-color: $jq-ui-border; .ui-datepicker-prev, .ui-datepicker-next { @@ -39,7 +39,7 @@ } &.ui-autocomplete { - border-color: #ddd; + border-color: $jq-ui-border; padding: 0; margin-top: 2px; z-index: 1001; @@ -50,9 +50,9 @@ } .ui-state-default { - border: 1px solid #fff; - background: #fff; - color: #777; + border: 1px solid $white-light; + background: $white-light; + color: $jq-ui-default-color; } .ui-state-highlight { @@ -66,7 +66,7 @@ .ui-state-focus { border: 1px solid $gl-primary; background: $gl-primary; - color: #fff; + color: $white-light; } } } diff --git a/app/assets/stylesheets/framework/layout.scss b/app/assets/stylesheets/framework/layout.scss index 7baa4296abf..dfaf2f7f1d3 100644 --- a/app/assets/stylesheets/framework/layout.scss +++ b/app/assets/stylesheets/framework/layout.scss @@ -6,7 +6,7 @@ html { body { &.navless { - background-color: white !important; + background-color: $white-light !important; } } diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index bc0610cc417..db8677433bb 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -11,8 +11,8 @@ > li { padding: 10px 15px; min-height: 20px; - border-bottom: 1px solid #eee; - border-bottom: 1px solid rgba(0, 0, 0, 0.05); + border-bottom: 1px solid $list-border-light; + border-bottom: 1px solid $list-border; &::after { content: " "; @@ -21,7 +21,7 @@ } &.disabled { - color: #888; + color: $list-text-disabled-color; } &.unstyled { @@ -31,9 +31,9 @@ } &.warning-row { - background-color: #fcf8e3; - border-color: #faebcc; - color: #8a6d3b; + background-color: $list-warning-row-bg; + border-color: $list-warning-row-border; + color: $list-warning-row-color; } &.smoke { background-color: $background-color; } diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss index 42087c91530..59a30d31ac7 100644 --- a/app/assets/stylesheets/framework/markdown_area.scss +++ b/app/assets/stylesheets/framework/markdown_area.scss @@ -56,16 +56,9 @@ .md-header { .nav-links { - .active { - a { - border-bottom-color: #000; - } - } - a { padding-top: 0; line-height: 19px; - border-bottom: 1px solid $border-color; &.btn.btn-xs { padding: 2px 5px; @@ -80,7 +73,7 @@ } .referenced-users { - color: #4c4e54; + color: $gl-header-color; padding-top: 10px; } @@ -92,8 +85,8 @@ .markdown-area { border-radius: 0; - background: #fff; - border: 1px solid #ddd; + background: $white-light; + border: 1px solid $md-area-border; min-height: 140px; max-height: 500px; padding: 5px; @@ -115,7 +108,7 @@ hr { // Darken 'whitesmoke' a bit to make it more visible in note bodies - border-color: darken(#f5f5f5, 8%); + border-color: darken($gray-normal, 8%); margin: 10px 0; } @@ -142,7 +135,7 @@ .toolbar-btn { float: left; padding: 0 5px; - color: #959494; + color: $note-toolbar-color; background: transparent; border: 0; outline: 0; diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss index f84ca36d10f..4f2ac77f228 100644 --- a/app/assets/stylesheets/framework/mixins.scss +++ b/app/assets/stylesheets/framework/mixins.scss @@ -24,7 +24,7 @@ @include clearfix; padding: 10px 0; - border-bottom: 1px solid #eee; + border-bottom: 1px solid $list-border-light; display: block; margin: 0; @@ -67,8 +67,8 @@ } @mixin dark-diff-match-line { - color: rgba(255, 255, 255, 0.3); - background: rgba(255, 255, 255, 0.1); + color: $dark-diff-match-bg; + background: $dark-diff-match-color; } @mixin webkit-prefix($property, $value) { diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss index 9391661a595..abfdd7a759d 100644 --- a/app/assets/stylesheets/framework/mobile.scss +++ b/app/assets/stylesheets/framework/mobile.scss @@ -133,9 +133,9 @@ right: 0; top: 30%; padding: 5px 15px; - background: #eee; + background: $show-aside-bg; font-size: 20px; - color: #777; + color: $show-aside-color; z-index: 100; - box-shadow: 0 1px 2px #ddd; + box-shadow: 0 1px 2px $show-aside-shadow; } diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index 1839ffa0976..d2d3fc23b6c 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -75,7 +75,7 @@ .badge { font-weight: normal; - background-color: #eee; + background-color: $nav-badge-bg; color: $btn-transparent-color; vertical-align: baseline; } @@ -123,7 +123,7 @@ line-height: 28px; /* Small devices (phones, tablets, 768px and lower) */ - @media (max-width: $screen-sm-min) { + @media (max-width: $screen-xs-max) { width: 100%; } } diff --git a/app/assets/stylesheets/framework/pagination.scss b/app/assets/stylesheets/framework/pagination.scss index cb2c351c368..b37c1d0d670 100644 --- a/app/assets/stylesheets/framework/pagination.scss +++ b/app/assets/stylesheets/framework/pagination.scss @@ -43,7 +43,7 @@ /** * Small screen pagination */ -@media (max-width: $screen-xs) { +@media (max-width: $screen-xs-min) { .gl-pagination { .pagination li a { padding: 6px 10px; @@ -62,7 +62,7 @@ /** * Medium screen pagination */ -@media (min-width: $screen-xs) and (max-width: $screen-md-max) { +@media (min-width: $screen-xs-min) and (max-width: $screen-md-max) { .gl-pagination { .page { display: none; diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss index 920ce249b9a..fde1431b13e 100644 --- a/app/assets/stylesheets/framework/selects.scss +++ b/app/assets/stylesheets/framework/selects.scss @@ -6,7 +6,7 @@ .select2-container, .select2-container.select2-drop-above { .select2-choice { - background: #fff; + background: $white-light; border-color: $input-border; height: 35px; padding: $gl-vert-padding $gl-input-padding; @@ -47,7 +47,7 @@ } .select2-drop { - box-shadow: rgba(76, 86, 103, 0.247059) 0 0 1px 0, rgba(31, 37, 50, 0.317647) 0 2px 18px 0; + box-shadow: $select2-drop-shadow1 0 0 1px 0, $select2-drop-shadow2 0 2px 18px 0; border-radius: $border-radius-default; border: none; min-width: 175px; @@ -59,7 +59,7 @@ } .select2-drop { - color: #7f8fa4; + color: $gl-grayish-blue; } .select2-highlighted { @@ -156,7 +156,7 @@ .select2-search input { padding: 2px 25px 2px 5px; - background: #fff image-url('select2.png'); + background: $white-light image-url('select2.png'); background-repeat: no-repeat; background-position: right 0 bottom 6px; border: 1px solid $input-border; @@ -169,7 +169,7 @@ } .select2-search input.select2-active { - background-color: #fff; + background-color: $white-light; background-image: image-url('select2-spinner.gif') !important; background-repeat: no-repeat; background-position: right 5px center !important; @@ -206,7 +206,7 @@ .select2-highlighted { .group-result { .group-path { - color: #fff; + color: $white-light; } } } @@ -221,7 +221,7 @@ } .group-path { - color: #999; + color: $group-path-color; } } @@ -241,7 +241,7 @@ .namespace-result { .namespace-kind { - color: #aaa; + color: $namespace-kind-color; font-weight: normal; } diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index 44c445c0543..0aa609b8dd5 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -36,7 +36,7 @@ transition: padding $sidebar-transition-duration; .container-fluid { - background: #fff; + background: $white-light; padding: 0 $gl-padding; &.container-blank { @@ -59,11 +59,6 @@ padding: 0 !important; } - .sidebar-header { - padding: 11px 22px 12px; - font-size: 20px; - } - li { &.separate-item { padding-top: 10px; @@ -220,7 +215,7 @@ header.header-sidebar-pinned { padding-right: 0; @media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) { - &:not(.build-sidebar) { + &:not(.build-sidebar):not(.wiki-sidebar) { padding-right: $sidebar_collapsed_width; } } diff --git a/app/assets/stylesheets/framework/tables.scss b/app/assets/stylesheets/framework/tables.scss index 9a90d3794fd..a5f36c177fc 100644 --- a/app/assets/stylesheets/framework/tables.scss +++ b/app/assets/stylesheets/framework/tables.scss @@ -14,11 +14,11 @@ table { .warning, .danger, .info { - color: #fff; + color: $white-light; a:not(.btn) { text-decoration: underline; - color: #fff; + color: $white-light; } } diff --git a/app/assets/stylesheets/framework/tw_bootstrap.scss b/app/assets/stylesheets/framework/tw_bootstrap.scss index 59f4594bb83..55bc325b858 100644 --- a/app/assets/stylesheets/framework/tw_bootstrap.scss +++ b/app/assets/stylesheets/framework/tw_bootstrap.scss @@ -97,13 +97,13 @@ display: inline-block; &.label-gray { - background-color: #f8fafc; + background-color: $label-gray-bg; color: $gl-gray; text-shadow: none; } &.label-inverse { - background-color: #333; + background-color: $label-inverse-bg; } } @@ -158,7 +158,7 @@ font-weight: normal; a { - color: #777; + color: $panel-heading-link-color; } } } @@ -172,7 +172,7 @@ .alert { a:not(.btn) { @extend .alert-link; - color: #fff; + color: $white-light; text-decoration: underline; } } diff --git a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss index 44fe37d3a4a..c731a8f222f 100644 --- a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss +++ b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss @@ -48,7 +48,7 @@ $font-size-base: $gl-font-size; $padding-base-vertical: $gl-vert-padding; $padding-base-horizontal: $gl-padding; -$component-active-color: #fff; +$component-active-color: $white-light; $component-active-bg: $brand-info; //== Forms @@ -66,7 +66,7 @@ $legend-color: $text-color; //## $pagination-color: $gl-gray; -$pagination-bg: #fff; +$pagination-bg: $white-light; $pagination-border: $border-color; $pagination-hover-color: $gl-gray; @@ -74,7 +74,7 @@ $pagination-hover-bg: $row-hover; $pagination-hover-border: $border-color; $pagination-active-color: $blue-dark; -$pagination-active-bg: #fff; +$pagination-active-bg: $white-light; $pagination-active-border: $border-color; $pagination-disabled-color: #cdcdcd; @@ -86,19 +86,19 @@ $pagination-disabled-border: $border-color; // //## Define colors for form feedback states and, by default, alerts. -$state-success-text: #fff; +$state-success-text: $white-light; $state-success-bg: $brand-success; $state-success-border: $brand-success; -$state-info-text: #fff; +$state-info-text: $white-light; $state-info-bg: $brand-info; $state-info-border: $brand-info; -$state-warning-text: #fff; +$state-warning-text: $white-light; $state-warning-bg: $brand-warning; $state-warning-border: $brand-warning; -$state-danger-text: #fff; +$state-danger-text: $white-light; $state-danger-bg: $brand-danger; $state-danger-border: $brand-danger; @@ -135,14 +135,14 @@ $well-border: #eee; $code-color: #c7254e; $code-bg: #f9f2f4; -$kbd-color: #fff; +$kbd-color: $white-light; $kbd-bg: #333; //== Buttons // //## $btn-default-color: $gl-text-color; -$btn-default-bg: #fff; +$btn-default-bg: $white-light; $btn-default-border: #e7e9ed; //== Nav diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index 070e42d63d2..d906d26bba9 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -33,15 +33,15 @@ padding: 3px 5px; font-size: 11px; line-height: 10px; - color: #555; + color: $kdb-color; vertical-align: middle; - background-color: #fcfcfc; + background-color: $kdb-bg; border-width: 1px; border-style: solid; - border-color: #ccc #ccc #bbb; + border-color: $kdb-border $kdb-border $kdb-border-bottom; border-image: none; border-radius: 3px; - box-shadow: 0 -1px 0 #bbb inset; + box-shadow: 0 -1px 0 $kdb-shadow inset; } h1 { @@ -81,7 +81,7 @@ } blockquote { - color: #7f8fa4; + color: $gl-grayish-blue; font-size: inherit; padding: 8px 21px; margin: 12px 0; @@ -94,13 +94,13 @@ } blockquote p { - color: #7f8fa4 !important; + color: $gl-grayish-blue !important; font-size: inherit; line-height: 1.5; } p { - color: #5c5d5e; + color: $gl-text-color-dark; margin: 6px 0 0; } @@ -108,10 +108,10 @@ @extend .table; @extend .table-bordered; margin: 12px 0; - color: #5c5d5e; + color: $gl-text-color-dark; th { - background: #f8fafc; + background: $label-gray-bg; } } @@ -182,6 +182,7 @@ left: -16px; position: absolute; text-decoration: none; + outline: none; &::after { content: image-url('icon_anchor.svg'); @@ -201,7 +202,7 @@ * */ body { - -webkit-text-shadow: rgba(255,255,255,0.01) 0 0 1px; + -webkit-text-shadow: $body-text-shadow 0 0 1px; } .page-title { diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 8a9c279d124..edfb2c33f4a 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -96,6 +96,8 @@ $dark-background-color: #f5f5f5; $table-text-gray: #8f8f8f; $well-expand-item: #e8f2f7; $well-inner-border: #eef0f2; +$well-light-border: #f1f1f1; +$well-light-text-color: #5b6169; /* * Text @@ -103,11 +105,13 @@ $well-inner-border: #eef0f2; $gl-font-size: 15px; $gl-title-color: #333; $gl-text-color: #5c5c5c; +$gl-text-color-dark: #5c5d5e; $gl-text-color-light: #8c8c8c; $gl-text-green: #4a2; $gl-text-red: #d12f19; $gl-text-orange: #d90; $gl-link-color: #3777b0; +$gl-diff-text-color: #555; $gl-dark-link-color: #333; $gl-placeholder-color: #8f8f8f; $gl-icon-color: $gl-placeholder-color; @@ -123,13 +127,20 @@ $gl-header-color: #4c4e54; $list-font-size: $gl-font-size; $list-title-color: $gl-title-color; $list-text-color: $gl-text-color; +$list-text-disabled-color: #888; +$list-border-light: #eee; +$list-border: rgba(0, 0, 0, 0.05); $list-text-height: 42px; +$list-warning-row-bg: #fcf8e3; +$list-warning-row-border: #faebcc; +$list-warning-row-color: #8a6d3b; /* * Markdown */ $md-text-color: $gl-text-color; $md-link-color: $gl-link-color; +$md-area-border: #ddd; /* * Code @@ -153,10 +164,8 @@ $gl-sidebar-padding: 22px; $row-hover: #f7faff; $row-hover-border: #b2d7ff; $progress-color: #c0392b; -$avatar_radius: 50%; $header-height: 50px; $fixed-layout-width: 1280px; -$gl-avatar-size: 40px; $error-exclamation-point: #e62958; $border-radius-default: 2px; $btn-transparent-color: #8f8f8f; @@ -166,10 +175,47 @@ $provider-btn-not-active-color: #4688f1; $link-underline-blue: #4a8bee; $active-item-blue: #4a8bee; $layout-link-gray: #7e7c7c; -$todo-alert-blue: #428bca; $btn-side-margin: 10px; $btn-sm-side-margin: 7px; $btn-xs-side-margin: 5px; +$issue-status-expired: #cea61b; +$issuable-sidebar-color: #999; +$issuable-avatar-hover-border: #999; +$issuable-clipboard-color: #999; +$show-aside-bg: #eee; +$show-aside-color: #777; +$show-aside-shadow: #ddd; +$group-path-color: #999; +$namespace-kind-color: #aaa; +$panel-heading-link-color: #777; +$graph-author-email-color: #777; +$count-arrow-border: #dce0e5; +$save-project-loader-color: #555; +$divergence-graph-bar-bg: #ccc; +$divergence-graph-separator-bg: #ccc; +$issue-box-upcoming-bg: #8f8f8f; + +/* +* Common component specific colors +*/ +$hint-color: #999; +$well-pre-bg: #eee; +$well-pre-color: #555; +$loading-color: #555; +$update-author-color: #999; +$user-mention-color: #2fa0bb; +$time-color: #999; +$project-member-show-color: #aaa; +$gl-promo-color: #aaa; +$error-bg: #c67; +$warning-message-bg: #ffffe6; +$warning-message-border: #ed9; +$warning-message-color: #b90; +$control-group-descr-color: #666; +$table-permission-x-bg: #d9edf7; +$username-color: #666; +$description-color: #666; +$profiler-border: #eee; /* tanuki logo colors */ $tanuki-red: #e24329; @@ -205,12 +251,22 @@ $table-border-gray: #f0f0f0; $line-target-blue: #f6faff; $line-select-yellow: #fcf8e7; $line-select-yellow-dark: #f0e2bd; +$dark-diff-match-bg: rgba(255, 255, 255, 0.3); +$dark-diff-match-color: rgba(255, 255, 255, 0.1); +$file-mode-changed: #777; +$file-mode-changed: #777; +$diff-image-bg: #ddd; +$diff-image-info-color: grey; +$diff-image-img-bg: #e5e5e5; +$diff-swipe-border: #999; +$diff-view-modes-color: grey; +$diff-view-modes-border: #c1c1c1; /* * Fonts */ $monospace_font: 'Menlo', 'Liberation Mono', 'Consolas', 'DejaVu Sans Mono', 'Ubuntu Mono', 'Courier New', 'andale mono', 'lucida console', monospace; -$regular_font: 'Source Sans Pro', "Helvetica Neue", Helvetica, Arial, sans-serif; +$regular_font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; /* * Dropdowns @@ -226,6 +282,7 @@ $dropdown-divider-color: rgba(#000, .1); $dropdown-header-color: #959494; $dropdown-title-btn-color: #bfbfbf; $dropdown-input-color: #555; +$dropdown-input-fa-color: #c7c7c7; $dropdown-input-focus-border: $focus-border-color; $dropdown-input-focus-shadow: rgba($dropdown-input-focus-border, .4); $dropdown-loading-bg: rgba(#fff, .6); @@ -243,6 +300,7 @@ $dropdown-toggle-hover-icon-color: darken($dropdown-toggle-icon-color, 7%); * Buttons */ $btn-active-gray: #ececec; +$btn-active-gray-light: e4e7ed; $btn-placeholder-gray: #c7c7c7; $btn-white-active: #848484; $btn-gray-hover: #eee; @@ -252,6 +310,7 @@ $btn-gray-hover: #eee; */ $award-emoji-menu-bg: #fff; $award-emoji-menu-border: #f1f2f4; +$award-emoji-menu-shadow: rgba(0,0,0,.175); $award-emoji-new-btn-icon-color: #dcdcdc; /* @@ -273,17 +332,28 @@ $notes-light-color: #8e8e8e; $notes-action-color: #c3c3c3; $notes-role-color: #8e8e8e; $notes-role-border-color: #e4e4e4; - $note-disabled-comment-color: #b2b2b2; $note-form-border-color: #e5e5e5; $note-toolbar-color: #959494; +$note-targe3-outside: #fffff0; +$note-targe3-inside: #ffffd3; +$note-line2-border: #ddd; + +/* +* Zen +*/ +$zen-control-color: #555; $zen-control-hover-color: #111; +/* +* Calendar +*/ $calendar-header-color: #b8b8b8; $calendar-hover-bg: #ecf3fe; $calendar-border-color: rgba(#000, .1); $calendar-unselectable-bg: $gray-light; +$calendar-user-contrib-text: #959494; /* * Cycle Analytics @@ -293,13 +363,217 @@ $cycle-analytics-box-text-color: #8c8c8c; $cycle-analytics-big-font: 19px; $cycle-analytics-dark-text: $gl-title-color; $cycle-analytics-light-gray: #bfbfbf; +$cycle-analytics-dismiss-icon-color: #b2b2b2; /* * Personal Access Tokens */ $personal-access-tokens-disabled-label-color: #bbb; +/* +* CI +*/ $ci-output-bg: #1d1f21; $ci-text-color: #c5c8c6; +$ci-skipped-color: #888; +/* +* Boards +*/ $issue-boards-font-size: 15px; +$issue-boards-card-shadow: rgba(186, 186, 186, 0.5); + +/* +* Avatar +*/ +$avatar_radius: 50%; +$avatar-border: rgba(0, 0, 0, .1); +$gl-avatar-size: 40px; + +/* +* Builds +*/ +$builds-trace-bg: #111; + +/* +* Callout +*/ +$callout-danger-bg: #fdf7f7; +$callout-danger-border: #eed3d7; +$callout-danger-color: #b94a48; +$callout-warning-bg: #faf8f0; +$callout-warning-border: #faebcc; +$callout-warning-color: #8a6d3b; +$callout-info-bg: #f4f8fa; +$callout-info-border: #bce8f1; +$callout-info-color: #34789a; +$callout-success-bg: #dff0d8; +$callout-success-border: #5ca64d; +$callout-success-color: #3c763d; + +/* +* Commit Page +*/ +$commit-committer-color: #999; +$commit-max-width-marker-color: rgba(0, 0, 0, 0.0); +$commit-message-text-area-bg: rgba(0, 0, 0, 0.0); + +/* +* Common +*/ +$common-gray: $gl-gray; +$common-gray-light: #bbb; +$common-gray-dark: #444; +$common-red: $gl-text-red; +$common-green: $gl-text-green; + + +/* +* Dashboard +*/ +$dashboard-project-access-icon-color: #888; + +/* +* Editor +*/ +$editor-cancel-color: #b94a48; + +/* +* Events +*/ +$events-pre-color: #777; +$events-note-icon-color: #777; +$events-body-border: #ddd; + +/* +* Files +*/ +$file-image-bg: #eee; +$blob-bg: #eee; +$blame-border: #eee; +$blame-line-numbers-border: #ddd; +$logs-bg: #eee; +$logs-li-color: #888; +$logs-p-color: #333; + +/* +* Forms +*/ +$input-danger-bg: #f2dede; +$input-danger-border: #d66; +$input-group-addon-bg: #f7f8fa; +$gl-field-focus-shadow: rgba(0, 0, 0, 0.075); +$gl-field-focus-shadow-error: rgba(210, 40, 82, 0.6); + +/* +* Help +*/ +$document-index-color: #888; +$help-shortcut-color: #999; +$help-shortcut-mapping-color: #555; +$help-shortcut-header-color: #333; + +/* +* Issues +*/ +$issues-border: #e5e5e5; +$issues-today-bg: #f3fff2; +$issues-today-border: #e1e8d5; + +/* +* jQuery UI +*/ +$jq-ui-border: #ddd; +$jq-ui-default-color: #777; + +/* +* Label +*/ +$label-gray-bg: #f8fafc; +$label-inverse-bg: #333; +$label-remove-border: rgba(0, 0, 0, .1); + +/* +* Lint +*/ +$lint-incorrect-color: red; +$lint-correct-color: #47a447; + +/* +* Login +*/ +$login-brand-holder-color: #888; +$login-devise-error-color: #a00; + +/* +* Nav +*/ +$nav-link-gray: #959494; +$nav-badge-bg: #eee; +$nav-toggle-gray: #666; + +/* +* Notify +*/ +$notify-details: #777; +$notify-footer: #777; +$notify-new-file: #090; +$notify-deleted-file: #b00; + +/* +* Projects +*/ +$project-option-descr-color: #54565b; +$project-breadcrumb-color: #999; +$project-private-forks-notice-odd: #2aa056; +$project-network-controls-color: #888; +$project-limit-message-bg: #f28d35; + +/* +* Runners +*/ +$runner-state-shared-bg: #32b186; +$runner-state-specific-bg: #3498db; +$runner-status-online-color: green; +$runner-status-offline-color: gray; +$runner-status-paused-color: red; + +/* +Stat Graph +*/ +$stat-graph-common-bg: #f3f3f3; +$stat-graph-area-fill: #1db34f; +$stat-graph-axis-fill: #aaa; +$stat-graph-orange-fill: #f17f49; +$stat-graph-selection-fill: #333; +$stat-graph-selection-stroke: #333; + +/* +* Selects +*/ +$select2-drop-shadow1: rgba(76, 86, 103, 0.247059); +$select2-drop-shadow2: rgba(31, 37, 50, 0.317647); + + +/* +* Todo +*/ +$todo-alert-blue: #428bca; +$todo-body-pre-color: #777; +$todo-body-border: #ddd; + +/* +* Typography +*/ +$kdb-bg: #fcfcfc; +$kdb-color: #555; +$kdb-border: #ccc; +$kdb-border-bottom: #bbb; +$kdb-shadow: #bbb; +$body-text-shadow: rgba(255,255,255,0.01); + +/* +* UI Dev Kit +*/ +$ui-dev-kit-example-color: #bbb; +$ui-dev-kit-example-border: #ddd; diff --git a/app/assets/stylesheets/framework/zen.scss b/app/assets/stylesheets/framework/zen.scss index ff02ebdd34c..e5c7d70d45a 100644 --- a/app/assets/stylesheets/framework/zen.scss +++ b/app/assets/stylesheets/framework/zen.scss @@ -1,6 +1,6 @@ .zen-backdrop { &.fullscreen { - background-color: white; + background-color: $white-light; position: fixed; top: 0; bottom: 0; @@ -12,7 +12,7 @@ border: none; box-shadow: none; border-radius: 0; - color: #000; + color: $black; font-size: 20px; line-height: 26px; padding: 30px; @@ -34,7 +34,7 @@ .zen-control { padding: 0; - color: #555; + color: $zen-control-color; background: none; border: 0; } diff --git a/app/assets/stylesheets/highlight/dark.scss b/app/assets/stylesheets/highlight/dark.scss index d22d9b01495..cb923166b25 100644 --- a/app/assets/stylesheets/highlight/dark.scss +++ b/app/assets/stylesheets/highlight/dark.scss @@ -1,27 +1,109 @@ /* https://github.com/MozMorris/tomorrow-pygments */ + +/* +* Dark syntax colors +*/ +$dark-new-bg: rgba(51, 255, 51, 0.1); +$dark-new-idiff: rgba(51, 255, 51, 0.2); +$dark-old-bg: rgba(255, 51, 51, 0.2); +$dark-old-idiff: rgba(255, 51, 51, 0.25); +$dark-border: #808080; +$dark-code-border: #666; +$dark-main-bg: #1d1f21; +$dark-main-color: #1d1f21; +$dark-line-color: #c5c8c6; +$dark-line-num-color: rgba(255, 255, 255, 0.3); +$dark-diff-not-empty-bg: #557; +$dark-highlight-bg: #ffe792; +$dark-highlight-color: $black; +$dark-pre-hll-bg: #373b41; +$dark-hll-bg: #373b41; +$dark-c: #969896; +$dark-err: #c66; +$dark-k: #b294bb; +$dark-l: #de935f; +$dark-n: #c5c8c6; +$dark-o: #8abeb7; +$dark-p: #c5c8c6; +$dark-cm: #969896; +$dark-cp: #969896; +$dark-c1: #969896; +$dark-cs: #969896; +$dark-gd: #c66; +$dark-gh: #c5c8c6; +$dark-gi: #b5bd68; +$dark-gp: #969896; +$dark-gu: #8abeb7; +$dark-kc: #b294bb; +$dark-kd: #b294bb; +$dark-kn: #8abeb7; +$dark-kp: #b294bb; +$dark-kr: #b294bb; +$dark-kt: #f0c674; +$dark-ld: #b5bd68; +$dark-m: #de935f; +$dark-s: #b5bd68; +$dark-na: #81a2be; +$dark-nb: #c5c8c6; +$dark-nc: #f0c674; +$dark-no: #c66; +$dark-nd: #8abeb7; +$dark-ni: #c5c8c6; +$dark-ne: #c66; +$dark-nf: #81a2be; +$dark-nl: #c5c8c6; +$dark-nn: #f0c674; +$dark-nx: #81a2be; +$dark-py: #c5c8c6; +$dark-nt: #8abeb7; +$dark-nv: #c66; +$dark-ow: #8abeb7; +$dark-w: #c5c8c6; +$dark-mf: #de935f; +$dark-mh: #de935f; +$dark-mi: #de935f; +$dark-mo: #de935f; +$dark-sb: #b5bd68; +$dark-sc: #c5c8c6; +$dark-sd: #969896; +$dark-s2: #b5bd68; +$dark-se: #de935f; +$dark-sh: #b5bd68; +$dark-si: #de935f; +$dark-sx: #b5bd68; +$dark-sr: #b5bd68; +$dark-s1: #b5bd68; +$dark-ss: #b5bd68; +$dark-bp: #c5c8c6; +$dark-vc: #c66; +$dark-vg: #c66; +$dark-vi: #c66; +$dark-il: #de935f; + .code.dark { // Line numbers .line-numbers, .diff-line-num { - background-color: #1d1f21; + background-color: $dark-main-bg; } .diff-line-num, .diff-line-num a { - color: rgba(255, 255, 255, 0.3); + color: $dark-main-color; + color: $dark-line-num-color; } // Code itself pre.code, .diff-line-num { - border-color: #666; + border-color: $dark-code-border; } &, pre.code, .line_holder .line_content { - background-color: #1d1f21; - color: #c5c8c6; + background-color: $dark-main-bg; + color: $dark-line-color; } // Diff line @@ -32,18 +114,18 @@ td.diff-line-num.hll:not(.empty-cell), td.line_content.hll:not(.empty-cell) { - background-color: #557; - border-color: darken(#557, 15%); + background-color: $dark-diff-not-empty-bg; + border-color: darken($dark-diff-not-empty-bg, 15%); } .diff-line-num.new, .line_content.new { - @include diff_background(rgba(51, 255, 51, 0.1), rgba(51, 255, 51, 0.2), #808080); + @include diff_background($dark-new-bg, $dark-new-idiff, $dark-border); } .diff-line-num.old, .line_content.old { - @include diff_background(rgba(255, 51, 51, 0.2), rgba(255, 51, 51, 0.25), #808080); + @include diff_background($dark-old-bg, $dark-old-idiff, $dark-border); } .line_content.match { @@ -53,77 +135,77 @@ // highlight line via anchor pre .hll { - background-color: #557 !important; + background-color: $dark-pre-hll-bg !important; } // Search result highlight span.highlight_word { - background-color: #ffe792 !important; - color: #000 !important; + background-color: $dark-highlight-bg !important; + color: $dark-highlight-color !important; } - .hll { background-color: #373b41; } - .c { color: #969896; } /* Comment */ - .err { color: #c66; } /* Error */ - .k { color: #b294bb; } /* Keyword */ - .l { color: #de935f; } /* Literal */ - .n { color: #c5c8c6; } /* Name */ - .o { color: #8abeb7; } /* Operator */ - .p { color: #c5c8c6; } /* Punctuation */ - .cm { color: #969896; } /* Comment.Multiline */ - .cp { color: #969896; } /* Comment.Preproc */ - .c1 { color: #969896; } /* Comment.Single */ - .cs { color: #969896; } /* Comment.Special */ - .gd { color: #c66; } /* Generic.Deleted */ + .hll { background-color: $dark-hll-bg; } + .c { color: $dark-c; } /* Comment */ + .err { color: $dark-err; } /* Error */ + .k { color: $dark-k; } /* Keyword */ + .l { color: $dark-l; } /* Literal */ + .n { color: $dark-n; } /* Name */ + .o { color: $dark-o; } /* Operator */ + .p { color: $dark-p; } /* Punctuation */ + .cm { color: $dark-cm; } /* Comment.Multiline */ + .cp { color: $dark-cp; } /* Comment.Preproc */ + .c1 { color: $dark-c1; } /* Comment.Single */ + .cs { color: $dark-cs; } /* Comment.Special */ + .gd { color: $dark-gd; } /* Generic.Deleted */ .ge { font-style: italic; } /* Generic.Emph */ - .gh { color: #c5c8c6; font-weight: bold; } /* Generic.Heading */ - .gi { color: #b5bd68; } /* Generic.Inserted */ - .gp { color: #969896; font-weight: bold; } /* Generic.Prompt */ + .gh { color: $dark-gh; font-weight: bold; } /* Generic.Heading */ + .gi { color: $dark-gi; } /* Generic.Inserted */ + .gp { color: $dark-gp; font-weight: bold; } /* Generic.Prompt */ .gs { font-weight: bold; } /* Generic.Strong */ - .gu { color: #8abeb7; font-weight: bold; } /* Generic.Subheading */ - .kc { color: #b294bb; } /* Keyword.Constant */ - .kd { color: #b294bb; } /* Keyword.Declaration */ - .kn { color: #8abeb7; } /* Keyword.Namespace */ - .kp { color: #b294bb; } /* Keyword.Pseudo */ - .kr { color: #b294bb; } /* Keyword.Reserved */ - .kt { color: #f0c674; } /* Keyword.Type */ - .ld { color: #b5bd68; } /* Literal.Date */ - .m { color: #de935f; } /* Literal.Number */ - .s { color: #b5bd68; } /* Literal.String */ - .na { color: #81a2be; } /* Name.Attribute */ - .nb { color: #c5c8c6; } /* Name.Builtin */ - .nc { color: #f0c674; } /* Name.Class */ - .no { color: #c66; } /* Name.Constant */ - .nd { color: #8abeb7; } /* Name.Decorator */ - .ni { color: #c5c8c6; } /* Name.Entity */ - .ne { color: #c66; } /* Name.Exception */ - .nf { color: #81a2be; } /* Name.Function */ - .nl { color: #c5c8c6; } /* Name.Label */ - .nn { color: #f0c674; } /* Name.Namespace */ - .nx { color: #81a2be; } /* Name.Other */ - .py { color: #c5c8c6; } /* Name.Property */ - .nt { color: #8abeb7; } /* Name.Tag */ - .nv { color: #c66; } /* Name.Variable */ - .ow { color: #8abeb7; } /* Operator.Word */ - .w { color: #c5c8c6; } /* Text.Whitespace */ - .mf { color: #de935f; } /* Literal.Number.Float */ - .mh { color: #de935f; } /* Literal.Number.Hex */ - .mi { color: #de935f; } /* Literal.Number.Integer */ - .mo { color: #de935f; } /* Literal.Number.Oct */ - .sb { color: #b5bd68; } /* Literal.String.Backtick */ - .sc { color: #c5c8c6; } /* Literal.String.Char */ - .sd { color: #969896; } /* Literal.String.Doc */ - .s2 { color: #b5bd68; } /* Literal.String.Double */ - .se { color: #de935f; } /* Literal.String.Escape */ - .sh { color: #b5bd68; } /* Literal.String.Heredoc */ - .si { color: #de935f; } /* Literal.String.Interpol */ - .sx { color: #b5bd68; } /* Literal.String.Other */ - .sr { color: #b5bd68; } /* Literal.String.Regex */ - .s1 { color: #b5bd68; } /* Literal.String.Single */ - .ss { color: #b5bd68; } /* Literal.String.Symbol */ - .bp { color: #c5c8c6; } /* Name.Builtin.Pseudo */ - .vc { color: #c66; } /* Name.Variable.Class */ - .vg { color: #c66; } /* Name.Variable.Global */ - .vi { color: #c66; } /* Name.Variable.Instance */ - .il { color: #de935f; } /* Literal.Number.Integer.Long */ + .gu { color: $dark-gu; font-weight: bold; } /* Generic.Subheading */ + .kc { color: $dark-kc; } /* Keyword.Constant */ + .kd { color: $dark-kd; } /* Keyword.Declaration */ + .kn { color: $dark-kn; } /* Keyword.Namespace */ + .kp { color: $dark-kp; } /* Keyword.Pseudo */ + .kr { color: $dark-kr; } /* Keyword.Reserved */ + .kt { color: $dark-kt; } /* Keyword.Type */ + .ld { color: $dark-ld; } /* Literal.Date */ + .m { color: $dark-m; } /* Literal.Number */ + .s { color: $dark-s; } /* Literal.String */ + .na { color: $dark-na; } /* Name.Attribute */ + .nb { color: $dark-nb; } /* Name.Builtin */ + .nc { color: $dark-nc; } /* Name.Class */ + .no { color: $dark-no; } /* Name.Constant */ + .nd { color: $dark-nd; } /* Name.Decorator */ + .ni { color: $dark-ni; } /* Name.Entity */ + .ne { color: $dark-ne; } /* Name.Exception */ + .nf { color: $dark-nf; } /* Name.Function */ + .nl { color: $dark-nl; } /* Name.Label */ + .nn { color: $dark-nn; } /* Name.Namespace */ + .nx { color: $dark-nx; } /* Name.Other */ + .py { color: $dark-py; } /* Name.Property */ + .nt { color: $dark-nt; } /* Name.Tag */ + .nv { color: $dark-nv; } /* Name.Variable */ + .ow { color: $dark-ow; } /* Operator.Word */ + .w { color: $dark-w; } /* Text.Whitespace */ + .mf { color: $dark-mf; } /* Literal.Number.Float */ + .mh { color: $dark-mh; } /* Literal.Number.Hex */ + .mi { color: $dark-mi; } /* Literal.Number.Integer */ + .mo { color: $dark-mo; } /* Literal.Number.Oct */ + .sb { color: $dark-sb; } /* Literal.String.Backtick */ + .sc { color: $dark-sc; } /* Literal.String.Char */ + .sd { color: $dark-sd; } /* Literal.String.Doc */ + .s2 { color: $dark-s2; } /* Literal.String.Double */ + .se { color: $dark-se; } /* Literal.String.Escape */ + .sh { color: $dark-sh; } /* Literal.String.Heredoc */ + .si { color: $dark-si; } /* Literal.String.Interpol */ + .sx { color: $dark-sx; } /* Literal.String.Other */ + .sr { color: $dark-sr; } /* Literal.String.Regex */ + .s1 { color: $dark-s1; } /* Literal.String.Single */ + .ss { color: $dark-ss; } /* Literal.String.Symbol */ + .bp { color: $dark-bp; } /* Name.Builtin.Pseudo */ + .vc { color: $dark-vc; } /* Name.Variable.Class */ + .vg { color: $dark-vg; } /* Name.Variable.Global */ + .vi { color: $dark-vi; } /* Name.Variable.Instance */ + .il { color: $dark-il; } /* Literal.Number.Integer.Long */ } diff --git a/app/assets/stylesheets/highlight/monokai.scss b/app/assets/stylesheets/highlight/monokai.scss index db8da8aab10..d8510baad8a 100644 --- a/app/assets/stylesheets/highlight/monokai.scss +++ b/app/assets/stylesheets/highlight/monokai.scss @@ -1,27 +1,108 @@ /* https://github.com/richleland/pygments-css/blob/master/monokai.css */ + +/* +* Monokai Colors +*/ +$monokai-bg: #272822; +$monokai-border: #555; +$monokai-text-color: #f8f8f2; +$monokai-line-num-color: rgba(255, 255, 255, 0.3); +$monokai-line-empty-bg: #49483e; +$monokai-line-empty-border: darken($monokai-line-empty-bg, 15%); +$monokai-diff-border: #808080; +$monokai-highlight-bg: #ffe792; + +$monokai-new-bg: rgba(166, 226, 46, 0.1); +$monokai-new-idiff: rgba(166, 226, 46, 0.15); + +$monokai-old-bg: rgba(254, 147, 140, 0.15); +$monokai-old-idiff: rgba(254, 147, 140, 0.2); + +$monokai-hll: #49483e; +$monokai-c: #75715e; +$monokai-err-color: #960050; +$monokai-err-bg: #1e0010; +$monokai-k: #66d9ef; +$monokai-l: #ae81ff; +$monokai-n: #f8f8f2; +$monokai-o: #f92672; +$monokai-p: #f8f8f2; +$monokai-cm: #75715e; +$monokai-cp: #75715e; +$monokai-c1: #75715e; +$monokai-cs: #75715e; +$monokai-kc: #66d9ef; +$monokai-kd: #66d9ef; +$monokai-kn: #f92672; +$monokai-kp: #66d9ef; +$monokai-kr: #66d9ef; +$monokai-kt: #66d9ef; +$monokai-ld: #e6db74; +$monokai-m: #ae81ff; +$monokai-s: #e6db74; +$monokai-na: #a6e22e; +$monokai-nb: #f8f8f2; +$monokai-nc: #a6e22e; +$monokai-no: #66d9ef; +$monokai-nd: #a6e22e; +$monokai-ni: #f8f8f2; +$monokai-ne: #a6e22e; +$monokai-nf: #a6e22e; +$monokai-nl: #f8f8f2; +$monokai-nn: #f8f8f2; +$monokai-nx: #a6e22e; +$monokai-py: #f8f8f2; +$monokai-nt: #f92672; +$monokai-nv: #f8f8f2; +$monokai-ow: #f92672; +$monokai-w: #f8f8f2; +$monokai-mf: #ae81ff; +$monokai-mh: #ae81ff; +$monokai-mi: #ae81ff; +$monokai-mo: #ae81ff; +$monokai-sb: #e6db74; +$monokai-sc: #e6db74; +$monokai-sd: #e6db74; +$monokai-s2: #e6db74; +$monokai-se: #ae81ff; +$monokai-sh: #e6db74; +$monokai-si: #e6db74; +$monokai-sx: #e6db74; +$monokai-sr: #e6db74; +$monokai-s1: #e6db74; +$monokai-ss: #e6db74; +$monokai-bp: #f8f8f2; +$monokai-vc: #f8f8f2; +$monokai-vg: #f8f8f2; +$monokai-vi: #f8f8f2; +$monokai-il: #ae81ff; +$monokai-gu: #75715e; +$monokai-gd: #f92672; +$monokai-gi: #a6e22e; + .code.monokai { // Line numbers .line-numbers, .diff-line-num { - background-color: #272822; + background-color: $monokai-bg; } .diff-line-num, .diff-line-num a { - color: rgba(255, 255, 255, 0.3); + color: $monokai-line-num-color; } // Code itself pre.code, .diff-line-num { - border-color: #555; + border-color: $monokai-border; } &, pre.code, .line_holder .line_content { - background-color: #272822; - color: #f8f8f2; + background-color: $monokai-bg; + color: $monokai-text-color; } // Diff line @@ -32,18 +113,18 @@ td.diff-line-num.hll:not(.empty-cell), td.line_content.hll:not(.empty-cell) { - background-color: #49483e; - border-color: darken(#49483e, 15%); + background-color: $monokai-line-empty-bg; + border-color: $monokai-line-empty-border; } .diff-line-num.new, .line_content.new { - @include diff_background(rgba(166, 226, 46, 0.1), rgba(166, 226, 46, 0.15), #808080); + @include diff_background($monokai-new-bg, $monokai-new-idiff, $monokai-diff-border); } .diff-line-num.old, .line_content.old { - @include diff_background(rgba(254, 147, 140, 0.15), rgba(254, 147, 140, 0.2), #808080); + @include diff_background($monokai-old-bg, $monokai-old-idiff, $monokai-diff-border); } .line_content.match { @@ -53,75 +134,75 @@ // highlight line via anchor pre .hll { - background-color: #49483e !important; + background-color: $monokai-hll !important; } // Search result highlight span.highlight_word { - background-color: #ffe792 !important; - color: #000 !important; + background-color: $monokai-highlight-bg !important; + color: $black !important; } - .hll { background-color: #49483e; } - .c { color: #75715e; } /* Comment */ - .err { color: #960050; background-color: #1e0010; } /* Error */ - .k { color: #66d9ef; } /* Keyword */ - .l { color: #ae81ff; } /* Literal */ - .n { color: #f8f8f2; } /* Name */ - .o { color: #f92672; } /* Operator */ - .p { color: #f8f8f2; } /* Punctuation */ - .cm { color: #75715e; } /* Comment.Multiline */ - .cp { color: #75715e; } /* Comment.Preproc */ - .c1 { color: #75715e; } /* Comment.Single */ - .cs { color: #75715e; } /* Comment.Special */ + .hll { background-color: $monokai-hll; } + .c { color: $monokai-c; } /* Comment */ + .err { color: $monokai-err-color; background-color: $monokai-err-bg; } /* Error */ + .k { color: $monokai-k; } /* Keyword */ + .l { color: $monokai-l; } /* Literal */ + .n { color: $monokai-n; } /* Name */ + .o { color: $monokai-o; } /* Operator */ + .p { color: $monokai-p; } /* Punctuation */ + .cm { color: $monokai-cm; } /* Comment.Multiline */ + .cp { color: $monokai-cp; } /* Comment.Preproc */ + .c1 { color: $monokai-c1; } /* Comment.Single */ + .cs { color: $monokai-cs; } /* Comment.Special */ .ge { font-style: italic; } /* Generic.Emph */ .gs { font-weight: bold; } /* Generic.Strong */ - .kc { color: #66d9ef; } /* Keyword.Constant */ - .kd { color: #66d9ef; } /* Keyword.Declaration */ - .kn { color: #f92672; } /* Keyword.Namespace */ - .kp { color: #66d9ef; } /* Keyword.Pseudo */ - .kr { color: #66d9ef; } /* Keyword.Reserved */ - .kt { color: #66d9ef; } /* Keyword.Type */ - .ld { color: #e6db74; } /* Literal.Date */ - .m { color: #ae81ff; } /* Literal.Number */ - .s { color: #e6db74; } /* Literal.String */ - .na { color: #a6e22e; } /* Name.Attribute */ - .nb { color: #f8f8f2; } /* Name.Builtin */ - .nc { color: #a6e22e; } /* Name.Class */ - .no { color: #66d9ef; } /* Name.Constant */ - .nd { color: #a6e22e; } /* Name.Decorator */ - .ni { color: #f8f8f2; } /* Name.Entity */ - .ne { color: #a6e22e; } /* Name.Exception */ - .nf { color: #a6e22e; } /* Name.Function */ - .nl { color: #f8f8f2; } /* Name.Label */ - .nn { color: #f8f8f2; } /* Name.Namespace */ - .nx { color: #a6e22e; } /* Name.Other */ - .py { color: #f8f8f2; } /* Name.Property */ - .nt { color: #f92672; } /* Name.Tag */ - .nv { color: #f8f8f2; } /* Name.Variable */ - .ow { color: #f92672; } /* Operator.Word */ - .w { color: #f8f8f2; } /* Text.Whitespace */ - .mf { color: #ae81ff; } /* Literal.Number.Float */ - .mh { color: #ae81ff; } /* Literal.Number.Hex */ - .mi { color: #ae81ff; } /* Literal.Number.Integer */ - .mo { color: #ae81ff; } /* Literal.Number.Oct */ - .sb { color: #e6db74; } /* Literal.String.Backtick */ - .sc { color: #e6db74; } /* Literal.String.Char */ - .sd { color: #e6db74; } /* Literal.String.Doc */ - .s2 { color: #e6db74; } /* Literal.String.Double */ - .se { color: #ae81ff; } /* Literal.String.Escape */ - .sh { color: #e6db74; } /* Literal.String.Heredoc */ - .si { color: #e6db74; } /* Literal.String.Interpol */ - .sx { color: #e6db74; } /* Literal.String.Other */ - .sr { color: #e6db74; } /* Literal.String.Regex */ - .s1 { color: #e6db74; } /* Literal.String.Single */ - .ss { color: #e6db74; } /* Literal.String.Symbol */ - .bp { color: #f8f8f2; } /* Name.Builtin.Pseudo */ - .vc { color: #f8f8f2; } /* Name.Variable.Class */ - .vg { color: #f8f8f2; } /* Name.Variable.Global */ - .vi { color: #f8f8f2; } /* Name.Variable.Instance */ - .il { color: #ae81ff; } /* Literal.Number.Integer.Long */ - .gu { color: #75715e; } /* Generic.Subheading & Diff Unified/Comment? */ - .gd { color: #f92672; } /* Generic.Deleted & Diff Deleted */ - .gi { color: #a6e22e; } /* Generic.Inserted & Diff Inserted */ + .kc { color: $monokai-kc; } /* Keyword.Constant */ + .kd { color: $monokai-kd; } /* Keyword.Declaration */ + .kn { color: $monokai-kn; } /* Keyword.Namespace */ + .kp { color: $monokai-kp; } /* Keyword.Pseudo */ + .kr { color: $monokai-kr; } /* Keyword.Reserved */ + .kt { color: $monokai-kt; } /* Keyword.Type */ + .ld { color: $monokai-ld; } /* Literal.Date */ + .m { color: $monokai-m; } /* Literal.Number */ + .s { color: $monokai-s; } /* Literal.String */ + .na { color: $monokai-na; } /* Name.Attribute */ + .nb { color: $monokai-nb; } /* Name.Builtin */ + .nc { color: $monokai-nc; } /* Name.Class */ + .no { color: $monokai-no; } /* Name.Constant */ + .nd { color: $monokai-nd; } /* Name.Decorator */ + .ni { color: $monokai-ni; } /* Name.Entity */ + .ne { color: $monokai-ne; } /* Name.Exception */ + .nf { color: $monokai-nf; } /* Name.Function */ + .nl { color: $monokai-nl; } /* Name.Label */ + .nn { color: $monokai-nn; } /* Name.Namespace */ + .nx { color: $monokai-nx; } /* Name.Other */ + .py { color: $monokai-py; } /* Name.Property */ + .nt { color: $monokai-nt; } /* Name.Tag */ + .nv { color: $monokai-nv; } /* Name.Variable */ + .ow { color: $monokai-ow; } /* Operator.Word */ + .w { color: $monokai-w; } /* Text.Whitespace */ + .mf { color: $monokai-mf; } /* Literal.Number.Float */ + .mh { color: $monokai-mh; } /* Literal.Number.Hex */ + .mi { color: $monokai-mi; } /* Literal.Number.Integer */ + .mo { color: $monokai-mo; } /* Literal.Number.Oct */ + .sb { color: $monokai-sb; } /* Literal.String.Backtick */ + .sc { color: $monokai-sc; } /* Literal.String.Char */ + .sd { color: $monokai-sd; } /* Literal.String.Doc */ + .s2 { color: $monokai-s2; } /* Literal.String.Double */ + .se { color: $monokai-se; } /* Literal.String.Escape */ + .sh { color: $monokai-sh; } /* Literal.String.Heredoc */ + .si { color: $monokai-si; } /* Literal.String.Interpol */ + .sx { color: $monokai-sx; } /* Literal.String.Other */ + .sr { color: $monokai-sr; } /* Literal.String.Regex */ + .s1 { color: $monokai-s1; } /* Literal.String.Single */ + .ss { color: $monokai-ss; } /* Literal.String.Symbol */ + .bp { color: $monokai-bp; } /* Name.Builtin.Pseudo */ + .vc { color: $monokai-vc; } /* Name.Variable.Class */ + .vg { color: $monokai-vg; } /* Name.Variable.Global */ + .vi { color: $monokai-vi; } /* Name.Variable.Instance */ + .il { color: $monokai-il; } /* Literal.Number.Integer.Long */ + .gu { color: $monokai-gu; } /* Generic.Subheading & Diff Unified/Comment? */ + .gd { color: $monokai-gd; } /* Generic.Deleted & Diff Deleted */ + .gi { color: $monokai-gi; } /* Generic.Inserted & Diff Inserted */ } diff --git a/app/assets/stylesheets/highlight/solarized_dark.scss b/app/assets/stylesheets/highlight/solarized_dark.scss index a87333146de..874aecb5e16 100644 --- a/app/assets/stylesheets/highlight/solarized_dark.scss +++ b/app/assets/stylesheets/highlight/solarized_dark.scss @@ -1,27 +1,112 @@ /* https://gist.github.com/qguv/7936275 */ + +/* +* Solarized dark colors +*/ +$solarized-dark-new-bg: rgba(133, 153, 0, 0.15); +$solarized-dark-new-idiff: rgba(133, 153, 0, 0.25); +$solarized-dark-old-bg: rgba(220, 50, 47, 0.3); +$solarized-dark-old-idiff: rgba(220, 50, 47, 0.25); +$solarized-dark-border: #113b46; +$solarized-dark-pre-bg: #002b36; +$solarized-dark-pre-color: #93a1a1; +$solarized-dark-pre-border: #113b46; +$solarized-dark-line-bg: #002b36; +$solarized-dark-line-color: rgba(255, 255, 255, 0.3); +$solarized-dark-highlight: #094554; +$solarized-dark-hll-bg: #174652; +$solarized-dark-c: #586e75; +$solarized-dark-err: #93a1a1; +$solarized-dark-g: #93a1a1; +$solarized-dark-k: #859900; +$solarized-dark-l: #93a1a1; +$solarized-dark-n: #93a1a1; +$solarized-dark-o: #859900; +$solarized-dark-x: #cb4b16; +$solarized-dark-p: #93a1a1; +$solarized-dark-cm: #586e75; +$solarized-dark-cp: #859900; +$solarized-dark-c1: #586e75; +$solarized-dark-cs: #859900; +$solarized-dark-gd: #2aa198; +$solarized-dark-ge: #93a1a1; +$solarized-dark-gr: #dc322f; +$solarized-dark-gh: #cb4b16; +$solarized-dark-gi: #859900; +$solarized-dark-go: #93a1a1; +$solarized-dark-gp: #93a1a1; +$solarized-dark-gs: #93a1a1; +$solarized-dark-gu: #cb4b16; +$solarized-dark-gt: #93a1a1; +$solarized-dark-kc: #cb4b16; +$solarized-dark-kd: #268bd2; +$solarized-dark-kn: #859900; +$solarized-dark-kp: #859900; +$solarized-dark-kr: #268bd2; +$solarized-dark-kt: #dc322f; +$solarized-dark-ld: #93a1a1; +$solarized-dark-m: #2aa198; +$solarized-dark-s: #2aa198; +$solarized-dark-na: #93a1a1; +$solarized-dark-nb: #b58900; +$solarized-dark-nc: #268bd2; +$solarized-dark-no: #cb4b16; +$solarized-dark-nd: #268bd2; +$solarized-dark-ni: #cb4b16; +$solarized-dark-ne: #cb4b16; +$solarized-dark-nf: #268bd2; +$solarized-dark-nl: #93a1a1; +$solarized-dark-nn: #93a1a1; +$solarized-dark-nx: #93a1a1; +$solarized-dark-py: #93a1a1; +$solarized-dark-nt: #268bd2; +$solarized-dark-nv: #268bd2; +$solarized-dark-ow: #859900; +$solarized-dark-w: #93a1a1; +$solarized-dark-mf: #2aa198; +$solarized-dark-mh: #2aa198; +$solarized-dark-mi: #2aa198; +$solarized-dark-mo: #2aa198; +$solarized-dark-sb: #586e75; +$solarized-dark-sc: #2aa198; +$solarized-dark-sd: #93a1a1; +$solarized-dark-s2: #2aa198; +$solarized-dark-se: #cb4b16; +$solarized-dark-sh: #93a1a1; +$solarized-dark-si: #2aa198; +$solarized-dark-sx: #2aa198; +$solarized-dark-sr: #dc322f; +$solarized-dark-s1: #2aa198; +$solarized-dark-ss: #2aa198; +$solarized-dark-bp: #268bd2; +$solarized-dark-vc: #268bd2; +$solarized-dark-vg: #268bd2; +$solarized-dark-vi: #268bd2; +$solarized-dark-il: #2aa198; + .code.solarized-dark { // Line numbers .line-numbers, .diff-line-num { - background-color: #002b36; + background-color: $solarized-dark-line-bg; } .diff-line-num, .diff-line-num a { - color: rgba(255, 255, 255, 0.3); + color: $solarized-dark-line-color; } // Code itself pre.code, .diff-line-num { - border-color: #113b46; + border-color: $solarized-dark-pre-border; } &, pre.code, .line_holder .line_content { - background-color: #002b36; - color: #93a1a1; + background-color: $solarized-dark-pre-bg; + color: $solarized-dark-pre-color; } // Diff line @@ -32,18 +117,18 @@ td.diff-line-num.hll:not(.empty-cell), td.line_content.hll:not(.empty-cell) { - background-color: #174652; - border-color: darken(#174652, 15%); + background-color: $solarized-dark-hll-bg; + border-color: darken($solarized-dark-hll-bg, 15%); } .diff-line-num.new, .line_content.new { - @include diff_background(rgba(133, 153, 0, 0.15), rgba(133, 153, 0, 0.25), #113b46); + @include diff_background($solarized-dark-new-bg, $solarized-dark-new-idiff, $solarized-dark-border); } .diff-line-num.old, .line_content.old { - @include diff_background(rgba(220, 50, 47, 0.3), rgba(220, 50, 47, 0.25), #113b46); + @include diff_background($solarized-dark-old-bg, $solarized-dark-old-idiff, $solarized-dark-border); } .line_content.match { @@ -53,12 +138,12 @@ // highlight line via anchor pre .hll { - background-color: #174652 !important; + background-color: $solarized-dark-hll-bg !important; } // Search result highlight span.highlight_word { - background-color: #094554 !important; + background-color: $solarized-dark-highlight !important; } /* Solarized Dark @@ -79,72 +164,72 @@ green #859900 operators, other keywords */ - .c { color: #586e75; } /* Comment */ - .err { color: #93a1a1; } /* Error */ - .g { color: #93a1a1; } /* Generic */ - .k { color: #859900; } /* Keyword */ - .l { color: #93a1a1; } /* Literal */ - .n { color: #93a1a1; } /* Name */ - .o { color: #859900; } /* Operator */ - .x { color: #cb4b16; } /* Other */ - .p { color: #93a1a1; } /* Punctuation */ - .cm { color: #586e75; } /* Comment.Multiline */ - .cp { color: #859900; } /* Comment.Preproc */ - .c1 { color: #586e75; } /* Comment.Single */ - .cs { color: #859900; } /* Comment.Special */ - .gd { color: #2aa198; } /* Generic.Deleted */ - .ge { color: #93a1a1; font-style: italic; } /* Generic.Emph */ - .gr { color: #dc322f; } /* Generic.Error */ - .gh { color: #cb4b16; } /* Generic.Heading */ - .gi { color: #859900; } /* Generic.Inserted */ - .go { color: #93a1a1; } /* Generic.Output */ - .gp { color: #93a1a1; } /* Generic.Prompt */ - .gs { color: #93a1a1; font-weight: bold; } /* Generic.Strong */ - .gu { color: #cb4b16; } /* Generic.Subheading */ - .gt { color: #93a1a1; } /* Generic.Traceback */ - .kc { color: #cb4b16; } /* Keyword.Constant */ - .kd { color: #268bd2; } /* Keyword.Declaration */ - .kn { color: #859900; } /* Keyword.Namespace */ - .kp { color: #859900; } /* Keyword.Pseudo */ - .kr { color: #268bd2; } /* Keyword.Reserved */ - .kt { color: #dc322f; } /* Keyword.Type */ - .ld { color: #93a1a1; } /* Literal.Date */ - .m { color: #2aa198; } /* Literal.Number */ - .s { color: #2aa198; } /* Literal.String */ - .na { color: #93a1a1; } /* Name.Attribute */ - .nb { color: #b58900; } /* Name.Builtin */ - .nc { color: #268bd2; } /* Name.Class */ - .no { color: #cb4b16; } /* Name.Constant */ - .nd { color: #268bd2; } /* Name.Decorator */ - .ni { color: #cb4b16; } /* Name.Entity */ - .ne { color: #cb4b16; } /* Name.Exception */ - .nf { color: #268bd2; } /* Name.Function */ - .nl { color: #93a1a1; } /* Name.Label */ - .nn { color: #93a1a1; } /* Name.Namespace */ - .nx { color: #93a1a1; } /* Name.Other */ - .py { color: #93a1a1; } /* Name.Property */ - .nt { color: #268bd2; } /* Name.Tag */ - .nv { color: #268bd2; } /* Name.Variable */ - .ow { color: #859900; } /* Operator.Word */ - .w { color: #93a1a1; } /* Text.Whitespace */ - .mf { color: #2aa198; } /* Literal.Number.Float */ - .mh { color: #2aa198; } /* Literal.Number.Hex */ - .mi { color: #2aa198; } /* Literal.Number.Integer */ - .mo { color: #2aa198; } /* Literal.Number.Oct */ - .sb { color: #586e75; } /* Literal.String.Backtick */ - .sc { color: #2aa198; } /* Literal.String.Char */ - .sd { color: #93a1a1; } /* Literal.String.Doc */ - .s2 { color: #2aa198; } /* Literal.String.Double */ - .se { color: #cb4b16; } /* Literal.String.Escape */ - .sh { color: #93a1a1; } /* Literal.String.Heredoc */ - .si { color: #2aa198; } /* Literal.String.Interpol */ - .sx { color: #2aa198; } /* Literal.String.Other */ - .sr { color: #dc322f; } /* Literal.String.Regex */ - .s1 { color: #2aa198; } /* Literal.String.Single */ - .ss { color: #2aa198; } /* Literal.String.Symbol */ - .bp { color: #268bd2; } /* Name.Builtin.Pseudo */ - .vc { color: #268bd2; } /* Name.Variable.Class */ - .vg { color: #268bd2; } /* Name.Variable.Global */ - .vi { color: #268bd2; } /* Name.Variable.Instance */ - .il { color: #2aa198; } /* Literal.Number.Integer.Long */ + .c { color: $solarized-dark-c; } /* Comment */ + .err { color: $solarized-dark-err; } /* Error */ + .g { color: $solarized-dark-g; } /* Generic */ + .k { color: $solarized-dark-k; } /* Keyword */ + .l { color: $solarized-dark-l; } /* Literal */ + .n { color: $solarized-dark-n; } /* Name */ + .o { color: $solarized-dark-o; } /* Operator */ + .x { color: $solarized-dark-x; } /* Other */ + .p { color: $solarized-dark-p; } /* Punctuation */ + .cm { color: $solarized-dark-cm; } /* Comment.Multiline */ + .cp { color: $solarized-dark-cp; } /* Comment.Preproc */ + .c1 { color: $solarized-dark-c1; } /* Comment.Single */ + .cs { color: $solarized-dark-cs; } /* Comment.Special */ + .gd { color: $solarized-dark-gd; } /* Generic.Deleted */ + .ge { color: $solarized-dark-ge; font-style: italic; } /* Generic.Emph */ + .gr { color: $solarized-dark-gr; } /* Generic.Error */ + .gh { color: $solarized-dark-gh; } /* Generic.Heading */ + .gi { color: $solarized-dark-gi; } /* Generic.Inserted */ + .go { color: $solarized-dark-go; } /* Generic.Output */ + .gp { color: $solarized-dark-gp; } /* Generic.Prompt */ + .gs { color: $solarized-dark-gs; font-weight: bold; } /* Generic.Strong */ + .gu { color: $solarized-dark-gu; } /* Generic.Subheading */ + .gt { color: $solarized-dark-gt; } /* Generic.Traceback */ + .kc { color: $solarized-dark-kc; } /* Keyword.Constant */ + .kd { color: $solarized-dark-kd; } /* Keyword.Declaration */ + .kn { color: $solarized-dark-kn; } /* Keyword.Namespace */ + .kp { color: $solarized-dark-kp; } /* Keyword.Pseudo */ + .kr { color: $solarized-dark-kr; } /* Keyword.Reserved */ + .kt { color: $solarized-dark-kt; } /* Keyword.Type */ + .ld { color: $solarized-dark-ld; } /* Literal.Date */ + .m { color: $solarized-dark-m; } /* Literal.Number */ + .s { color: $solarized-dark-s; } /* Literal.String */ + .na { color: $solarized-dark-na; } /* Name.Attribute */ + .nb { color: $solarized-dark-nb; } /* Name.Builtin */ + .nc { color: $solarized-dark-nc; } /* Name.Class */ + .no { color: $solarized-dark-no; } /* Name.Constant */ + .nd { color: $solarized-dark-nd; } /* Name.Decorator */ + .ni { color: $solarized-dark-ni; } /* Name.Entity */ + .ne { color: $solarized-dark-ne; } /* Name.Exception */ + .nf { color: $solarized-dark-nf; } /* Name.Function */ + .nl { color: $solarized-dark-nl; } /* Name.Label */ + .nn { color: $solarized-dark-nn; } /* Name.Namespace */ + .nx { color: $solarized-dark-nx; } /* Name.Other */ + .py { color: $solarized-dark-py; } /* Name.Property */ + .nt { color: $solarized-dark-nt; } /* Name.Tag */ + .nv { color: $solarized-dark-nv; } /* Name.Variable */ + .ow { color: $solarized-dark-ow; } /* Operator.Word */ + .w { color: $solarized-dark-w; } /* Text.Whitespace */ + .mf { color: $solarized-dark-mf; } /* Literal.Number.Float */ + .mh { color: $solarized-dark-mh; } /* Literal.Number.Hex */ + .mi { color: $solarized-dark-mi; } /* Literal.Number.Integer */ + .mo { color: $solarized-dark-mo; } /* Literal.Number.Oct */ + .sb { color: $solarized-dark-sb; } /* Literal.String.Backtick */ + .sc { color: $solarized-dark-sc; } /* Literal.String.Char */ + .sd { color: $solarized-dark-sd; } /* Literal.String.Doc */ + .s2 { color: $solarized-dark-s2; } /* Literal.String.Double */ + .se { color: $solarized-dark-se; } /* Literal.String.Escape */ + .sh { color: $solarized-dark-sh; } /* Literal.String.Heredoc */ + .si { color: $solarized-dark-si; } /* Literal.String.Interpol */ + .sx { color: $solarized-dark-sx; } /* Literal.String.Other */ + .sr { color: $solarized-dark-sr; } /* Literal.String.Regex */ + .s1 { color: $solarized-dark-s1; } /* Literal.String.Single */ + .ss { color: $solarized-dark-ss; } /* Literal.String.Symbol */ + .bp { color: $solarized-dark-bp; } /* Name.Builtin.Pseudo */ + .vc { color: $solarized-dark-vc; } /* Name.Variable.Class */ + .vg { color: $solarized-dark-vg; } /* Name.Variable.Global */ + .vi { color: $solarized-dark-vi; } /* Name.Variable.Instance */ + .il { color: $solarized-dark-il; } /* Literal.Number.Integer.Long */ } diff --git a/app/assets/stylesheets/highlight/solarized_light.scss b/app/assets/stylesheets/highlight/solarized_light.scss index faff353ded7..499a1c108b8 100644 --- a/app/assets/stylesheets/highlight/solarized_light.scss +++ b/app/assets/stylesheets/highlight/solarized_light.scss @@ -1,15 +1,99 @@ /* https://gist.github.com/qguv/7936275 */ +/* +* Solarized light syntax colors +*/ +$solarized-light-matchline-bg: rgba(255, 255, 255, 0.4); +$solarized-light-new-bg: rgba(133, 153, 0, 0.2); +$solarized-light-new-idiff: rgba(133, 153, 0, 0.25); +$solarized-light-old-bg: rgba(220, 50, 47, 0.2); +$solarized-light-old-idiff: rgba(220, 50, 47, 0.25); +$solarized-light-border: #c5d0d4; +$solarized-light-pre-bg: #002b36; +$solarized-light-pre-bg: #fdf6e3; +$solarized-light-pre-color: #586e75; +$solarized-light-line-bg: #fdf6e3; +$solarized-light-highlight: #eee8d5; +$solarized-light-hll-bg: #ddd8c5; +$solarized-light-c: #93a1a1; +$solarized-light-err: #586e75; +$solarized-light-g: #586e75; +$solarized-light-k: #859900; +$solarized-light-l: #586e75; +$solarized-light-n: #586e75; +$solarized-light-o: #859900; +$solarized-light-x: #cb4b16; +$solarized-light-p: #586e75; +$solarized-light-cm: #93a1a1; +$solarized-light-cp: #859900; +$solarized-light-c1: #93a1a1; +$solarized-light-cs: #859900; +$solarized-light-gd: #2aa198; +$solarized-light-ge: #586e75; +$solarized-light-gr: #dc322f; +$solarized-light-gh: #cb4b16; +$solarized-light-gi: #859900; +$solarized-light-go: #586e75; +$solarized-light-gp: #586e75; +$solarized-light-gs: #586e75; +$solarized-light-gu: #cb4b16; +$solarized-light-gt: #586e75; +$solarized-light-kc: #cb4b16; +$solarized-light-kd: #268bd2; +$solarized-light-kn: #859900; +$solarized-light-kp: #859900; +$solarized-light-kr: #268bd2; +$solarized-light-kt: #dc322f; +$solarized-light-ld: #586e75; +$solarized-light-m: #2aa198; +$solarized-light-s: #2aa198; +$solarized-light-na: #586e75; +$solarized-light-nb: #b58900; +$solarized-light-nc: #268bd2; +$solarized-light-no: #cb4b16; +$solarized-light-nd: #268bd2; +$solarized-light-ni: #cb4b16; +$solarized-light-ne: #cb4b16; +$solarized-light-nf: #268bd2; +$solarized-light-nl: #586e75; +$solarized-light-nn: #586e75; +$solarized-light-nx: #586e75; +$solarized-light-py: #586e75; +$solarized-light-nt: #268bd2; +$solarized-light-nv: #268bd2; +$solarized-light-ow: #859900; +$solarized-light-w: #586e75; +$solarized-light-mf: #2aa198; +$solarized-light-mh: #2aa198; +$solarized-light-mi: #2aa198; +$solarized-light-mo: #2aa198; +$solarized-light-sb: #93a1a1; +$solarized-light-sc: #2aa198; +$solarized-light-sd: #586e75; +$solarized-light-s2: #2aa198; +$solarized-light-se: #cb4b16; +$solarized-light-sh: #586e75; +$solarized-light-si: #2aa198; +$solarized-light-sx: #2aa198; +$solarized-light-sr: #dc322f; +$solarized-light-s1: #2aa198; +$solarized-light-ss: #2aa198; +$solarized-light-bp: #268bd2; +$solarized-light-vc: #268bd2; +$solarized-light-vg: #268bd2; +$solarized-light-vi: #268bd2; +$solarized-light-il: #2aa198; + @mixin matchLine { color: $black-transparent; - background: rgba(255, 255, 255, 0.4); + background: $solarized-light-matchline-bg; } .code.solarized-light { // Line numbers .line-numbers, .diff-line-num { - background-color: #fdf6e3; + background-color: $solarized-light-line-bg; } .diff-line-num, @@ -20,14 +104,14 @@ // Code itself pre.code, .diff-line-num { - border-color: #c5d0d4; + border-color: $solarized-light-border; } &, pre.code, .line_holder .line_content { - background-color: #fdf6e3; - color: #586e75; + background-color: $solarized-light-pre-bg; + color: $solarized-light-pre-color; } // Diff line @@ -38,18 +122,19 @@ td.diff-line-num.hll:not(.empty-cell), td.line_content.hll:not(.empty-cell) { - background-color: #ddd8c5; - border-color: darken(#ddd8c5, 15%); + background-color: $solarized-light-hll-bg; + border-color: darken($solarized-light-hll-bg, 15%); } .diff-line-num.new, .line_content.new { - @include diff_background(rgba(133, 153, 0, 0.2), rgba(133, 153, 0, 0.25), #c5d0d4); + @include diff_background($solarized-light-new-bg, + $solarized-light-new-idiff, $solarized-light-border); } .diff-line-num.old, .line_content.old { - @include diff_background(rgba(220, 50, 47, 0.2), rgba(220, 50, 47, 0.25), #c5d0d4); + @include diff_background($solarized-light-old-bg, $solarized-light-old-idiff, $solarized-light-border); } .line_content.match { @@ -59,12 +144,12 @@ // highlight line via anchor pre .hll { - background-color: #ddd8c5 !important; + background-color: $solarized-light-hll-bg !important; } // Search result highlight span.highlight_word { - background-color: #eee8d5 !important; + background-color: $solarized-light-highlight !important; } /* Solarized Light @@ -85,72 +170,72 @@ green #859900 operators, other keywords */ - .c { color: #93a1a1; } /* Comment */ - .err { color: #586e75; } /* Error */ - .g { color: #586e75; } /* Generic */ - .k { color: #859900; } /* Keyword */ - .l { color: #586e75; } /* Literal */ - .n { color: #586e75; } /* Name */ - .o { color: #859900; } /* Operator */ - .x { color: #cb4b16; } /* Other */ - .p { color: #586e75; } /* Punctuation */ - .cm { color: #93a1a1; } /* Comment.Multiline */ - .cp { color: #859900; } /* Comment.Preproc */ - .c1 { color: #93a1a1; } /* Comment.Single */ - .cs { color: #859900; } /* Comment.Special */ - .gd { color: #2aa198; } /* Generic.Deleted */ - .ge { color: #586e75; font-style: italic; } /* Generic.Emph */ - .gr { color: #dc322f; } /* Generic.Error */ - .gh { color: #cb4b16; } /* Generic.Heading */ - .gi { color: #859900; } /* Generic.Inserted */ - .go { color: #586e75; } /* Generic.Output */ - .gp { color: #586e75; } /* Generic.Prompt */ - .gs { color: #586e75; font-weight: bold; } /* Generic.Strong */ - .gu { color: #cb4b16; } /* Generic.Subheading */ - .gt { color: #586e75; } /* Generic.Traceback */ - .kc { color: #cb4b16; } /* Keyword.Constant */ - .kd { color: #268bd2; } /* Keyword.Declaration */ - .kn { color: #859900; } /* Keyword.Namespace */ - .kp { color: #859900; } /* Keyword.Pseudo */ - .kr { color: #268bd2; } /* Keyword.Reserved */ - .kt { color: #dc322f; } /* Keyword.Type */ - .ld { color: #586e75; } /* Literal.Date */ - .m { color: #2aa198; } /* Literal.Number */ - .s { color: #2aa198; } /* Literal.String */ - .na { color: #586e75; } /* Name.Attribute */ - .nb { color: #b58900; } /* Name.Builtin */ - .nc { color: #268bd2; } /* Name.Class */ - .no { color: #cb4b16; } /* Name.Constant */ - .nd { color: #268bd2; } /* Name.Decorator */ - .ni { color: #cb4b16; } /* Name.Entity */ - .ne { color: #cb4b16; } /* Name.Exception */ - .nf { color: #268bd2; } /* Name.Function */ - .nl { color: #586e75; } /* Name.Label */ - .nn { color: #586e75; } /* Name.Namespace */ - .nx { color: #586e75; } /* Name.Other */ - .py { color: #586e75; } /* Name.Property */ - .nt { color: #268bd2; } /* Name.Tag */ - .nv { color: #268bd2; } /* Name.Variable */ - .ow { color: #859900; } /* Operator.Word */ - .w { color: #586e75; } /* Text.Whitespace */ - .mf { color: #2aa198; } /* Literal.Number.Float */ - .mh { color: #2aa198; } /* Literal.Number.Hex */ - .mi { color: #2aa198; } /* Literal.Number.Integer */ - .mo { color: #2aa198; } /* Literal.Number.Oct */ - .sb { color: #93a1a1; } /* Literal.String.Backtick */ - .sc { color: #2aa198; } /* Literal.String.Char */ - .sd { color: #586e75; } /* Literal.String.Doc */ - .s2 { color: #2aa198; } /* Literal.String.Double */ - .se { color: #cb4b16; } /* Literal.String.Escape */ - .sh { color: #586e75; } /* Literal.String.Heredoc */ - .si { color: #2aa198; } /* Literal.String.Interpol */ - .sx { color: #2aa198; } /* Literal.String.Other */ - .sr { color: #dc322f; } /* Literal.String.Regex */ - .s1 { color: #2aa198; } /* Literal.String.Single */ - .ss { color: #2aa198; } /* Literal.String.Symbol */ - .bp { color: #268bd2; } /* Name.Builtin.Pseudo */ - .vc { color: #268bd2; } /* Name.Variable.Class */ - .vg { color: #268bd2; } /* Name.Variable.Global */ - .vi { color: #268bd2; } /* Name.Variable.Instance */ - .il { color: #2aa198; } /* Literal.Number.Integer.Long */ + .c { color: $solarized-light-c; } /* Comment */ + .err { color: $solarized-light-err; } /* Error */ + .g { color: $solarized-light-g; } /* Generic */ + .k { color: $solarized-light-k; } /* Keyword */ + .l { color: $solarized-light-l; } /* Literal */ + .n { color: $solarized-light-n; } /* Name */ + .o { color: $solarized-light-o; } /* Operator */ + .x { color: $solarized-light-x; } /* Other */ + .p { color: $solarized-light-p; } /* Punctuation */ + .cm { color: $solarized-light-cm; } /* Comment.Multiline */ + .cp { color: $solarized-light-cp; } /* Comment.Preproc */ + .c1 { color: $solarized-light-c1; } /* Comment.Single */ + .cs { color: $solarized-light-cs; } /* Comment.Special */ + .gd { color: $solarized-light-gd; } /* Generic.Deleted */ + .ge { color: $solarized-light-ge; font-style: italic; } /* Generic.Emph */ + .gr { color: $solarized-light-gr; } /* Generic.Error */ + .gh { color: $solarized-light-gh; } /* Generic.Heading */ + .gi { color: $solarized-light-gi; } /* Generic.Inserted */ + .go { color: $solarized-light-go; } /* Generic.Output */ + .gp { color: $solarized-light-gp; } /* Generic.Prompt */ + .gs { color: $solarized-light-gs; font-weight: bold; } /* Generic.Strong */ + .gu { color: $solarized-light-gu; } /* Generic.Subheading */ + .gt { color: $solarized-light-gt; } /* Generic.Traceback */ + .kc { color: $solarized-light-kc; } /* Keyword.Constant */ + .kd { color: $solarized-light-kd; } /* Keyword.Declaration */ + .kn { color: $solarized-light-kn; } /* Keyword.Namespace */ + .kp { color: $solarized-light-kp; } /* Keyword.Pseudo */ + .kr { color: $solarized-light-kr; } /* Keyword.Reserved */ + .kt { color: $solarized-light-kt; } /* Keyword.Type */ + .ld { color: $solarized-light-ld; } /* Literal.Date */ + .m { color: $solarized-light-m; } /* Literal.Number */ + .s { color: $solarized-light-s; } /* Literal.String */ + .na { color: $solarized-light-na; } /* Name.Attribute */ + .nb { color: $solarized-light-nb; } /* Name.Builtin */ + .nc { color: $solarized-light-nc; } /* Name.Class */ + .no { color: $solarized-light-no; } /* Name.Constant */ + .nd { color: $solarized-light-nd; } /* Name.Decorator */ + .ni { color: $solarized-light-ni; } /* Name.Entity */ + .ne { color: $solarized-light-ne; } /* Name.Exception */ + .nf { color: $solarized-light-nf; } /* Name.Function */ + .nl { color: $solarized-light-nl; } /* Name.Label */ + .nn { color: $solarized-light-nn; } /* Name.Namespace */ + .nx { color: $solarized-light-nx; } /* Name.Other */ + .py { color: $solarized-light-py; } /* Name.Property */ + .nt { color: $solarized-light-nt; } /* Name.Tag */ + .nv { color: $solarized-light-nv; } /* Name.Variable */ + .ow { color: $solarized-light-ow; } /* Operator.Word */ + .w { color: $solarized-light-w; } /* Text.Whitespace */ + .mf { color: $solarized-light-mf; } /* Literal.Number.Float */ + .mh { color: $solarized-light-mh; } /* Literal.Number.Hex */ + .mi { color: $solarized-light-mi; } /* Literal.Number.Integer */ + .mo { color: $solarized-light-mo; } /* Literal.Number.Oct */ + .sb { color: $solarized-light-sb; } /* Literal.String.Backtick */ + .sc { color: $solarized-light-sc; } /* Literal.String.Char */ + .sd { color: $solarized-light-sd; } /* Literal.String.Doc */ + .s2 { color: $solarized-light-s2; } /* Literal.String.Double */ + .se { color: $solarized-light-se; } /* Literal.String.Escape */ + .sh { color: $solarized-light-sh; } /* Literal.String.Heredoc */ + .si { color: $solarized-light-si; } /* Literal.String.Interpol */ + .sx { color: $solarized-light-sx; } /* Literal.String.Other */ + .sr { color: $solarized-light-sr; } /* Literal.String.Regex */ + .s1 { color: $solarized-light-s1; } /* Literal.String.Single */ + .ss { color: $solarized-light-ss; } /* Literal.String.Symbol */ + .bp { color: $solarized-light-bp; } /* Name.Builtin.Pseudo */ + .vc { color: $solarized-light-vc; } /* Name.Variable.Class */ + .vg { color: $solarized-light-vg; } /* Name.Variable.Global */ + .vi { color: $solarized-light-vi; } /* Name.Variable.Instance */ + .il { color: $solarized-light-il; } /* Literal.Number.Integer.Long */ } diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss index d5367d5f3f0..1adab3ffd94 100644 --- a/app/assets/stylesheets/highlight/white.scss +++ b/app/assets/stylesheets/highlight/white.scss @@ -1,5 +1,72 @@ /* https://github.com/aahan/pygments-github-style */ +/* +* White Syntax Colors +*/ +$white-code-color: #333; +$white-highlight: #fafe3d; +$white-pre-hll-bg: #f8eec7; +$white-hll-bg: #f8f8f8; +$white-c: #998; +$white-err: #a61717; +$white-err-bg: #e3d2d2; +$white-cm: #998; +$white-cp: #999; +$white-c1: #998; +$white-cs: #999; +$white-gd: $black; +$white-gd-bg: #fdd; +$white-gd-x: $black; +$white-gd-x-bg: #faa; +$white-gr: #a00; +$white-gh: #999; +$white-gi: $black; +$white-gi-bg: #dfd; +$white-gi-x: $black; +$white-gi-x-bg: #afa; +$white-go: #888; +$white-gp: #555; +$white-gu: #800080; +$white-gt: #a00; +$white-kt: #458; +$white-m: #099; +$white-s: #d14; +$white-n: #333; +$white-na: teal; +$white-nb: #0086b3; +$white-nc: #458; +$white-no: teal; +$white-ni: purple; +$white-ne: #900; +$white-nf: #900; +$white-nn: #555; +$white-nt: navy; +$white-nv: teal; +$white-w: #bbb; +$white-mf: #099; +$white-mh: #099; +$white-mi: #099; +$white-mo: #099; +$white-sb: #d14; +$white-sc: #d14; +$white-sd: #d14; +$white-s2: #d14; +$white-se: #d14; +$white-sh: #d14; +$white-si: #d14; +$white-sx: #d14; +$white-sr: #009926; +$white-s1: #d14; +$white-ss: #990073; +$white-bp: #999; +$white-vc: teal; +$white-vg: teal; +$white-vi: teal; +$white-il: #099; +$white-gc-color: #999; +$white-gc-bg: #eaf2f5; + + @mixin matchLine { color: $black-transparent; background-color: $match-line; @@ -26,8 +93,8 @@ &, pre.code, .line_holder .line_content { - background-color: #fff; - color: #333; + background-color: $white-light; + color: $white-code-color; } // Diff line @@ -83,75 +150,75 @@ // highlight line via anchor pre .hll { - background-color: #f8eec7 !important; + background-color: $white-pre-hll-bg !important; } // Search result highlight span.highlight_word { - background-color: #fafe3d !important; + background-color: $white-highlight !important; } - .hll { background-color: #f8f8f8; } - .c { color: #998; font-style: italic; } - .err { color: #a61717; background-color: #e3d2d2; } + .hll { background-color: $white-hll-bg; } + .c { color: $white-c; font-style: italic; } + .err { color: $white-err; background-color: $white-err-bg; } .k { font-weight: bold; } .o { font-weight: bold; } - .cm { color: #998; font-style: italic; } - .cp { color: #999; font-weight: bold; } - .c1 { color: #998; font-style: italic; } - .cs { color: #999; font-weight: bold; font-style: italic; } - .gd { color: #000; background-color: #fdd; } - .gd .x { color: #000; background-color: #faa; } + .cm { color: $white-cm; font-style: italic; } + .cp { color: $white-cp; font-weight: bold; } + .c1 { color: $white-c1; font-style: italic; } + .cs { color: $white-cs; font-weight: bold; font-style: italic; } + .gd { color: $white-gd; background-color: $white-gd-bg; } + .gd .x { color: $white-gd-x; background-color: $white-gd-x-bg; } .ge { font-style: italic; } - .gr { color: #a00; } - .gh { color: #999; } - .gi { color: #000; background-color: #dfd; } - .gi .x { color: #000; background-color: #afa; } - .go { color: #888; } - .gp { color: #555; } + .gr { color: $white-gr; } + .gh { color: $white-gh; } + .gi { color: $white-gi; background-color: $white-gi-bg; } + .gi .x { color: $white-gi-x; background-color: $white-gi-x-bg; } + .go { color: $white-go; } + .gp { color: $white-gp; } .gs { font-weight: bold; } - .gu { color: #800080; font-weight: bold; } - .gt { color: #a00; } + .gu { color: $white-gu; font-weight: bold; } + .gt { color: $white-gt; } .kc { font-weight: bold; } .kd { font-weight: bold; } .kn { font-weight: bold; } .kp { font-weight: bold; } .kr { font-weight: bold; } - .kt { color: #458; font-weight: bold; } - .m { color: #099; } - .s { color: #d14; } - .n { color: #333; } - .na { color: teal; } - .nb { color: #0086b3; } - .nc { color: #458; font-weight: bold; } - .no { color: teal; } - .ni { color: purple; } - .ne { color: #900; font-weight: bold; } - .nf { color: #900; font-weight: bold; } - .nn { color: #555; } - .nt { color: navy; } - .nv { color: teal; } + .kt { color: $white-kt; font-weight: bold; } + .m { color: $white-m; } + .s { color: $white-s; } + .n { color: $white-n; } + .na { color: $white-na; } + .nb { color: $white-nb; } + .nc { color: $white-nc; font-weight: bold; } + .no { color: $white-no; } + .ni { color: $white-ni; } + .ne { color: $white-ne; font-weight: bold; } + .nf { color: $white-nf; font-weight: bold; } + .nn { color: $white-nn; } + .nt { color: $white-nt; } + .nv { color: $white-nv; } .ow { font-weight: bold; } - .w { color: #bbb; } - .mf { color: #099; } - .mh { color: #099; } - .mi { color: #099; } - .mo { color: #099; } - .sb { color: #d14; } - .sc { color: #d14; } - .sd { color: #d14; } - .s2 { color: #d14; } - .se { color: #d14; } - .sh { color: #d14; } - .si { color: #d14; } - .sx { color: #d14; } - .sr { color: #009926; } - .s1 { color: #d14; } - .ss { color: #990073; } - .bp { color: #999; } - .vc { color: teal; } - .vg { color: teal; } - .vi { color: teal; } - .il { color: #099; } - .gc { color: #999; background-color: #eaf2f5; } + .w { color: $white-w; } + .mf { color: $white-mf; } + .mh { color: $white-mh; } + .mi { color: $white-mi; } + .mo { color: $white-mo; } + .sb { color: $white-sb; } + .sc { color: $white-sc; } + .sd { color: $white-sd; } + .s2 { color: $white-s2; } + .se { color: $white-se; } + .sh { color: $white-sh; } + .si { color: $white-si; } + .sx { color: $white-sx; } + .sr { color: $white-sr; } + .s1 { color: $white-s1; } + .ss { color: $white-ss; } + .bp { color: $white-bp; } + .vc { color: $white-vc; } + .vg { color: $white-vg; } + .vi { color: $white-vi; } + .il { color: $white-il; } + .gc { color: $white-gc-color; background-color: $white-gc-bg; } } diff --git a/app/assets/stylesheets/mailers/devise.scss b/app/assets/stylesheets/mailers/devise.scss index b2bce482fde..9f613710cf4 100644 --- a/app/assets/stylesheets/mailers/devise.scss +++ b/app/assets/stylesheets/mailers/devise.scss @@ -1,3 +1,5 @@ +@import "framework/variables"; + // NOTE: This stylesheet is for the exclusive use of the `devise_mailer` layout // used for Devise email templates, and _should not_ be included in any // application stylesheets. @@ -46,7 +48,7 @@ table { &#body { background-color: $message-background-color; - border: 1px solid #000; + border: 1px solid $black; border-radius: 4px; margin: 0 auto; width: 600px; diff --git a/app/assets/stylesheets/mailers/highlighted_diff_email.scss b/app/assets/stylesheets/mailers/highlighted_diff_email.scss index 8d1a6020ca4..024b4df6bd0 100644 --- a/app/assets/stylesheets/mailers/highlighted_diff_email.scss +++ b/app/assets/stylesheets/mailers/highlighted_diff_email.scss @@ -10,8 +10,72 @@ // preference): plain class selectors, type (element name) selectors, or // explicit child selectors. +/* +* Highlighted Diff Email Syntax Colors +*/ +$highlighted-highlight-word: #fafe3d; +$highlighted-hll-bg: #f8f8f8; +$highlighted-c: #998; +$highlighted-err: #a61717; +$highlighted-err-bg: #e3d2d2; +$highlighted-cm: #998; +$highlighted-cp: #999; +$highlighted-c1: #998; +$highlighted-cs: #999; +$highlighted-gd: #000; +$highlighted-gd-bg: #fdd; +$highlighted-gd-x: #000; +$highlighted-gd-x-bg: #faa; +$highlighted-gr: #a00; +$highlighted-gh: #999; +$highlighted-gi: #000; +$highlighted-gi-bg: #dfd; +$highlighted-gi-x: #000; +$highlighted-gi-x-bg: #afa; +$highlighted-go: #888; +$highlighted-gp: #555; +$highlighted-gu: #800080; +$highlighted-gt: #a00; +$highlighted-kt: #458; +$highlighted-m: #099; +$highlighted-s: #d14; +$highlighted-n: #333; +$highlighted-na: teal; +$highlighted-nb: #0086b3; +$highlighted-nc: #458; +$highlighted-no: teal; +$highlighted-ni: purple; +$highlighted-ne: #900; +$highlighted-nf: #900; +$highlighted-nn: #555; +$highlighted-nt: navy; +$highlighted-nv: teal; +$highlighted-w: #bbb; +$highlighted-mf: #099; +$highlighted-mh: #099; +$highlighted-mi: #099; +$highlighted-mo: #099; +$highlighted-sb: #d14; +$highlighted-sc: #d14; +$highlighted-sd: #d14; +$highlighted-s2: #d14; +$highlighted-se: #d14; +$highlighted-sh: #d14; +$highlighted-si: #d14; +$highlighted-sx: #d14; +$highlighted-sr: #009926; +$highlighted-s1: #d14; +$highlighted-ss: #990073; +$highlighted-bp: #999; +$highlighted-vc: teal; +$highlighted-vg: teal; +$highlighted-vi: teal; +$highlighted-il: #099; +$highlighted-gc: #999; +$highlighted-gc-bg: #eaf2f5; + .code { - background-color: #fff; + background-color: $white-light; font-family: monospace; font-size: $code_font_size; -premailer-cellpadding: 0; @@ -75,69 +139,69 @@ pre { } span.highlight_word { - background-color: #fafe3d !important; + background-color: $highlighted-highlight-word !important; } -.hll { background-color: #f8f8f8; } -.c { color: #998; font-style: italic; } -.err { color: #a61717; background-color: #e3d2d2; } +.hll { background-color: $highlighted-hll-bg; } +.c { color: $highlighted-c; font-style: italic; } +.err { color: $highlighted-err; background-color: $highlighted-err-bg; } .k { font-weight: bold; } .o { font-weight: bold; } -.cm { color: #998; font-style: italic; } -.cp { color: #999; font-weight: bold; } -.c1 { color: #998; font-style: italic; } -.cs { color: #999; font-weight: bold; font-style: italic; } -.gd { color: #000; background-color: #fdd; } -.gd .x { color: #000; background-color: #faa; } +.cm { color: $highlighted-cm; font-style: italic; } +.cp { color: $highlighted-cp; font-weight: bold; } +.c1 { color: $highlighted-c1; font-style: italic; } +.cs { color: $highlighted-cs; font-weight: bold; font-style: italic; } +.gd { color: $highlighted-gd; background-color: $highlighted-gd-bg; } +.gd .x { color: $highlighted-gd; background-color: $highlighted-gd-x-bg; } .ge { font-style: italic; } -.gr { color: #a00; } -.gh { color: #999; } -.gi { color: #000; background-color: #dfd; } -.gi .x { color: #000; background-color: #afa; } -.go { color: #888; } -.gp { color: #555; } +.gr { color: $highlighted-gr; } +.gh { color: $highlighted-gh; } +.gi { color: $highlighted-gi; background-color: $highlighted-gi-bg; } +.gi .x { color: $highlighted-gi; background-color: $highlighted-gi-x-bg; } +.go { color: $highlighted-go; } +.gp { color: $highlighted-gp; } .gs { font-weight: bold; } -.gu { color: #800080; font-weight: bold; } -.gt { color: #a00; } +.gu { color: $highlighted-gu; font-weight: bold; } +.gt { color: $highlighted-gt; } .kc { font-weight: bold; } .kd { font-weight: bold; } .kn { font-weight: bold; } .kp { font-weight: bold; } .kr { font-weight: bold; } -.kt { color: #458; font-weight: bold; } -.m { color: #099; } -.s { color: #d14; } -.n { color: #333; } -.na { color: teal; } -.nb { color: #0086b3; } -.nc { color: #458; font-weight: bold; } -.no { color: teal; } -.ni { color: purple; } -.ne { color: #900; font-weight: bold; } -.nf { color: #900; font-weight: bold; } -.nn { color: #555; } -.nt { color: navy; } -.nv { color: teal; } +.kt { color: $highlighted-kt; font-weight: bold; } +.m { color: $highlighted-m; } +.s { color: $highlighted-s; } +.n { color: $highlighted-n; } +.na { color: $highlighted-na; } +.nb { color: $highlighted-nb; } +.nc { color: $highlighted-nc; font-weight: bold; } +.no { color: $highlighted-no; } +.ni { color: $highlighted-ni; } +.ne { color: $highlighted-ne; font-weight: bold; } +.nf { color: $highlighted-nf; font-weight: bold; } +.nn { color: $highlighted-nn; } +.nt { color: $highlighted-nt; } +.nv { color: $highlighted-nv; } .ow { font-weight: bold; } -.w { color: #bbb; } -.mf { color: #099; } -.mh { color: #099; } -.mi { color: #099; } -.mo { color: #099; } -.sb { color: #d14; } -.sc { color: #d14; } -.sd { color: #d14; } -.s2 { color: #d14; } -.se { color: #d14; } -.sh { color: #d14; } -.si { color: #d14; } -.sx { color: #d14; } -.sr { color: #009926; } -.s1 { color: #d14; } -.ss { color: #990073; } -.bp { color: #999; } -.vc { color: teal; } -.vg { color: teal; } -.vi { color: teal; } -.il { color: #099; } -.gc { color: #999; background-color: #eaf2f5; } +.w { color: $highlighted-w; } +.mf { color: $highlighted-mf; } +.mh { color: $highlighted-mh; } +.mi { color: $highlighted-mi; } +.mo { color: $highlighted-mo; } +.sb { color: $highlighted-sb; } +.sc { color: $highlighted-sc; } +.sd { color: $highlighted-sd; } +.s2 { color: $highlighted-s2; } +.se { color: $highlighted-se; } +.sh { color: $highlighted-sh; } +.si { color: $highlighted-si; } +.sx { color: $highlighted-sx; } +.sr { color: $highlighted-sr; } +.s1 { color: $highlighted-s1; } +.ss { color: $highlighted-ss; } +.bp { color: $highlighted-bp; } +.vc { color: $highlighted-vc; } +.vg { color: $highlighted-vg; } +.vi { color: $highlighted-vi; } +.il { color: $highlighted-il; } +.gc { color: $highlighted-gc; background-color: $highlighted-gc-bg; } diff --git a/app/assets/stylesheets/notify.scss b/app/assets/stylesheets/notify.scss index ced8c4a9907..ddc382362f7 100644 --- a/app/assets/stylesheets/notify.scss +++ b/app/assets/stylesheets/notify.scss @@ -1,3 +1,5 @@ +@import "framework/variables"; + img { max-width: 100%; height: auto; @@ -5,12 +7,12 @@ img { p.details { font-style: italic; - color: #777; + color: $notify-details; } .footer > p { font-size: small; - color: #777; + color: $notify-footer; } pre.commit-message { @@ -21,10 +23,10 @@ pre.commit-message { text-decoration: none; > .new-file { - color: #090; + color: $notify-new-file; } > .deleted-file { - color: #b00; + color: $notify-deleted-file; } } diff --git a/app/assets/stylesheets/pages/admin.scss b/app/assets/stylesheets/pages/admin.scss index 14812e171fd..291372b88e3 100644 --- a/app/assets/stylesheets/pages/admin.scss +++ b/app/assets/stylesheets/pages/admin.scss @@ -31,7 +31,7 @@ .form-actions { padding-left: 130px; - background: #fff; + background: $white-light; } .visibility-levels { diff --git a/app/assets/stylesheets/pages/awards.scss b/app/assets/stylesheets/pages/awards.scss index 486ad16ea26..dce5c31f282 100644 --- a/app/assets/stylesheets/pages/awards.scss +++ b/app/assets/stylesheets/pages/awards.scss @@ -15,7 +15,7 @@ background-color: $award-emoji-menu-bg; border: 1px solid $award-emoji-menu-border; border-radius: $border-radius-base; - box-shadow: 0 6px 12px rgba(0,0,0,.175); + box-shadow: 0 6px 12px $award-emoji-menu-shadow; pointer-events: none; opacity: 0; transform: scale(.2); diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index 4327f8bf640..0d9cf679e7c 100644 --- a/app/assets/stylesheets/pages/boards.scss +++ b/app/assets/stylesheets/pages/boards.scss @@ -145,7 +145,7 @@ .board-blank-state { height: calc(100% - 49px); padding: $gl-padding; - background-color: #fff; + background-color: $white-light; } .board-blank-state-list { @@ -191,9 +191,9 @@ .card { position: relative; padding: 10px $gl-padding; - background: #fff; + background: $white-light; border-radius: $border-radius-default; - box-shadow: 0 1px 2px rgba(186, 186, 186, 0.5); + box-shadow: 0 1px 2px $issue-boards-card-shadow; list-style: none; &:not(:last-child) { @@ -325,7 +325,6 @@ } .issuable-header-text { - width: 100%; padding-right: 35px; > strong { diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index 48f11eb2552..842c0434bf2 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -1,7 +1,7 @@ .build-page { pre.trace { - background: #111; - color: #fff; + background: $builds-trace-bg; + color: $white-light; font-family: $monospace_font; white-space: pre-wrap; overflow: auto; diff --git a/app/assets/stylesheets/pages/ci_projects.scss b/app/assets/stylesheets/pages/ci_projects.scss index 87c453a7a27..d1cd1e5d848 100644 --- a/app/assets/stylesheets/pages/ci_projects.scss +++ b/app/assets/stylesheets/pages/ci_projects.scss @@ -1,7 +1,7 @@ .ci-body { .project-title { margin: 0; - color: #444; + color: $common-gray-dark; font-size: 20px; line-height: 1.5; } diff --git a/app/assets/stylesheets/pages/commit.scss b/app/assets/stylesheets/pages/commit.scss index ddc9d0e2b1a..bf656d0e28e 100644 --- a/app/assets/stylesheets/pages/commit.scss +++ b/app/assets/stylesheets/pages/commit.scss @@ -5,7 +5,7 @@ .commit-author, .commit-committer { display: block; - color: #999; + color: $commit-committer-color; font-weight: normal; font-style: italic; } @@ -113,17 +113,17 @@ overflow: hidden; // See https://gitlab.com/gitlab-org/gitlab-ce/issues/13987 .max-width-marker { width: 72ch; - color: rgba(0, 0, 0, 0.0); + color: $commit-max-width-marker-color; font-family: inherit; left: $left; height: 100%; - border-right: 1px solid mix($input-border, white); + border-right: 1px solid mix($input-border, $white-light); position: absolute; z-index: 1; } > textarea { - background-color: rgba(0, 0, 0, 0.0); + background-color: $commit-message-text-area-bg; font-family: inherit; padding-left: $left; position: relative; diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss index 83ffa0e1d39..c29b5fdea78 100644 --- a/app/assets/stylesheets/pages/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -8,8 +8,8 @@ .commit-header { padding: 5px 10px; background-color: $background-color; - border-top: 1px solid #eee; - border-bottom: 1px solid #eee; + border-top: 1px solid $gray-darker; + border-bottom: 1px solid $gray-darker; font-size: 14px; &:first-child { @@ -94,7 +94,7 @@ } &:not(:last-child) { - border-bottom: 1px solid #eee; + border-bottom: 1px solid $gray-darker; } a, @@ -201,7 +201,7 @@ .bar { position: absolute; height: 4px; - background-color: #ccc; + background-color: $divergence-graph-bar-bg; } .bar-behind { @@ -218,7 +218,7 @@ padding-top: 6px; padding-bottom: 0; font-size: 12px; - color: #333; + color: $gl-title-color; display: block; } @@ -239,6 +239,6 @@ height: 18px; margin: 5px 0 0; float: left; - background-color: #ccc; + background-color: $divergence-graph-separator-bg; } } diff --git a/app/assets/stylesheets/pages/confirmation.scss b/app/assets/stylesheets/pages/confirmation.scss index 81e5cee240d..8aab5e8231d 100644 --- a/app/assets/stylesheets/pages/confirmation.scss +++ b/app/assets/stylesheets/pages/confirmation.scss @@ -1,6 +1,6 @@ .well-confirmation { margin-bottom: 20px; - border-bottom: 1px solid #eee; + border-bottom: 1px solid $gray-darker; > h1, h2, diff --git a/app/assets/stylesheets/pages/cycle_analytics.scss b/app/assets/stylesheets/pages/cycle_analytics.scss index 498a8f68e49..e7a2c91003f 100644 --- a/app/assets/stylesheets/pages/cycle_analytics.scss +++ b/app/assets/stylesheets/pages/cycle_analytics.scss @@ -53,7 +53,7 @@ border-bottom: none; position: relative; - @media (max-width: $screen-sm-min) { + @media (max-width: $screen-xs-max) { padding: 6px 0 24px; } } @@ -61,7 +61,7 @@ .column { text-align: center; - @media (max-width: $screen-sm-min) { + @media (max-width: $screen-xs-max) { padding: 15px 0; } @@ -78,7 +78,7 @@ } &:last-child { - @media (max-width: $screen-sm-min) { + @media (max-width: $screen-xs-max) { text-align: center; } } @@ -141,9 +141,9 @@ .dismiss-icon { position: absolute; - right: $cycle-analytics-box-padding; + right: $cycle-analytics-dismiss-icon-color; cursor: pointer; - color: #b2b2b2; + color: $cycle-analytics-dismiss-icon-color; } .svg-container { @@ -156,7 +156,7 @@ } .inner-content { - @media (max-width: $screen-sm-min) { + @media (max-width: $screen-xs-max) { padding: 0 28px; text-align: center; } diff --git a/app/assets/stylesheets/pages/dashboard.scss b/app/assets/stylesheets/pages/dashboard.scss index 016bab104eb..4421ed6a0b9 100644 --- a/app/assets/stylesheets/pages/dashboard.scss +++ b/app/assets/stylesheets/pages/dashboard.scss @@ -32,7 +32,7 @@ margin-bottom: 15px; i { - color: #888; + color: $dashboard-project-access-icon-color; } } diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss index 0f0c0abe7ae..80baebd5ea3 100644 --- a/app/assets/stylesheets/pages/detail_page.scss +++ b/app/assets/stylesheets/pages/detail_page.scss @@ -1,16 +1,16 @@ .detail-page-header { padding: $gl-padding-top 0; border-bottom: 1px solid $border-color; - color: #5c5d5e; + color: $gl-text-color-dark; font-size: 16px; line-height: 34px; .author { - color: #5c5d5e; + color: $gl-text-color-dark; } .identifier { - color: #5c5d5e; + color: $gl-text-color-dark; } .issue_created_ago, diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 99fdea15218..737f6e0f4be 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -14,7 +14,7 @@ background: $background-color; border-bottom: 1px solid $border-color; padding: 10px 16px; - color: #555; + color: $gl-diff-text-color; z-index: 10; border-radius: 3px 3px 0 0; @@ -24,7 +24,7 @@ display: block; .file-mode { - color: #777; + color: $file-mode-changed; } } @@ -49,8 +49,8 @@ .diff-content { overflow: auto; overflow-y: hidden; - background: #fff; - color: #333; + background: $white-light; + color: $gl-title-color; border-radius: 0 0 3px 3px; .unfold { @@ -59,7 +59,7 @@ .file-mode-changed { padding: 10px; - color: #777; + color: $file-mode-changed; } .suppressed-container { @@ -172,7 +172,7 @@ } .image { - background: #ddd; + background: $diff-image-bg; text-align: center; padding: 30px; @@ -182,13 +182,13 @@ .frame { display: inline-block; - background-color: #fff; + background-color: $white-light; line-height: 0; img { - border: 1px solid #fff; - background-image: linear-gradient(45deg, #e5e5e5 25%, transparent 25%, transparent 75%, #e5e5e5 75%, #e5e5e5 100%), - linear-gradient(45deg, #e5e5e5 25%, transparent 25%, transparent 75%, #e5e5e5 75%, #e5e5e5 100%); + border: 1px solid $white-light; + background-image: linear-gradient(45deg, $diff-image-img-bg 25%, transparent 25%, transparent 75%, $diff-image-img-bg 75%, $diff-image-img-bg 100%), + linear-gradient(45deg, $diff-image-img-bg 25%, transparent 25%, transparent 75%, $diff-image-img-bg 75%, $diff-image-img-bg 100%); background-size: 10px 10px; background-position: 0 0, 5px 5px; max-width: 100%; @@ -206,7 +206,7 @@ .image-info { font-size: 12px; margin: 5px 0 0; - color: grey; + color: $diff-image-info-color; } .view.swipe { @@ -220,7 +220,7 @@ .swipe-wrap { overflow: hidden; - border-left: 1px solid #999; + border-left: 1px solid $diff-swipe-border; position: absolute; display: block; top: 13px; @@ -350,7 +350,7 @@ .view-modes { padding: 10px; text-align: center; - background: #eee; + background: $gray-darker; ul, li { @@ -361,8 +361,8 @@ } li { - color: grey; - border-left: 1px solid #c1c1c1; + color: $diff-view-modes-color; + border-left: 1px solid $diff-view-modes-border; padding: 0 12px 0 16px; cursor: pointer; @@ -380,7 +380,7 @@ } cursor: default; - color: #333; + color: $gl-title-color; } &.disabled { diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss index 778126bcfb7..6cde9c592de 100644 --- a/app/assets/stylesheets/pages/editor.scss +++ b/app/assets/stylesheets/pages/editor.scss @@ -14,10 +14,10 @@ } .cancel-btn { - color: #b94a48; + color: $editor-cancel-color; &:hover { - color: #b94a48; + color: $editor-cancel-color; } } diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss index 4b382e8adaf..de3d2ba549f 100644 --- a/app/assets/stylesheets/pages/environments.scss +++ b/app/assets/stylesheets/pages/environments.scss @@ -8,7 +8,7 @@ font-size: 34px; } -@media (max-width: $screen-sm-min) { +@media (max-width: $screen-xs-max) { .environments-container { width: 100%; overflow: auto; diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss index 3004959ff7b..dc67d411c71 100644 --- a/app/assets/stylesheets/pages/events.scss +++ b/app/assets/stylesheets/pages/events.scss @@ -62,7 +62,7 @@ border: none; background: $gray-light; border-radius: 0; - color: #777; + color: $events-pre-color; margin: 0 20px; overflow: hidden; } @@ -80,7 +80,7 @@ } .event-note-icon { - color: #777; + color: $events-pre-color; float: left; font-size: $gl-font-size; line-height: 16px; @@ -91,7 +91,7 @@ .event_icon { position: relative; float: right; - border: 1px solid #eee; + border: 1px solid $gray-darker; padding: 5px; border-radius: 5px; background: $gray-light; @@ -170,7 +170,7 @@ .event-body { margin: 0; - border-left: 2px solid #ddd; + border-left: 2px solid $events-body-border; padding-left: 10px; } @@ -186,4 +186,3 @@ display: none; } } - diff --git a/app/assets/stylesheets/pages/graph.scss b/app/assets/stylesheets/pages/graph.scss index f7f9a9bb770..84da9180f93 100644 --- a/app/assets/stylesheets/pages/graph.scss +++ b/app/assets/stylesheets/pages/graph.scss @@ -2,15 +2,15 @@ border: 1px solid $border-color; .controls { - color: #888; + color: $project-network-controls-color; font-size: 14px; padding: 5px; border-bottom: 1px solid $border-color; - background: #eee; + background: $gray-darker; } .network-graph { - background: #fff; + background: $white-light; height: 500px; overflow-y: scroll; overflow-x: hidden; @@ -20,15 +20,14 @@ .graphs { .graph-author-email { float: right; - color: #777; + color: $graph-author-email-color; } .graph-additions { - color: #4a2; + color: $gl-text-green; } .graph-deletions { - color: #d12f19; + color: $gl-text-red; } } - diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss index 57d028cec8c..a9af7af59e2 100644 --- a/app/assets/stylesheets/pages/groups.scss +++ b/app/assets/stylesheets/pages/groups.scss @@ -49,14 +49,14 @@ padding: 50px 100px; overflow: hidden; - @media (max-width: $screen-md-min) { + @media (max-width: $screen-sm-max) { padding: 50px 0; } svg { float: right; - @media (max-width: $screen-md-min) { + @media (max-width: $screen-sm-max) { float: none; display: block; width: 250px; @@ -71,7 +71,7 @@ width: 460px; margin-top: 120px; - @media (max-width: $screen-md-min) { + @media (max-width: $screen-sm-max) { float: none; margin-top: 60px; width: auto; diff --git a/app/assets/stylesheets/pages/help.scss b/app/assets/stylesheets/pages/help.scss index a48b4c65db8..e2e644dc23b 100644 --- a/app/assets/stylesheets/pages/help.scss +++ b/app/assets/stylesheets/pages/help.scss @@ -9,7 +9,7 @@ li { line-height: 24px; - color: #888; + color: $document-index-color; a { margin-right: 3px; @@ -20,7 +20,7 @@ .shortcut-mappings { font-size: 12px; - color: #555; + color: $help-shortcut-mapping-color; tbody:first-child tr:first-child { padding-top: 0; @@ -29,7 +29,7 @@ th { padding-top: 15px; line-height: 1.5; - color: #333; + color: $help-shortcut-header-color; text-align: left; } @@ -42,7 +42,7 @@ .shortcut { padding-right: 10px; - color: #999; + color: $help-shortcut-color; text-align: right; white-space: nowrap; } diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 7aad99eee4e..90587b9425b 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -233,7 +233,7 @@ width: 100%; text-align: center; padding-bottom: 10px; - color: #999; + color: $issuable-sidebar-color; &:hover { color: $gl-gray; @@ -249,12 +249,12 @@ } .avatar:hover { - border-color: #999; + border-color: $issuable-avatar-hover-border; } .btn-clipboard { border: none; - color: #999; + color: $issuable-clipboard-color; &:hover { background: transparent; diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index eb171195309..3b47f99df2c 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -82,18 +82,18 @@ ul.related-merge-requests > li { .merge-request, .issue { &.today { - background: #f3fff2; - border-color: #e1e8d5; + background: $issues-today-bg; + border-color: $issues-today-border; } &.closed { background: $gray-light; - border-color: #e5e5e5; + border-color: $issues-border; } &.merged { background: $gray-light; - border-color: #e5e5e5; + border-color: $issues-border; } } diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index e39ce19f846..b1ccd644450 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -198,7 +198,7 @@ } .label-remove { - border-left: 1px solid rgba(0, 0, 0, .1); + border-left: 1px solid $label-remove-border; z-index: 3; } diff --git a/app/assets/stylesheets/pages/lint.scss b/app/assets/stylesheets/pages/lint.scss index 8290519dc25..8d30bd64278 100644 --- a/app/assets/stylesheets/pages/lint.scss +++ b/app/assets/stylesheets/pages/lint.scss @@ -1,11 +1,11 @@ .ci-body { .incorrect-syntax { font-size: 19px; - color: red; + color: $lint-incorrect-color; } .correct-syntax { font-size: 19px; - color: #47a447; + color: $lint-correct-color; } } diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index 54c89d75e94..dd27a06fcd2 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -18,7 +18,7 @@ p { font-size: 18px; - color: #888; + color: $login-brand-holder-color; } h1:first-child { @@ -174,7 +174,7 @@ .form-control { &:active, &:focus { - background-color: #fff; + background-color: $white-light; } } @@ -195,7 +195,7 @@ h2 { margin-top: 0; font-size: 14px; - color: #a00; + color: $login-devise-error-color; } } } @@ -254,4 +254,3 @@ } } } - diff --git a/app/assets/stylesheets/pages/merge_conflicts.scss b/app/assets/stylesheets/pages/merge_conflicts.scss index 19ab198c2e7..7a90713dd3f 100644 --- a/app/assets/stylesheets/pages/merge_conflicts.scss +++ b/app/assets/stylesheets/pages/merge_conflicts.scss @@ -1,3 +1,5 @@ +// Disabled to use the color map for creating color schemes +// scss-lint:disable ColorVariable $colors: ( white_header_head_neutral : #e1fad7, white_line_head_neutral : #effdec, @@ -98,6 +100,7 @@ $colors: ( solarized_dark_header_not_chosen : rgba(#839496, .25), solarized_dark_line_not_chosen : rgba(#839496, .15) ); +// scss-lint:enable ColorVariable @mixin color-scheme($color) { @@ -228,14 +231,15 @@ $colors: ( position: absolute; right: 10px; padding: 0; - color: #fff; + outline: none; + color: $white-light; width: 75px; // static width to make 2 buttons have same width height: 19px; } } .btn-success .fa-spinner { - color: #fff; + color: $white-light; } .editor-wrap { diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index da1187af41c..1c6fe7afe14 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -86,7 +86,7 @@ } .normal { - color: #5c5d5e; + color: $gl-text-color-dark; } .js-deployment-link { @@ -143,7 +143,7 @@ } .mr-widget-footer { - border-top: 1px solid #eee; + border-top: 1px solid $gray-darker; } .ci-coverage { diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss index 8843d1463db..dfc6079bd15 100644 --- a/app/assets/stylesheets/pages/milestone.scss +++ b/app/assets/stylesheets/pages/milestone.scss @@ -123,7 +123,7 @@ padding: 20px 0; } -@media (max-width: $screen-sm-min) { +@media (max-width: $screen-xs-max) { .milestone-actions { @include clearfix(); padding-top: $gl-vert-padding; diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index 16ddef481bd..ff092d53845 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -111,7 +111,7 @@ text-align: center; font-size: 13px; - @media (max-width: $screen-md-min) { + @media (max-width: $screen-sm-max) { // On smaller devices the warning becomes the fourth item in the list, // rather than centering, and grows to span the full width of the // comment area. @@ -132,7 +132,7 @@ font-size: 15px; .md-area { - background-color: #fff; + background-color: $white-light; } } diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index e66c1f8d072..16b099c09eb 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -3,9 +3,9 @@ */ @-webkit-keyframes targe3-note { - from { background: #fffff0; } - 50% { background: #ffffd3; } - to { background: #fffff0; } + from { background: $note-targe3-outside; } + 50% { background: $note-targe3-inside; } + to { background: $note-targe3-outside; } } ul.notes { @@ -90,14 +90,14 @@ ul.notes { } &.system-note-commit-list { - max-height: 63px; + max-height: 70px; overflow: hidden; display: block; ul { - margin: 3px 0 3px 15px !important; + margin: 3px 0 3px 16px !important; - li { + .gfm-commit { font-family: $monospace_font; font-size: 12px; } @@ -172,6 +172,10 @@ ul.notes { &.timeline-entry { padding: 14px 10px; } + + .system-note { + padding: 0; + } } &.is-editting { @@ -249,7 +253,7 @@ ul.notes { } .page-sidebar-pinned.right-sidebar-expanded { - @media (max-width: $screen-lg-min) { + @media (max-width: $screen-md-max) { .note-header { .note-headline-light { display: block; @@ -301,7 +305,7 @@ ul.notes { &.notes_line2 { text-align: center; padding: 10px 0; - border-left: 1px solid #ddd !important; + border-left: 1px solid $note-line2-border !important; } &.notes_content { @@ -467,7 +471,7 @@ ul.notes { .add-diff-note { margin-top: -4px; border-radius: 40px; - background: #fff; + background: $white-light; padding: 4px; font-size: 16px; color: $gl-link-color; @@ -480,7 +484,7 @@ ul.notes { &:hover { background: $gl-info; - color: #fff; + color: $white-light; @include show-add-diff-note; } } diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss index 6fab97a71aa..f8677f93fe0 100644 --- a/app/assets/stylesheets/pages/profile.scss +++ b/app/assets/stylesheets/pages/profile.scss @@ -180,7 +180,7 @@ .modal-dialog { width: 380px; - @media (max-width: $screen-sm-min) { + @media (max-width: $screen-xs-max) { width: auto; } @@ -261,4 +261,4 @@ table.u2f-registrations { td:not(:last-child) { border-right: solid 1px transparent; } -}
\ No newline at end of file +} diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 0562ee7b178..62862c72b3b 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -8,7 +8,7 @@ .no-ssh-key-message, .project-limit-message { - background-color: #f28d35; + background-color: $project-limit-message-bg; margin-bottom: 0; } @@ -76,7 +76,7 @@ &.static-namespace { height: 35px; border-radius: 3px; - border: 1px solid #e5e5e5; + border: 1px solid $border-color; } &+ .select2 a { @@ -189,7 +189,7 @@ } .download-button { - @media (max-width: $screen-lg-min) { + @media (max-width: $screen-md-max) { margin-left: 0; } } @@ -225,7 +225,7 @@ left: 0; margin-top: -6px; border-width: 7px 5px 7px 0; - border-right-color: #dce0e5; + border-right-color: $count-arrow-border; pointer-events: none; } @@ -240,7 +240,7 @@ left: 1px; margin-top: -9px; border-width: 10px 7px 10px 0; - border-right-color: #fff; + border-right-color: $white-light; pointer-events: none; } } @@ -248,7 +248,7 @@ .count { @include btn-gray; display: inline-block; - background: white; + background: $white-light; border-radius: 2px; border-width: 1px; border-style: solid; @@ -270,7 +270,7 @@ } &:hover { - background: #fff; + background: $white-light; } } } @@ -302,7 +302,7 @@ .option-descr { margin-left: 29px; - color: #54565b; + color: $project-option-descr-color; } } } @@ -310,7 +310,7 @@ .save-project-loader { margin-top: 50px; margin-bottom: 50px; - color: #555; + color: $save-project-loader-color; } .transfer-project .select2-container { @@ -373,7 +373,7 @@ a.deploy-project-label { > li + li::before { padding: 0 3px; - color: #999; + color: $project-breadcrumb-color; } a { @@ -549,20 +549,20 @@ a.deploy-project-label { } pre.light-well { - border-color: #f1f1f1; + border-color: $well-light-border; } .git-empty { margin: 0 7px 7px; h5 { - color: #5c5d5e; + color: $gl-text-color-dark; } .light-well { border-radius: 2px; - color: #5b6169; + color: $well-light-text-color; font-size: 13px; line-height: 1.6em; } @@ -716,7 +716,7 @@ pre.light-well { .form-control { @extend .monospace; - background: #fff; + background: $white-light; font-size: 14px; margin-left: -1px; cursor: auto; @@ -726,17 +726,17 @@ pre.light-well { .cannot-be-merged, .cannot-be-merged:hover { - color: #e62958; + color: $error-exclamation-point; margin-top: 2px; } .private-forks-notice .private-fork-icon { i:nth-child(1) { - color: #2aa056; + color: $project-private-forks-notice-odd; } i:nth-child(2) { - color: #fff; + color: $white-light; } } diff --git a/app/assets/stylesheets/pages/runners.scss b/app/assets/stylesheets/pages/runners.scss index 7b3878c91df..9b6ff237557 100644 --- a/app/assets/stylesheets/pages/runners.scss +++ b/app/assets/stylesheets/pages/runners.scss @@ -1,27 +1,27 @@ .runner-state { padding: 6px 12px; margin-right: 10px; - color: #fff; + color: $white-light; &.runner-state-shared { - background: #32b186; + background: $runner-state-shared-bg; } &.runner-state-specific { - background: #3498db; + background: $runner-state-specific-bg; } } .runner-status-online { - color: green; + color: $runner-status-online-color; } .runner-status-offline { - color: gray; + color: $runner-status-offline-color; } .runner-status-paused { - color: red; + color: $runner-status-paused-color; } .runner { diff --git a/app/assets/stylesheets/pages/stat_graph.scss b/app/assets/stylesheets/pages/stat_graph.scss index 69288b31cc4..dfa4d033fb8 100644 --- a/app/assets/stylesheets/pages/stat_graph.scss +++ b/app/assets/stylesheets/pages/stat_graph.scss @@ -1,16 +1,16 @@ .tint-box { - background: #f3f3f3; + background: $stat-graph-common-bg; position: relative; margin-bottom: 10px; } .area { - fill: #1db34f; + fill: $stat-graph-area-fill; fill-opacity: 0.5; } .axis { - fill: #aaa; + fill: $stat-graph-axis-fill; font-size: 10px; } @@ -37,26 +37,26 @@ @include make-md-column(6); margin-top: 10px; - @media (max-width: $screen-sm-min) { + @media (max-width: $screen-xs-max) { width: 100%; } } .person .spark { display: block; - background: #f3f3f3; + background: $stat-graph-common-bg; width: 100%; } .person .area-contributor { - fill: #f17f49; + fill: $stat-graph-orange-fill; } } .selection rect { - fill: #333; + fill: $stat-graph-selection-fill; fill-opacity: 0.1; - stroke: #333; + stroke: $stat-graph-selection-stroke; stroke-width: 1px; stroke-opacity: 0.4; shape-rendering: crispedges; diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss index 4c258bae1f4..5084b466722 100644 --- a/app/assets/stylesheets/pages/status.scss +++ b/app/assets/stylesheets/pages/status.scss @@ -2,7 +2,7 @@ .ci-status { padding: 2px 7px; margin-right: 10px; - border: 1px solid #eee; + border: 1px solid $gray-darker; white-space: nowrap; border-radius: 4px; diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss index b3aef2fdd32..508b30f3947 100644 --- a/app/assets/stylesheets/pages/todos.scss +++ b/app/assets/stylesheets/pages/todos.scss @@ -11,7 +11,7 @@ background: $todo-alert-blue; margin-left: -17px; font-size: 11px; - color: white; + color: $white-light; padding: 3px; padding-top: 1px; padding-bottom: 1px; @@ -81,7 +81,7 @@ word-wrap: break-word; .md { - color: #7f8fa4; + color: $gl-grayish-blue; font-size: $gl-font-size; .label { @@ -90,7 +90,7 @@ } p { - color: #5c5d5e; + color: $gl-text-color-dark; } } @@ -102,7 +102,7 @@ border: none; background: $gray-light; border-radius: 0; - color: #777; + color: $todo-body-pre-color; margin: 0 20px; overflow: hidden; } @@ -146,7 +146,7 @@ .todo-body { margin: 0; - border-left: 2px solid #ddd; + border-left: 2px solid $todo-body-border; padding-left: 10px; } } diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss index 2b836fa1f4a..20ad63be045 100644 --- a/app/assets/stylesheets/pages/tree.scss +++ b/app/assets/stylesheets/pages/tree.scss @@ -31,7 +31,7 @@ .last-commit { @include str-truncated(506px); - @media (min-width: $screen-sm-max) and (max-width: $screen-md-max) { + @media (min-width: $screen-md-min) and (max-width: $screen-md-max) { @include str-truncated(450px); } diff --git a/app/assets/stylesheets/pages/ui_dev_kit.scss b/app/assets/stylesheets/pages/ui_dev_kit.scss index e73cecc92be..8c87bc3cafd 100644 --- a/app/assets/stylesheets/pages/ui_dev_kit.scss +++ b/app/assets/stylesheets/pages/ui_dev_kit.scss @@ -7,11 +7,11 @@ .example { &::before { content: "Example"; - color: #bbb; + color: $ui-dev-kit-example-color; } padding: 15px; - border: 1px dashed #ddd; + border: 1px dashed $ui-dev-kit-example-border; margin-bottom: 15px; } } diff --git a/app/assets/stylesheets/pages/wiki.scss b/app/assets/stylesheets/pages/wiki.scss index dfaeba41cf6..b9f81533150 100644 --- a/app/assets/stylesheets/pages/wiki.scss +++ b/app/assets/stylesheets/pages/wiki.scss @@ -4,3 +4,128 @@ margin-right: auto; padding-right: 7px; } + +.wiki-page-header { + @extend .top-area; + position: relative; + + .wiki-page-title { + margin: 0; + font-size: 22px; + } + + .wiki-last-edit-by { + color: $gl-gray-light; + + strong { + color: $gl-text-color; + } + } + + .light { + font-weight: normal; + color: $gl-gray-light; + } + + .git-access-header { + padding: 16px 40px 11px 0; + line-height: 28px; + font-size: 18px; + } + + .git-clone-holder { + width: 100%; + padding-bottom: 40px; + } + + button.sidebar-toggle { + position: absolute; + right: 0; + top: 11px; + display: block; + } + + @media (min-width: $screen-sm-min) { + &.has-sidebar-toggle { + padding-right: 40px; + } + + .git-clone-holder { + width: 480px; + } + + .nav-controls { + width: auto; + min-width: 50%; + white-space: nowrap; + } + } + + @media (min-width: $screen-md-min) { + &.has-sidebar-toggle { + padding-right: 0; + } + + button.sidebar-toggle { + display: none; + } + } +} + +.wiki-git-access { + margin: $gl-padding 0; + + h3 { + font-size: 22px; + font-weight: normal; + margin-top: 1.4em; + } +} + +.right-sidebar.wiki-sidebar { + padding: $gl-padding 0; + + &.right-sidebar-collapsed { + display: none; + } + + .blocks-container { + padding: 0 $gl-padding; + } + + .block { + width: 100%; + } + + a { + color: $layout-link-gray; + + &:hover, + &.active { + color: $black; + } + } + + .active > a { + color: $black; + } + + ul.wiki-pages, + ul.wiki-pages li { + list-style: none; + padding: 0; + margin: 0; + } + + ul.wiki-pages li { + margin: 5px 0 10px; + } + + .wiki-sidebar-header { + padding: 0 $gl-padding $gl-padding; + + .gutter-toggle { + margin-top: 0; + } + } +} diff --git a/app/assets/stylesheets/pages/xterm.scss b/app/assets/stylesheets/pages/xterm.scss index 3fa7fa3d7e3..9f9d630978a 100644 --- a/app/assets/stylesheets/pages/xterm.scss +++ b/app/assets/stylesheets/pages/xterm.scss @@ -20,6 +20,266 @@ $l-cyan: #8abeb7; $l-white: $ci-text-color; + /* + * xterm colors + */ + $xterm-fg-0: $black; + $xterm-fg-1: #800000; + $xterm-fg-2: #008000; + $xterm-fg-3: #808000; + $xterm-fg-4: #000080; + $xterm-fg-5: #800080; + $xterm-fg-6: #008080; + $xterm-fg-7: #c0c0c0; + $xterm-fg-8: #808080; + $xterm-fg-9: #f00; + $xterm-fg-10: #0f0; + $xterm-fg-11: #ff0; + $xterm-fg-12: #00f; + $xterm-fg-13: #f0f; + $xterm-fg-14: #0ff; + $xterm-fg-15: $white-light; + $xterm-fg-16: $black; + $xterm-fg-17: #00005f; + $xterm-fg-18: #000087; + $xterm-fg-19: #0000af; + $xterm-fg-20: #0000d7; + $xterm-fg-21: #00f; + $xterm-fg-22: #005f00; + $xterm-fg-23: #005f5f; + $xterm-fg-24: #005f87; + $xterm-fg-25: #005faf; + $xterm-fg-26: #005fd7; + $xterm-fg-27: #005fff; + $xterm-fg-28: #008700; + $xterm-fg-29: #00875f; + $xterm-fg-30: #008787; + $xterm-fg-31: #0087af; + $xterm-fg-32: #0087d7; + $xterm-fg-33: #0087ff; + $xterm-fg-34: #00af00; + $xterm-fg-35: #00af5f; + $xterm-fg-36: #00af87; + $xterm-fg-37: #00afaf; + $xterm-fg-38: #00afd7; + $xterm-fg-39: #00afff; + $xterm-fg-40: #00d700; + $xterm-fg-41: #00d75f; + $xterm-fg-42: #00d787; + $xterm-fg-43: #00d7af; + $xterm-fg-44: #00d7d7; + $xterm-fg-45: #00d7ff; + $xterm-fg-46: #0f0; + $xterm-fg-47: #00ff5f; + $xterm-fg-48: #00ff87; + $xterm-fg-49: #00ffaf; + $xterm-fg-50: #00ffd7; + $xterm-fg-51: #0ff; + $xterm-fg-52: #5f0000; + $xterm-fg-53: #5f005f; + $xterm-fg-54: #5f0087; + $xterm-fg-55: #5f00af; + $xterm-fg-56: #5f00d7; + $xterm-fg-57: #5f00ff; + $xterm-fg-58: #5f5f00; + $xterm-fg-59: #5f5f5f; + $xterm-fg-60: #5f5f87; + $xterm-fg-61: #5f5faf; + $xterm-fg-62: #5f5fd7; + $xterm-fg-63: #5f5fff; + $xterm-fg-64: #5f8700; + $xterm-fg-65: #5f875f; + $xterm-fg-66: #5f8787; + $xterm-fg-67: #5f87af; + $xterm-fg-68: #5f87d7; + $xterm-fg-69: #5f87ff; + $xterm-fg-70: #5faf00; + $xterm-fg-71: #5faf5f; + $xterm-fg-72: #5faf87; + $xterm-fg-73: #5fafaf; + $xterm-fg-74: #5fafd7; + $xterm-fg-75: #5fafff; + $xterm-fg-76: #5fd700; + $xterm-fg-77: #5fd75f; + $xterm-fg-78: #5fd787; + $xterm-fg-79: #5fd7af; + $xterm-fg-80: #5fd7d7; + $xterm-fg-81: #5fd7ff; + $xterm-fg-82: #5fff00; + $xterm-fg-83: #5fff5f; + $xterm-fg-84: #5fff87; + $xterm-fg-85: #5fffaf; + $xterm-fg-86: #5fffd7; + $xterm-fg-87: #5fffff; + $xterm-fg-88: #870000; + $xterm-fg-89: #87005f; + $xterm-fg-90: #870087; + $xterm-fg-91: #8700af; + $xterm-fg-92: #8700d7; + $xterm-fg-93: #8700ff; + $xterm-fg-94: #875f00; + $xterm-fg-95: #875f5f; + $xterm-fg-96: #875f87; + $xterm-fg-97: #875faf; + $xterm-fg-98: #875fd7; + $xterm-fg-99: #875fff; + $xterm-fg-100: #878700; + $xterm-fg-101: #87875f; + $xterm-fg-102: #878787; + $xterm-fg-103: #8787af; + $xterm-fg-104: #8787d7; + $xterm-fg-105: #8787ff; + $xterm-fg-106: #87af00; + $xterm-fg-107: #87af5f; + $xterm-fg-108: #87af87; + $xterm-fg-109: #87afaf; + $xterm-fg-110: #87afd7; + $xterm-fg-111: #87afff; + $xterm-fg-112: #87d700; + $xterm-fg-113: #87d75f; + $xterm-fg-114: #87d787; + $xterm-fg-115: #87d7af; + $xterm-fg-116: #87d7d7; + $xterm-fg-117: #87d7ff; + $xterm-fg-118: #87ff00; + $xterm-fg-119: #87ff5f; + $xterm-fg-120: #87ff87; + $xterm-fg-121: #87ffaf; + $xterm-fg-122: #87ffd7; + $xterm-fg-123: #87ffff; + $xterm-fg-124: #af0000; + $xterm-fg-125: #af005f; + $xterm-fg-126: #af0087; + $xterm-fg-127: #af00af; + $xterm-fg-128: #af00d7; + $xterm-fg-129: #af00ff; + $xterm-fg-130: #af5f00; + $xterm-fg-131: #af5f5f; + $xterm-fg-132: #af5f87; + $xterm-fg-133: #af5faf; + $xterm-fg-134: #af5fd7; + $xterm-fg-135: #af5fff; + $xterm-fg-136: #af8700; + $xterm-fg-137: #af875f; + $xterm-fg-138: #af8787; + $xterm-fg-139: #af87af; + $xterm-fg-140: #af87d7; + $xterm-fg-141: #af87ff; + $xterm-fg-142: #afaf00; + $xterm-fg-143: #afaf5f; + $xterm-fg-144: #afaf87; + $xterm-fg-145: #afafaf; + $xterm-fg-146: #afafd7; + $xterm-fg-147: #afafff; + $xterm-fg-148: #afd700; + $xterm-fg-149: #afd75f; + $xterm-fg-150: #afd787; + $xterm-fg-151: #afd7af; + $xterm-fg-152: #afd7d7; + $xterm-fg-153: #afd7ff; + $xterm-fg-154: #afff00; + $xterm-fg-155: #afff5f; + $xterm-fg-156: #afff87; + $xterm-fg-157: #afffaf; + $xterm-fg-158: #afffd7; + $xterm-fg-159: #afffff; + $xterm-fg-160: #d70000; + $xterm-fg-161: #d7005f; + $xterm-fg-162: #d70087; + $xterm-fg-163: #d700af; + $xterm-fg-164: #d700d7; + $xterm-fg-165: #d700ff; + $xterm-fg-166: #d75f00; + $xterm-fg-167: #d75f5f; + $xterm-fg-168: #d75f87; + $xterm-fg-169: #d75faf; + $xterm-fg-170: #d75fd7; + $xterm-fg-171: #d75fff; + $xterm-fg-172: #d78700; + $xterm-fg-173: #d7875f; + $xterm-fg-174: #d78787; + $xterm-fg-175: #d787af; + $xterm-fg-176: #d787d7; + $xterm-fg-177: #d787ff; + $xterm-fg-178: #d7af00; + $xterm-fg-179: #d7af5f; + $xterm-fg-180: #d7af87; + $xterm-fg-181: #d7afaf; + $xterm-fg-182: #d7afd7; + $xterm-fg-183: #d7afff; + $xterm-fg-184: #d7d700; + $xterm-fg-185: #d7d75f; + $xterm-fg-186: #d7d787; + $xterm-fg-187: #d7d7af; + $xterm-fg-188: #d7d7d7; + $xterm-fg-189: #d7d7ff; + $xterm-fg-190: #d7ff00; + $xterm-fg-191: #d7ff5f; + $xterm-fg-192: #d7ff87; + $xterm-fg-193: #d7ffaf; + $xterm-fg-194: #d7ffd7; + $xterm-fg-195: #d7ffff; + $xterm-fg-196: #f00; + $xterm-fg-197: #ff005f; + $xterm-fg-198: #ff0087; + $xterm-fg-199: #ff00af; + $xterm-fg-200: #ff00d7; + $xterm-fg-201: #f0f; + $xterm-fg-202: #ff5f00; + $xterm-fg-203: #ff5f5f; + $xterm-fg-204: #ff5f87; + $xterm-fg-205: #ff5faf; + $xterm-fg-206: #ff5fd7; + $xterm-fg-207: #ff5fff; + $xterm-fg-208: #ff8700; + $xterm-fg-209: #ff875f; + $xterm-fg-210: #ff8787; + $xterm-fg-211: #ff87af; + $xterm-fg-212: #ff87d7; + $xterm-fg-213: #ff87ff; + $xterm-fg-214: #ffaf00; + $xterm-fg-215: #ffaf5f; + $xterm-fg-216: #ffaf87; + $xterm-fg-217: #ffafaf; + $xterm-fg-218: #ffafd7; + $xterm-fg-219: #ffafff; + $xterm-fg-220: #ffd700; + $xterm-fg-221: #ffd75f; + $xterm-fg-222: #ffd787; + $xterm-fg-223: #ffd7af; + $xterm-fg-224: #ffd7d7; + $xterm-fg-225: #ffd7ff; + $xterm-fg-226: #ff0; + $xterm-fg-227: #ffff5f; + $xterm-fg-228: #ffff87; + $xterm-fg-229: #ffffaf; + $xterm-fg-230: #ffffd7; + $xterm-fg-231: $white-light; + $xterm-fg-232: #080808; + $xterm-fg-233: #121212; + $xterm-fg-234: #1c1c1c; + $xterm-fg-235: #262626; + $xterm-fg-236: #303030; + $xterm-fg-237: #3a3a3a; + $xterm-fg-238: #444; + $xterm-fg-239: #4e4e4e; + $xterm-fg-240: #585858; + $xterm-fg-241: #626262; + $xterm-fg-242: #6c6c6c; + $xterm-fg-243: #767676; + $xterm-fg-244: #808080; + $xterm-fg-245: #8a8a8a; + $xterm-fg-246: #949494; + $xterm-fg-247: #9e9e9e; + $xterm-fg-248: #a8a8a8; + $xterm-fg-249: #b2b2b2; + $xterm-fg-250: #bcbcbc; + $xterm-fg-251: #c6c6c6; + $xterm-fg-252: #d0d0d0; + $xterm-fg-253: #dadada; + $xterm-fg-254: #e4e4e4; + $xterm-fg-255: #eee; + .term-bold { font-weight: bold; } @@ -169,1026 +429,1026 @@ } .xterm-fg-0 { - color: #000; + color: $xterm-fg-0; } .xterm-fg-1 { - color: #800000; + color: $xterm-fg-1; } .xterm-fg-2 { - color: #008000; + color: $xterm-fg-2; } .xterm-fg-3 { - color: #808000; + color: $xterm-fg-3; } .xterm-fg-4 { - color: #000080; + color: $xterm-fg-4; } .xterm-fg-5 { - color: #800080; + color: $xterm-fg-5; } .xterm-fg-6 { - color: #008080; + color: $xterm-fg-6; } .xterm-fg-7 { - color: #c0c0c0; + color: $xterm-fg-7; } .xterm-fg-8 { - color: #808080; + color: $xterm-fg-8; } .xterm-fg-9 { - color: #f00; + color: $xterm-fg-9; } .xterm-fg-10 { - color: #0f0; + color: $xterm-fg-10; } .xterm-fg-11 { - color: #ff0; + color: $xterm-fg-11; } .xterm-fg-12 { - color: #00f; + color: $xterm-fg-12; } .xterm-fg-13 { - color: #f0f; + color: $xterm-fg-13; } .xterm-fg-14 { - color: #0ff; + color: $xterm-fg-14; } .xterm-fg-15 { - color: #fff; + color: $white-light; } .xterm-fg-16 { - color: #000; + color: $black; } .xterm-fg-17 { - color: #00005f; + color: $xterm-fg-17; } .xterm-fg-18 { - color: #000087; + color: $xterm-fg-18; } .xterm-fg-19 { - color: #0000af; + color: $xterm-fg-19; } .xterm-fg-20 { - color: #0000d7; + color: $xterm-fg-20; } .xterm-fg-21 { - color: #00f; + color: $xterm-fg-21; } .xterm-fg-22 { - color: #005f00; + color: $xterm-fg-22; } .xterm-fg-23 { - color: #005f5f; + color: $xterm-fg-23; } .xterm-fg-24 { - color: #005f87; + color: $xterm-fg-24; } .xterm-fg-25 { - color: #005faf; + color: $xterm-fg-25; } .xterm-fg-26 { - color: #005fd7; + color: $xterm-fg-26; } .xterm-fg-27 { - color: #005fff; + color: $xterm-fg-27; } .xterm-fg-28 { - color: #008700; + color: $xterm-fg-28; } .xterm-fg-29 { - color: #00875f; + color: $xterm-fg-29; } .xterm-fg-30 { - color: #008787; + color: $xterm-fg-30; } .xterm-fg-31 { - color: #0087af; + color: $xterm-fg-31; } .xterm-fg-32 { - color: #0087d7; + color: $xterm-fg-32; } .xterm-fg-33 { - color: #0087ff; + color: $xterm-fg-33; } .xterm-fg-34 { - color: #00af00; + color: $xterm-fg-34; } .xterm-fg-35 { - color: #00af5f; + color: $xterm-fg-35; } .xterm-fg-36 { - color: #00af87; + color: $xterm-fg-36; } .xterm-fg-37 { - color: #00afaf; + color: $xterm-fg-37; } .xterm-fg-38 { - color: #00afd7; + color: $xterm-fg-38; } .xterm-fg-39 { - color: #00afff; + color: $xterm-fg-39; } .xterm-fg-40 { - color: #00d700; + color: $xterm-fg-40; } .xterm-fg-41 { - color: #00d75f; + color: $xterm-fg-41; } .xterm-fg-42 { - color: #00d787; + color: $xterm-fg-42; } .xterm-fg-43 { - color: #00d7af; + color: $xterm-fg-43; } .xterm-fg-44 { - color: #00d7d7; + color: $xterm-fg-44; } .xterm-fg-45 { - color: #00d7ff; + color: $xterm-fg-45; } .xterm-fg-46 { - color: #0f0; + color: $xterm-fg-46; } .xterm-fg-47 { - color: #00ff5f; + color: $xterm-fg-47; } .xterm-fg-48 { - color: #00ff87; + color: $xterm-fg-48; } .xterm-fg-49 { - color: #00ffaf; + color: $xterm-fg-49; } .xterm-fg-50 { - color: #00ffd7; + color: $xterm-fg-50; } .xterm-fg-51 { - color: #0ff; + color: $xterm-fg-51; } .xterm-fg-52 { - color: #5f0000; + color: $xterm-fg-52; } .xterm-fg-53 { - color: #5f005f; + color: $xterm-fg-53; } .xterm-fg-54 { - color: #5f0087; + color: $xterm-fg-54; } .xterm-fg-55 { - color: #5f00af; + color: $xterm-fg-55; } .xterm-fg-56 { - color: #5f00d7; + color: $xterm-fg-56; } .xterm-fg-57 { - color: #5f00ff; + color: $xterm-fg-57; } .xterm-fg-58 { - color: #5f5f00; + color: $xterm-fg-58; } .xterm-fg-59 { - color: #5f5f5f; + color: $xterm-fg-59; } .xterm-fg-60 { - color: #5f5f87; + color: $xterm-fg-60; } .xterm-fg-61 { - color: #5f5faf; + color: $xterm-fg-61; } .xterm-fg-62 { - color: #5f5fd7; + color: $xterm-fg-62; } .xterm-fg-63 { - color: #5f5fff; + color: $xterm-fg-63; } .xterm-fg-64 { - color: #5f8700; + color: $xterm-fg-64; } .xterm-fg-65 { - color: #5f875f; + color: $xterm-fg-65; } .xterm-fg-66 { - color: #5f8787; + color: $xterm-fg-66; } .xterm-fg-67 { - color: #5f87af; + color: $xterm-fg-67; } .xterm-fg-68 { - color: #5f87d7; + color: $xterm-fg-68; } .xterm-fg-69 { - color: #5f87ff; + color: $xterm-fg-69; } .xterm-fg-70 { - color: #5faf00; + color: $xterm-fg-70; } .xterm-fg-71 { - color: #5faf5f; + color: $xterm-fg-71; } .xterm-fg-72 { - color: #5faf87; + color: $xterm-fg-72; } .xterm-fg-73 { - color: #5fafaf; + color: $xterm-fg-73; } .xterm-fg-74 { - color: #5fafd7; + color: $xterm-fg-74; } .xterm-fg-75 { - color: #5fafff; + color: $xterm-fg-75; } .xterm-fg-76 { - color: #5fd700; + color: $xterm-fg-76; } .xterm-fg-77 { - color: #5fd75f; + color: $xterm-fg-77; } .xterm-fg-78 { - color: #5fd787; + color: $xterm-fg-78; } .xterm-fg-79 { - color: #5fd7af; + color: $xterm-fg-79; } .xterm-fg-80 { - color: #5fd7d7; + color: $xterm-fg-80; } .xterm-fg-81 { - color: #5fd7ff; + color: $xterm-fg-81; } .xterm-fg-82 { - color: #5fff00; + color: $xterm-fg-82; } .xterm-fg-83 { - color: #5fff5f; + color: $xterm-fg-83; } .xterm-fg-84 { - color: #5fff87; + color: $xterm-fg-84; } .xterm-fg-85 { - color: #5fffaf; + color: $xterm-fg-85; } .xterm-fg-86 { - color: #5fffd7; + color: $xterm-fg-86; } .xterm-fg-87 { - color: #5fffff; + color: $xterm-fg-87; } .xterm-fg-88 { - color: #870000; + color: $xterm-fg-88; } .xterm-fg-89 { - color: #87005f; + color: $xterm-fg-89; } .xterm-fg-90 { - color: #870087; + color: $xterm-fg-90; } .xterm-fg-91 { - color: #8700af; + color: $xterm-fg-91; } .xterm-fg-92 { - color: #8700d7; + color: $xterm-fg-92; } .xterm-fg-93 { - color: #8700ff; + color: $xterm-fg-93; } .xterm-fg-94 { - color: #875f00; + color: $xterm-fg-94; } .xterm-fg-95 { - color: #875f5f; + color: $xterm-fg-95; } .xterm-fg-96 { - color: #875f87; + color: $xterm-fg-96; } .xterm-fg-97 { - color: #875faf; + color: $xterm-fg-97; } .xterm-fg-98 { - color: #875fd7; + color: $xterm-fg-98; } .xterm-fg-99 { - color: #875fff; + color: $xterm-fg-99; } .xterm-fg-100 { - color: #878700; + color: $xterm-fg-100; } .xterm-fg-101 { - color: #87875f; + color: $xterm-fg-101; } .xterm-fg-102 { - color: #878787; + color: $xterm-fg-102; } .xterm-fg-103 { - color: #8787af; + color: $xterm-fg-103; } .xterm-fg-104 { - color: #8787d7; + color: $xterm-fg-104; } .xterm-fg-105 { - color: #8787ff; + color: $xterm-fg-105; } .xterm-fg-106 { - color: #87af00; + color: $xterm-fg-106; } .xterm-fg-107 { - color: #87af5f; + color: $xterm-fg-107; } .xterm-fg-108 { - color: #87af87; + color: $xterm-fg-108; } .xterm-fg-109 { - color: #87afaf; + color: $xterm-fg-109; } .xterm-fg-110 { - color: #87afd7; + color: $xterm-fg-110; } .xterm-fg-111 { - color: #87afff; + color: $xterm-fg-111; } .xterm-fg-112 { - color: #87d700; + color: $xterm-fg-112; } .xterm-fg-113 { - color: #87d75f; + color: $xterm-fg-113; } .xterm-fg-114 { - color: #87d787; + color: $xterm-fg-114; } .xterm-fg-115 { - color: #87d7af; + color: $xterm-fg-115; } .xterm-fg-116 { - color: #87d7d7; + color: $xterm-fg-116; } .xterm-fg-117 { - color: #87d7ff; + color: $xterm-fg-117; } .xterm-fg-118 { - color: #87ff00; + color: $xterm-fg-118; } .xterm-fg-119 { - color: #87ff5f; + color: $xterm-fg-119; } .xterm-fg-120 { - color: #87ff87; + color: $xterm-fg-120; } .xterm-fg-121 { - color: #87ffaf; + color: $xterm-fg-121; } .xterm-fg-122 { - color: #87ffd7; + color: $xterm-fg-122; } .xterm-fg-123 { - color: #87ffff; + color: $xterm-fg-123; } .xterm-fg-124 { - color: #af0000; + color: $xterm-fg-124; } .xterm-fg-125 { - color: #af005f; + color: $xterm-fg-125; } .xterm-fg-126 { - color: #af0087; + color: $xterm-fg-126; } .xterm-fg-127 { - color: #af00af; + color: $xterm-fg-127; } .xterm-fg-128 { - color: #af00d7; + color: $xterm-fg-128; } .xterm-fg-129 { - color: #af00ff; + color: $xterm-fg-129; } .xterm-fg-130 { - color: #af5f00; + color: $xterm-fg-130; } .xterm-fg-131 { - color: #af5f5f; + color: $xterm-fg-131; } .xterm-fg-132 { - color: #af5f87; + color: $xterm-fg-132; } .xterm-fg-133 { - color: #af5faf; + color: $xterm-fg-133; } .xterm-fg-134 { - color: #af5fd7; + color: $xterm-fg-134; } .xterm-fg-135 { - color: #af5fff; + color: $xterm-fg-135; } .xterm-fg-136 { - color: #af8700; + color: $xterm-fg-136; } .xterm-fg-137 { - color: #af875f; + color: $xterm-fg-137; } .xterm-fg-138 { - color: #af8787; + color: $xterm-fg-138; } .xterm-fg-139 { - color: #af87af; + color: $xterm-fg-139; } .xterm-fg-140 { - color: #af87d7; + color: $xterm-fg-140; } .xterm-fg-141 { - color: #af87ff; + color: $xterm-fg-141; } .xterm-fg-142 { - color: #afaf00; + color: $xterm-fg-142; } .xterm-fg-143 { - color: #afaf5f; + color: $xterm-fg-143; } .xterm-fg-144 { - color: #afaf87; + color: $xterm-fg-144; } .xterm-fg-145 { - color: #afafaf; + color: $xterm-fg-145; } .xterm-fg-146 { - color: #afafd7; + color: $xterm-fg-146; } .xterm-fg-147 { - color: #afafff; + color: $xterm-fg-147; } .xterm-fg-148 { - color: #afd700; + color: $xterm-fg-148; } .xterm-fg-149 { - color: #afd75f; + color: $xterm-fg-149; } .xterm-fg-150 { - color: #afd787; + color: $xterm-fg-150; } .xterm-fg-151 { - color: #afd7af; + color: $xterm-fg-151; } .xterm-fg-152 { - color: #afd7d7; + color: $xterm-fg-152; } .xterm-fg-153 { - color: #afd7ff; + color: $xterm-fg-153; } .xterm-fg-154 { - color: #afff00; + color: $xterm-fg-154; } .xterm-fg-155 { - color: #afff5f; + color: $xterm-fg-155; } .xterm-fg-156 { - color: #afff87; + color: $xterm-fg-156; } .xterm-fg-157 { - color: #afffaf; + color: $xterm-fg-157; } .xterm-fg-158 { - color: #afffd7; + color: $xterm-fg-158; } .xterm-fg-159 { - color: #afffff; + color: $xterm-fg-159; } .xterm-fg-160 { - color: #d70000; + color: $xterm-fg-160; } .xterm-fg-161 { - color: #d7005f; + color: $xterm-fg-161; } .xterm-fg-162 { - color: #d70087; + color: $xterm-fg-162; } .xterm-fg-163 { - color: #d700af; + color: $xterm-fg-163; } .xterm-fg-164 { - color: #d700d7; + color: $xterm-fg-164; } .xterm-fg-165 { - color: #d700ff; + color: $xterm-fg-165; } .xterm-fg-166 { - color: #d75f00; + color: $xterm-fg-166; } .xterm-fg-167 { - color: #d75f5f; + color: $xterm-fg-167; } .xterm-fg-168 { - color: #d75f87; + color: $xterm-fg-168; } .xterm-fg-169 { - color: #d75faf; + color: $xterm-fg-169; } .xterm-fg-170 { - color: #d75fd7; + color: $xterm-fg-170; } .xterm-fg-171 { - color: #d75fff; + color: $xterm-fg-171; } .xterm-fg-172 { - color: #d78700; + color: $xterm-fg-172; } .xterm-fg-173 { - color: #d7875f; + color: $xterm-fg-173; } .xterm-fg-174 { - color: #d78787; + color: $xterm-fg-174; } .xterm-fg-175 { - color: #d787af; + color: $xterm-fg-175; } .xterm-fg-176 { - color: #d787d7; + color: $xterm-fg-176; } .xterm-fg-177 { - color: #d787ff; + color: $xterm-fg-177; } .xterm-fg-178 { - color: #d7af00; + color: $xterm-fg-178; } .xterm-fg-179 { - color: #d7af5f; + color: $xterm-fg-179; } .xterm-fg-180 { - color: #d7af87; + color: $xterm-fg-180; } .xterm-fg-181 { - color: #d7afaf; + color: $xterm-fg-181; } .xterm-fg-182 { - color: #d7afd7; + color: $xterm-fg-182; } .xterm-fg-183 { - color: #d7afff; + color: $xterm-fg-183; } .xterm-fg-184 { - color: #d7d700; + color: $xterm-fg-184; } .xterm-fg-185 { - color: #d7d75f; + color: $xterm-fg-185; } .xterm-fg-186 { - color: #d7d787; + color: $xterm-fg-186; } .xterm-fg-187 { - color: #d7d7af; + color: $xterm-fg-187; } .xterm-fg-188 { - color: #d7d7d7; + color: $xterm-fg-188; } .xterm-fg-189 { - color: #d7d7ff; + color: $xterm-fg-189; } .xterm-fg-190 { - color: #d7ff00; + color: $xterm-fg-190; } .xterm-fg-191 { - color: #d7ff5f; + color: $xterm-fg-191; } .xterm-fg-192 { - color: #d7ff87; + color: $xterm-fg-192; } .xterm-fg-193 { - color: #d7ffaf; + color: $xterm-fg-193; } .xterm-fg-194 { - color: #d7ffd7; + color: $xterm-fg-194; } .xterm-fg-195 { - color: #d7ffff; + color: $xterm-fg-195; } .xterm-fg-196 { - color: #f00; + color: $xterm-fg-196; } .xterm-fg-197 { - color: #ff005f; + color: $xterm-fg-197; } .xterm-fg-198 { - color: #ff0087; + color: $xterm-fg-198; } .xterm-fg-199 { - color: #ff00af; + color: $xterm-fg-199; } .xterm-fg-200 { - color: #ff00d7; + color: $xterm-fg-200; } .xterm-fg-201 { - color: #f0f; + color: $xterm-fg-201; } .xterm-fg-202 { - color: #ff5f00; + color: $xterm-fg-202; } .xterm-fg-203 { - color: #ff5f5f; + color: $xterm-fg-203; } .xterm-fg-204 { - color: #ff5f87; + color: $xterm-fg-204; } .xterm-fg-205 { - color: #ff5faf; + color: $xterm-fg-205; } .xterm-fg-206 { - color: #ff5fd7; + color: $xterm-fg-206; } .xterm-fg-207 { - color: #ff5fff; + color: $xterm-fg-207; } .xterm-fg-208 { - color: #ff8700; + color: $xterm-fg-208; } .xterm-fg-209 { - color: #ff875f; + color: $xterm-fg-209; } .xterm-fg-210 { - color: #ff8787; + color: $xterm-fg-210; } .xterm-fg-211 { - color: #ff87af; + color: $xterm-fg-211; } .xterm-fg-212 { - color: #ff87d7; + color: $xterm-fg-212; } .xterm-fg-213 { - color: #ff87ff; + color: $xterm-fg-213; } .xterm-fg-214 { - color: #ffaf00; + color: $xterm-fg-214; } .xterm-fg-215 { - color: #ffaf5f; + color: $xterm-fg-215; } .xterm-fg-216 { - color: #ffaf87; + color: $xterm-fg-216; } .xterm-fg-217 { - color: #ffafaf; + color: $xterm-fg-217; } .xterm-fg-218 { - color: #ffafd7; + color: $xterm-fg-218; } .xterm-fg-219 { - color: #ffafff; + color: $xterm-fg-219; } .xterm-fg-220 { - color: #ffd700; + color: $xterm-fg-220; } .xterm-fg-221 { - color: #ffd75f; + color: $xterm-fg-221; } .xterm-fg-222 { - color: #ffd787; + color: $xterm-fg-222; } .xterm-fg-223 { - color: #ffd7af; + color: $xterm-fg-223; } .xterm-fg-224 { - color: #ffd7d7; + color: $xterm-fg-224; } .xterm-fg-225 { - color: #ffd7ff; + color: $xterm-fg-225; } .xterm-fg-226 { - color: #ff0; + color: $xterm-fg-226; } .xterm-fg-227 { - color: #ffff5f; + color: $xterm-fg-227; } .xterm-fg-228 { - color: #ffff87; + color: $xterm-fg-228; } .xterm-fg-229 { - color: #ffffaf; + color: $xterm-fg-229; } .xterm-fg-230 { - color: #ffffd7; + color: $xterm-fg-230; } .xterm-fg-231 { - color: #fff; + color: $xterm-fg-231; } .xterm-fg-232 { - color: #080808; + color: $xterm-fg-232; } .xterm-fg-233 { - color: #121212; + color: $xterm-fg-233; } .xterm-fg-234 { - color: #1c1c1c; + color: $xterm-fg-234; } .xterm-fg-235 { - color: #262626; + color: $xterm-fg-235; } .xterm-fg-236 { - color: #303030; + color: $xterm-fg-236; } .xterm-fg-237 { - color: #3a3a3a; + color: $xterm-fg-237; } .xterm-fg-238 { - color: #444; + color: $xterm-fg-238; } .xterm-fg-239 { - color: #4e4e4e; + color: $xterm-fg-239; } .xterm-fg-240 { - color: #585858; + color: $xterm-fg-240; } .xterm-fg-241 { - color: #626262; + color: $xterm-fg-241; } .xterm-fg-242 { - color: #6c6c6c; + color: $xterm-fg-242; } .xterm-fg-243 { - color: #767676; + color: $xterm-fg-243; } .xterm-fg-244 { - color: #808080; + color: $xterm-fg-244; } .xterm-fg-245 { - color: #8a8a8a; + color: $xterm-fg-245; } .xterm-fg-246 { - color: #949494; + color: $xterm-fg-246; } .xterm-fg-247 { - color: #9e9e9e; + color: $xterm-fg-247; } .xterm-fg-248 { - color: #a8a8a8; + color: $xterm-fg-248; } .xterm-fg-249 { - color: #b2b2b2; + color: $xterm-fg-249; } .xterm-fg-250 { - color: #bcbcbc; + color: $xterm-fg-250; } .xterm-fg-251 { - color: #c6c6c6; + color: $xterm-fg-251; } .xterm-fg-252 { - color: #d0d0d0; + color: $xterm-fg-252; } .xterm-fg-253 { - color: #dadada; + color: $xterm-fg-253; } .xterm-fg-254 { - color: #e4e4e4; + color: $xterm-fg-254; } .xterm-fg-255 { - color: #eee; + color: $xterm-fg-255; } } diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb index 5c44637fdee..5f13353baa1 100644 --- a/app/controllers/autocomplete_controller.rb +++ b/app/controllers/autocomplete_controller.rb @@ -11,7 +11,7 @@ class AutocompleteController < ApplicationController @users = @users.reorder(:name) @users = @users.page(params[:page]) - if params[:todo_filter].present? + if params[:todo_filter].present? && current_user @users = @users.todo_authors(current_user.id, params[:todo_state_filter]) end diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb index a10cdcce72b..37feff79999 100644 --- a/app/controllers/help_controller.rb +++ b/app/controllers/help_controller.rb @@ -7,8 +7,10 @@ class HelpController < ApplicationController @help_index = File.read(Rails.root.join('doc', 'README.md')) # Prefix Markdown links with `help/` unless they are external links - # See http://rubular.com/r/MioSrVLK3S - @help_index.gsub!(%r{(\]\()(?!.+://)([^\)\(]+\))}, '\1/help/\2') + # See http://rubular.com/r/X3baHTbPO2 + @help_index.gsub!(%r{(?<delim>\]\()(?!.+://)(?!/)(?<link>[^\)\(]+\))}) do + "#{$~[:delim]}#{Gitlab.config.gitlab.relative_url_root}/help/#{$~[:link]}" + end end def show diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index c7cde9e6a14..f0cb5a9d4b4 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -498,7 +498,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController def validates_merge_request # Show git not found page # if there is no saved commits between source & target branch - if @merge_request.commits.blank? + if @merge_request.has_no_commits? # and if target branch doesn't exist return invalid_mr unless @merge_request.target_branch_exists? end @@ -506,7 +506,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController def define_show_vars @noteable = @merge_request - @commits_count = @merge_request.commits.count + @commits_count = @merge_request.commits_count if @merge_request.locked_long_ago? @merge_request.unlock_mr diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index 533af80aee0..85188cfdd4c 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -1,6 +1,6 @@ class Projects::PipelinesController < Projects::ApplicationController before_action :pipeline, except: [:index, :new, :create] - before_action :commit, only: [:show] + before_action :commit, only: [:show, :builds] before_action :authorize_read_pipeline! before_action :authorize_create_pipeline!, only: [:new, :create] before_action :authorize_update_pipeline!, only: [:retry, :cancel] @@ -32,6 +32,14 @@ class Projects::PipelinesController < Projects::ApplicationController def show end + def builds + respond_to do |format| + format.html do + render 'show' + end + end + end + def retry pipeline.retry_failed(current_user) diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb index 177ccf5eec9..c3353446fd1 100644 --- a/app/controllers/projects/wikis_controller.rb +++ b/app/controllers/projects/wikis_controller.rb @@ -115,6 +115,8 @@ class Projects::WikisController < Projects::ApplicationController # Call #wiki to make sure the Wiki Repo is initialized @project_wiki.wiki + + @sidebar_wiki_pages = @project_wiki.pages.first(15) rescue ProjectWiki::CouldNotCreateWikiError flash[:notice] = "Could not create Wiki Repository at this time. Please try again later." redirect_to project_path(@project) diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 9a74e36870b..001c83ccb4b 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -49,6 +49,32 @@ class IssuableFinder execute.find_by(*params) end + # We often get counts for each state by running a query per state, and + # counting those results. This is typically slower than running one query + # (even if that query is slower than any of the individual state queries) and + # grouping and counting within that query. + # + def count_by_state + count_params = params.merge(state: nil, sort: nil) + labels_count = label_names.any? ? label_names.count : 1 + finder = self.class.new(current_user, count_params) + counts = Hash.new(0) + + # Searching by label includes a GROUP BY in the query, but ours will be last + # because it is added last. Searching by multiple labels also includes a row + # per issuable, so we have to count those in Ruby - which is bad, but still + # better than performing multiple queries. + # + finder.execute.reorder(nil).group(:state).count.each do |key, value| + counts[Array(key).last.to_sym] += value / labels_count + end + + counts[:all] = counts.values.sum + counts[:opened] += counts[:reopened] + + counts + end + def group return @group if defined?(@group) diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 6584aa3edd5..8231f8fa334 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -180,12 +180,9 @@ module IssuablesHelper end def issuables_count_for_state(issuable_type, state) - issuables_finder = public_send("#{issuable_type}_finder") - - params = issuables_finder.params.merge(state: state) - finder = issuables_finder.class.new(issuables_finder.current_user, params) - - finder.execute.page(1).total_count + @counts ||= {} + @counts[issuable_type] ||= public_send("#{issuable_type}_finder").count_by_state + @counts[issuable_type][state] end IRRELEVANT_PARAMS_FOR_CACHE_KEY = %i[utf8 sort page] @@ -195,6 +192,7 @@ module IssuablesHelper opts = params.with_indifferent_access opts[:state] = state opts.except!(*IRRELEVANT_PARAMS_FOR_CACHE_KEY) + opts.delete_if { |_, value| value.blank? } hexdigest(['issuables_count', issuable_type, opts.sort].flatten.join('-')) end diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb index df87fac132d..a3331dc80cb 100644 --- a/app/helpers/nav_helper.rb +++ b/app/helpers/nav_helper.rb @@ -20,6 +20,11 @@ module NavHelper end elsif current_path?('builds#show') "page-gutter build-sidebar right-sidebar-expanded" + elsif current_path?('wikis#show') || + current_path?('wikis#edit') || + current_path?('wikis#history') || + current_path?('wikis#git_access') + "page-gutter wiki-sidebar right-sidebar-expanded" end end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 898ce6a3af7..9cda3b78761 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -390,21 +390,7 @@ module ProjectsHelper "success" end end - - def new_readme_path - ref = @repository.root_ref if @repository - ref ||= 'master' - - namespace_project_new_blob_path(@project.namespace, @project, tree_join(ref), file_name: 'README.md') - end - - def new_license_path - ref = @repository.root_ref if @repository - ref ||= 'master' - - namespace_project_new_blob_path(@project.namespace, @project, tree_join(ref), file_name: 'LICENSE') - end - + def readme_cache_key sha = @project.commit.try(:sha) || 'nil' [@project.path_with_namespace, sha, "readme"].join('-') diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index e7d33bd26db..88c46076df6 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -203,7 +203,7 @@ module Ci .reorder(iid: :asc) merge_requests.find do |merge_request| - merge_request.commits.any? { |ci| ci.id == pipeline.sha } + merge_request.commits_sha.include?(pipeline.sha) end end diff --git a/app/models/commit.rb b/app/models/commit.rb index 9e7fde9503d..176c524cf7b 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -48,6 +48,10 @@ class Commit max_lines: DIFF_HARD_LIMIT_LINES, } end + + def from_hash(hash, project) + new(Gitlab::Git::Commit.new(hash), project) + end end attr_accessor :raw diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 64990f8134e..bfb016df46d 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -22,7 +22,8 @@ class MergeRequest < ActiveRecord::Base after_create :ensure_merge_request_diff, unless: :importing? after_update :reload_diff_if_branch_changed - delegate :commits, :real_size, to: :merge_request_diff, prefix: nil + delegate :commits, :real_size, :commits_sha, :commits_count, + to: :merge_request_diff, prefix: nil # When this attribute is true some MR validation is ignored # It allows us to close or modify broken merge requests @@ -662,7 +663,7 @@ class MergeRequest < ActiveRecord::Base end def broken? - self.commits.blank? || branch_missing? || cannot_be_merged? + has_no_commits? || branch_missing? || cannot_be_merged? end def can_be_merged_by?(user) @@ -770,10 +771,6 @@ class MergeRequest < ActiveRecord::Base diverged_commits_count > 0 end - def commits_sha - commits.map(&:sha) - end - def head_pipeline return unless diff_head_sha && source_project @@ -871,4 +868,12 @@ class MergeRequest < ActiveRecord::Base @conflicts_can_be_resolved_in_ui = false end end + + def has_commits? + commits_count > 0 + end + + def has_no_commits? + !has_commits? + end end diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 58a24eb84cb..b8f36a2c958 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -127,11 +127,7 @@ class MergeRequestDiff < ActiveRecord::Base end def commits_sha - if @commits - commits.map(&:sha) - else - st_commits.map { |commit| commit[:id] } - end + st_commits.map { |commit| commit[:id] } end def diff_refs @@ -176,6 +172,10 @@ class MergeRequestDiff < ActiveRecord::Base CompareService.new.execute(project, head_commit_sha, project, sha, straight: straight) end + def commits_count + st_commits.count + end + private # Old GitLab implementations may have generated diffs as ["--broken-diff"]. diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb index 70bbbbcda85..894315a8593 100644 --- a/app/models/project_services/jira_service.rb +++ b/app/models/project_services/jira_service.rb @@ -9,6 +9,9 @@ class JiraService < IssueTrackerService before_update :reset_password + # This is confusing, but JiraService does not really support these events. + # The values here are required to display correct options in the service + # configuration screen. def supported_events %w(commit merge_request) end @@ -105,18 +108,29 @@ class JiraService < IssueTrackerService "#{url}/secure/CreateIssue.jspa" end - def execute(push, issue = nil) - if issue.nil? - # No specific issue, that means - # we just want to test settings - test_settings - else - jira_issue = jira_request { client.Issue.find(issue.iid) } + def execute(push) + # This method is a no-op, because currently JiraService does not + # support any events. + end - return false unless jira_issue.present? + def close_issue(entity, external_issue) + issue = jira_request { client.Issue.find(external_issue.iid) } - close_issue(push, jira_issue) - end + return if issue.nil? || issue.resolution.present? || !jira_issue_transition_id.present? + + commit_id = if entity.is_a?(Commit) + entity.id + elsif entity.is_a?(MergeRequest) + entity.diff_head_sha + end + + commit_url = build_entity_url(:commit, commit_id) + + # Depending on the JIRA project's workflow, a comment during transition + # may or may not be allowed. Refresh the issue after transition and check + # if it is closed, so we don't have one comment for every commit. + issue = jira_request { client.Issue.find(issue.key) } if transition_issue(issue) + add_issue_solved_comment(issue, commit_id, commit_url) if issue.resolution end def create_cross_reference_note(mentioned, noteable, author) @@ -156,6 +170,11 @@ class JiraService < IssueTrackerService "Please fill in Password and Username." end + def test(_) + result = test_settings + { success: result.present?, result: result } + end + def can_test? username.present? && password.present? end @@ -182,24 +201,6 @@ class JiraService < IssueTrackerService end end - def close_issue(entity, issue) - return if issue.nil? || issue.resolution.present? || !jira_issue_transition_id.present? - - commit_id = if entity.is_a?(Commit) - entity.id - elsif entity.is_a?(MergeRequest) - entity.diff_head_sha - end - - commit_url = build_entity_url(:commit, commit_id) - - # Depending on the JIRA project's workflow, a comment during transition - # may or may not be allowed. Refresh the issue after transition and check - # if it is closed, so we don't have one comment for every commit. - issue = jira_request { client.Issue.find(issue.key) } if transition_issue(issue) - add_issue_solved_comment(issue, commit_id, commit_url) if issue.resolution - end - def transition_issue(issue) issue.transitions.build.save(transition: { id: jira_issue_transition_id }) end diff --git a/app/models/user.rb b/app/models/user.rb index b54ce14f0bf..b9bb4a9e3f7 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -512,7 +512,7 @@ class User < ActiveRecord::Base end def require_ssh_key? - keys.count == 0 + keys.count == 0 && Gitlab::ProtocolAccess.allowed?('ssh') end def require_password? diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index 1ee31023e26..8ac4bd9df6d 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -50,6 +50,7 @@ class ProjectPolicy < BasePolicy def reporter_access! can! :download_code + can! :download_wiki_code can! :fork_project can! :create_project_snippet can! :update_issue @@ -187,6 +188,7 @@ class ProjectPolicy < BasePolicy unless project.feature_available?(:wiki, user) || project.has_external_wiki? cannot!(*named_abilities(:wiki)) + cannot!(:download_wiki_code) end unless project.feature_available?(:builds, user) && repository_enabled @@ -226,6 +228,7 @@ class ProjectPolicy < BasePolicy can! :read_commit_status can! :read_container_image can! :download_code + can! :download_wiki_code can! :read_cycle_analytics # NOTE: may be overridden by IssuePolicy diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb index 647930d555c..185556c12cc 100644 --- a/app/services/git_push_service.rb +++ b/app/services/git_push_service.rb @@ -135,7 +135,7 @@ class GitPushService < BaseService @push_commits.each do |commit| ProcessCommitWorker. - perform_async(project.id, current_user.id, commit.id, default) + perform_async(project.id, current_user.id, commit.to_hash, default) end end diff --git a/app/services/issues/close_service.rb b/app/services/issues/close_service.rb index ab4c51386a4..f1030912c68 100644 --- a/app/services/issues/close_service.rb +++ b/app/services/issues/close_service.rb @@ -17,7 +17,7 @@ module Issues # allowed to close the given issue. def close_issue(issue, commit: nil, notifications: true, system_note: true) if project.jira_tracker? && project.jira_service.active - project.jira_service.execute(commit, issue) + project.jira_service.close_issue(commit, issue) todo_service.close_issue(issue, current_user) return issue end diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb index 22596b4014a..e4056306bc4 100644 --- a/app/services/merge_requests/refresh_service.rb +++ b/app/services/merge_requests/refresh_service.rb @@ -63,7 +63,7 @@ module MergeRequests if merge_request.source_branch == @branch_name || force_push? merge_request.reload_diff else - mr_commit_ids = merge_request.commits.map(&:id) + mr_commit_ids = merge_request.commits_sha push_commit_ids = @commits.map(&:id) matches = mr_commit_ids & push_commit_ids merge_request.reload_diff if matches.any? @@ -123,7 +123,7 @@ module MergeRequests return unless @commits.present? merge_requests_for_source_branch.each do |merge_request| - mr_commit_ids = Set.new(merge_request.commits.map(&:id)) + mr_commit_ids = Set.new(merge_request.commits_sha) new_commits, existing_commits = @commits.partition do |commit| mr_commit_ids.include?(commit.id) diff --git a/app/views/errors/access_denied.html.haml b/app/views/errors/access_denied.html.haml index c034bbe430e..8bddbef3562 100644 --- a/app/views/errors/access_denied.html.haml +++ b/app/views/errors/access_denied.html.haml @@ -1,6 +1,10 @@ -- page_title "Access Denied" -%h1 403 -%h3 Access Denied -%hr -%p You are not allowed to access this page. -%p Read more about project permissions #{link_to "here", help_page_path("user/permissions"), class: "vlink"} +- content_for(:title, 'Access Denied') +%img{:alt => "GitLab Logo", + :src => image_path('logo.svg')} + %h1 + 403 +.container + %h3 Access Denied + %hr + %p You are not allowed to access this page. + %p Read more about project permissions #{link_to "here", help_page_path("user/permissions"), class: "vlink"} diff --git a/app/views/errors/encoding.html.haml b/app/views/errors/encoding.html.haml index 90cfbebfcc6..064ff14ad2c 100644 --- a/app/views/errors/encoding.html.haml +++ b/app/views/errors/encoding.html.haml @@ -1,5 +1,9 @@ -- page_title "Encoding Error" -%h1 500 -%h3 Encoding Error -%hr -%p Page can't be loaded because of an encoding error. +- content_for(:title, 'Encoding Error') +%img{:alt => "GitLab Logo", + :src => image_path('logo.svg')} + %h1 + 500 +.container + %h3 Encoding Error + %hr + %p Page can't be loaded because of an encoding error. diff --git a/app/views/errors/git_not_found.html.haml b/app/views/errors/git_not_found.html.haml index ff5d4cc1506..c5c12a410ac 100644 --- a/app/views/errors/git_not_found.html.haml +++ b/app/views/errors/git_not_found.html.haml @@ -1,7 +1,10 @@ -- page_title "Git Resource Not Found" -%h1 404 -%h3 Git Resource Not found -%hr -%p - Application can't get access to some branch or commit in your repository. It - may have been moved. +- content_for(:title, 'Git Resource Not Found') +%img{:alt => "GitLab Logo", + :src => image_path('logo.svg')} + %h1 + 404 +.container + %h3 Git Resource Not found + %hr + %p Application can't get access to some branch or commit in your repository. It + may have been moved diff --git a/app/views/errors/not_found.html.haml b/app/views/errors/not_found.html.haml index 3756b98ebb2..50a54a93cb5 100644 --- a/app/views/errors/not_found.html.haml +++ b/app/views/errors/not_found.html.haml @@ -1,5 +1,9 @@ -- page_title "Not Found" -%h1 404 -%h3 The resource you were looking for doesn't exist. -%hr -%p You may have mistyped the address or the page may have moved. +- content_for(:title, 'Not Found') +%img{:alt => "GitLab Logo", + :src => image_path('logo.svg')} + %h1 + 404 +.container + %h3 The resource you were looking for doesn't exist. + %hr + %p You may have mistyped the address or the page may have moved. diff --git a/app/views/errors/omniauth_error.html.haml b/app/views/errors/omniauth_error.html.haml index 3e70e98a24c..d91f1878cb6 100644 --- a/app/views/errors/omniauth_error.html.haml +++ b/app/views/errors/omniauth_error.html.haml @@ -1,9 +1,13 @@ -- page_title "Auth Error" -%h1 422 -%h3 Sign-in using #{@provider} auth failed -%hr -%p Sign-in failed because #{@error}. -%p There are couple of steps you can take: +- content_for(:title, 'Auth Error') +%img{:alt => "GitLab Logo", + :src => image_path('logo.svg')} + %h1 + 422 +.container + %h3 Sign-in using #{@provider} auth failed + %hr + %p Sign-in failed because #{@error}. + %p There are couple of steps you can take: %ul %li Try logging in using your email diff --git a/app/views/layouts/errors.html.haml b/app/views/layouts/errors.html.haml index 7fbe065df00..a3b925f6afd 100644 --- a/app/views/layouts/errors.html.haml +++ b/app/views/layouts/errors.html.haml @@ -1,10 +1,59 @@ !!! 5 %html{ lang: "en"} - = render "layouts/head" - %body{class: "#{user_application_theme} application navless"} - = Gon::Base.render_data - = render "layouts/header/empty" - .container.navless-container - = render "layouts/flash" - .error-page - = yield + %head + %meta{:content => "width=device-width, initial-scale=1, maximum-scale=1", :name => "viewport"} + %title= yield(:title) + :css + body { + color: #666; + text-align: center; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + margin: auto; + font-size: 14px; + } + + h1 { + font-size: 56px; + line-height: 100px; + font-weight: normal; + color: #456; + } + + h2 { + font-size: 24px; + color: #666; + line-height: 1.5em; + } + + h3 { + color: #456; + font-size: 20px; + font-weight: normal; + line-height: 28px; + } + + hr { + max-width: 800px; + margin: 18px auto; + border: 0; + border-top: 1px solid #EEE; + border-bottom: 1px solid white; + } + + img { + max-width: 40vw; + display: block; + margin: 40px auto; + } + + .container { + margin: auto 20px; + } + + ul { + margin: auto; + text-align: left; + display:inline-block; + } +%body + = yield diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml index 2a6d9cda379..817e4bebb05 100644 --- a/app/views/layouts/nav/_dashboard.html.haml +++ b/app/views/layouts/nav/_dashboard.html.haml @@ -1,5 +1,4 @@ .nav-sidebar - .sidebar-header Across GitLab %ul.nav = nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: "#{project_tab_class} home"}) do = link_to dashboard_projects_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do diff --git a/app/views/projects/_readme.html.haml b/app/views/projects/_readme.html.haml index 369a847e7d4..b6fb08b68e9 100644 --- a/app/views/projects/_readme.html.haml +++ b/app/views/projects/_readme.html.haml @@ -18,5 +18,5 @@ distributed with computer software, forming part of its documentation. %p We recommend you to - = link_to "add a README", new_readme_path, class: 'underlined-link' + = link_to "add a README", add_special_file_path(@project, file_name: 'README.md'), class: 'underlined-link' file to the repository and GitLab will render it here instead of this message. diff --git a/app/views/projects/boards/components/sidebar/_notifications.html.haml b/app/views/projects/boards/components/sidebar/_notifications.html.haml index 21c9563e9db..a08c7f2af09 100644 --- a/app/views/projects/boards/components/sidebar/_notifications.html.haml +++ b/app/views/projects/boards/components/sidebar/_notifications.html.haml @@ -1,11 +1,7 @@ - if current_user .block.light.subscription{ ":data-url" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '/toggle_subscription'" } - .title + %span.issuable-header-text.hide-collapsed.pull-left Notifications - %button.btn.btn-block.btn-default.js-subscribe-button.issuable-subscribe-button.hide-collapsed{ type: "button" } - {{ issue.subscribed ? 'Unsubscribe' : 'Subscribe' }} - .subscription-status{ ":data-status" => "issue.subscribed ? 'subscribed' : 'unsubscribed'" } - .unsubscribed{ "v-show" => "!issue.subscribed" } - You're not receiving notifications from this thread. - .subscribed{ "v-show" => "issue.subscribed" } - You're receiving notifications because you're subscribed to this thread. + %button.btn.btn-default.pull-right.js-subscribe-button.issuable-subscribe-button.hide-collapsed{ type: "button" } + %span + {{issue.subscribed ? 'Unsubscribe' : 'Subscribe'}} diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index 8d9c15d0dc6..e75547c815f 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -66,8 +66,6 @@ %td - if build.project = link_to build.project.name_with_namespace, admin_namespace_project_path(build.project.namespace, build.project) - - - if admin %td - if build.try(:runner) = runner_link(build.runner) @@ -93,9 +91,8 @@ %span #{time_ago_with_tooltip(build.finished_at)} %td.coverage - - if coverage - - if build.try(:coverage) - #{build.coverage}% + - if coverage && build.try(:coverage) + #{build.coverage}% %td .pull-right diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml index 7a39064adc5..c0a83091c8c 100644 --- a/app/views/projects/empty.html.haml +++ b/app/views/projects/empty.html.haml @@ -16,7 +16,7 @@ %p Otherwise you can start with adding a = succeed ',' do - = link_to "README", new_readme_path, class: 'underlined-link' + = link_to "README", add_special_file_path(@project, file_name: 'README.md'), class: 'underlined-link' a = succeed ',' do = link_to "LICENSE", add_special_file_path(@project, file_name: 'LICENSE'), class: 'underlined-link' diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml index 0b99e9f8756..7f751d9ae2e 100644 --- a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml +++ b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml @@ -1,4 +1,12 @@ -%tr.generic_commit_status +- admin = local_assigns.fetch(:admin, false) +- ref = local_assigns.fetch(:ref, nil) +- commit_sha = local_assigns.fetch(:commit_sha, nil) +- retried = local_assigns.fetch(:retried, false) +- pipeline_link = local_assigns.fetch(:pipeline_link, false) +- stage = local_assigns.fetch(:stage, false) +- coverage = local_assigns.fetch(:coverage, false) + +%tr.generic_commit_status{class: ('retried' if retried)} %td.status - if can?(current_user, :read_commit_status, generic_commit_status) && generic_commit_status.target_url = ci_status_with_icon(generic_commit_status.status, generic_commit_status.target_url) @@ -8,14 +16,35 @@ %td.generic_commit_status-link - if can?(current_user, :read_commit_status, generic_commit_status) && generic_commit_status.target_url = link_to generic_commit_status.target_url do - %strong ##{generic_commit_status.id} + %span.build-link ##{generic_commit_status.id} - else - %strong ##{generic_commit_status.id} + %span.build-link ##{generic_commit_status.id} - - if defined?(retried) && retried + - if ref + - if generic_commit_status.ref + .icon-container + = generic_commit_status.tags.any? ? icon('tag') : icon('code-fork') + = link_to generic_commit_status.ref, namespace_project_commits_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.ref) + - else + .light none + .icon-container.commit-icon + = custom_icon("icon_commit") + + - if commit_sha + = link_to generic_commit_status.short_sha, namespace_project_commit_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.sha), class: "commit-id monospace" + + - if retried = icon('warning', class: 'text-warning has-tooltip', title: 'Status was retried.') - - if defined?(pipeline_link) && pipeline_link + .label-container + - if generic_commit_status.tags.any? + - generic_commit_status.tags.each do |tag| + %span.label.label-primary + = tag + - if retried + %span.label.label-warning retried + + - if pipeline_link %td = link_to pipeline_path(generic_commit_status.pipeline) do %span.pipeline-id ##{generic_commit_status.pipeline.id} @@ -25,25 +54,17 @@ - else %span.monospace API - - if defined?(commit_sha) && commit_sha - %td - = link_to generic_commit_status.short_sha, namespace_project_commit_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.sha), class: "monospace" - - - if defined?(ref) && ref + - if admin %td - - if generic_commit_status.ref - = link_to generic_commit_status.ref, namespace_project_commits_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.ref) - - else - .light none - - - if defined?(runner) && runner + - if generic_commit_status.project + = link_to generic_commit_status.project.name_with_namespace, admin_namespace_project_path(generic_commit_status.project.namespace, generic_commit_status.project) %td - if generic_commit_status.try(:runner) = runner_link(generic_commit_status.runner) - else .light none - - if defined?(stage) && stage + - if stage %td = generic_commit_status.stage @@ -51,24 +72,19 @@ = generic_commit_status.name %td - - if generic_commit_status.tags.any? - - generic_commit_status.tags.each do |tag| - %span.label.label-primary - = tag - - if defined?(retried) && retried - %span.label.label-warning retried - - %td.duration - if generic_commit_status.duration - = icon("clock-o") - = time_interval_in_words(generic_commit_status.duration) + %p.duration + = custom_icon("icon_timer") + = duration_in_numbers(generic_commit_status.duration) - %td.timestamp - if generic_commit_status.finished_at - = icon("calendar") - %span #{time_ago_with_tooltip(generic_commit_status.finished_at)} + %p.finished-at + = icon("calendar") + %span #{time_ago_with_tooltip(generic_commit_status.finished_at)} - - if defined?(coverage) && coverage - %td.coverage - - if generic_commit_status.try(:coverage) - #{generic_commit_status.coverage}% + %td.coverage + - if coverage && generic_commit_status.try(:coverage) + #{generic_commit_status.coverage}% + + %td + -# empty column to match number of columns in ci/builds/_build.html.haml diff --git a/app/views/projects/merge_requests/show/_versions.html.haml b/app/views/projects/merge_requests/show/_versions.html.haml index eab48b78cb3..5cc92595fe0 100644 --- a/app/views/projects/merge_requests/show/_versions.html.haml +++ b/app/views/projects/merge_requests/show/_versions.html.haml @@ -27,7 +27,7 @@ version #{version_index(merge_request_diff)} .monospace #{short_sha(merge_request_diff.head_commit_sha)} %small - #{number_with_delimiter(merge_request_diff.commits.count)} #{'commit'.pluralize(merge_request_diff.commits.count)}, + #{number_with_delimiter(merge_request_diff.commits_count)} #{'commit'.pluralize(merge_request_diff.commits_count)}, = time_ago_with_tooltip(merge_request_diff.created_at) - if @merge_request_diff.base_commit_sha diff --git a/app/views/projects/merge_requests/widget/_open.html.haml b/app/views/projects/merge_requests/widget/_open.html.haml index 20c93930abc..eee711dc5af 100644 --- a/app/views/projects/merge_requests/widget/_open.html.haml +++ b/app/views/projects/merge_requests/widget/_open.html.haml @@ -11,7 +11,7 @@ = render 'projects/merge_requests/widget/open/archived' - elsif @merge_request.branch_missing? = render 'projects/merge_requests/widget/open/missing_branch' - - elsif @merge_request.commits.blank? + - elsif @merge_request.has_no_commits? = render 'projects/merge_requests/widget/open/nothing' - elsif @merge_request.unchecked? = render 'projects/merge_requests/widget/open/check' diff --git a/app/views/projects/pipelines/_with_tabs.html.haml b/app/views/projects/pipelines/_with_tabs.html.haml index 718314701f9..3464e155a1b 100644 --- a/app/views/projects/pipelines/_with_tabs.html.haml +++ b/app/views/projects/pipelines/_with_tabs.html.haml @@ -1,14 +1,17 @@ .tabs-holder - %ul.nav-links.no-top.no-bottom - %li.active - = link_to "Pipeline", "#js-tab-pipeline", data: { target: '#js-tab-pipeline', action: 'pipeline', toggle: 'tab' }, class: 'pipeline-tab' - %li - = link_to "#js-tab-builds", data: { target: '#js-tab-builds', action: 'build', toggle: 'tab' }, class: 'builds-tab' do + %ul.pipelines-tabs.nav-links.no-top.no-bottom + %li.js-pipeline-tab-link + = link_to namespace_project_pipeline_path(@project.namespace, @project, @pipeline), data: { target: 'div#js-tab-pipeline', action: 'pipelines', toggle: 'tab' }, class: 'pipeline-tab' do + Pipeline + %li.js-builds-tab-link + = link_to builds_namespace_project_pipeline_path(@project.namespace, @project, @pipeline), data: {target: 'div#js-tab-builds', action: 'builds', toggle: 'tab' }, class: 'builds-tab' do Builds - %span.badge= pipeline.statuses.count + %span.badge.js-builds-counter= pipeline.statuses.count + + .tab-content - #js-tab-pipeline.tab-pane.active + #js-tab-pipeline.tab-pane .build-content.middle-block.pipeline-graph .pipeline-visualization %ul.stage-column-list diff --git a/app/views/projects/pipelines/show.html.haml b/app/views/projects/pipelines/show.html.haml index 8c6652a5f90..29a41bc664b 100644 --- a/app/views/projects/pipelines/show.html.haml +++ b/app/views/projects/pipelines/show.html.haml @@ -2,7 +2,7 @@ - page_title "Pipeline" = render "projects/pipelines/head" -%div{ class: container_class } +%div.js-pipeline-container{ class: container_class, data: { controller_action: "#{controller.action_name}" } } - if @commit = render "projects/pipelines/info" diff --git a/app/views/projects/pipelines_settings/_badge.html.haml b/app/views/projects/pipelines_settings/_badge.html.haml index 7b7fa56d993..22a3b884520 100644 --- a/app/views/projects/pipelines_settings/_badge.html.haml +++ b/app/views/projects/pipelines_settings/_badge.html.haml @@ -1,4 +1,4 @@ -.row{ class: badge.title.gsub(' ', '-') } +%div{ class: badge.title.gsub(' ', '-') } .col-lg-3.profile-settings-sidebar %h4.prepend-top-0 = badge.title.capitalize diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml index 4e41a15d9f4..c52527332bc 100644 --- a/app/views/projects/wikis/_form.html.haml +++ b/app/views/projects/wikis/_form.html.haml @@ -34,9 +34,6 @@ - if @page && @page.persisted? = f.submit 'Save changes', class: "btn-save btn" .pull-right - - if can?(current_user, :admin_wiki, @project) - = link_to namespace_project_wiki_path(@project.namespace, @project, @page), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-danger btn-grouped" do - Delete = link_to "Cancel", namespace_project_wiki_path(@project.namespace, @project, @page), class: "btn btn-cancel btn-grouped" - else = f.submit 'Create page', class: "btn-create btn" diff --git a/app/views/projects/wikis/_nav.html.haml b/app/views/projects/wikis/_nav.html.haml deleted file mode 100644 index afdef70e1cf..00000000000 --- a/app/views/projects/wikis/_nav.html.haml +++ /dev/null @@ -1,16 +0,0 @@ -= content_for :sub_nav do - .scrolling-tabs-container.sub-nav-scroll - = render 'shared/nav_scroll' - .nav-links.sub-nav.scrolling-tabs - %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) - - = nav_link(path: 'wikis#pages') do - = link_to 'Pages', namespace_project_wikis_pages_path(@project.namespace, @project) - - = nav_link(path: 'wikis#git_access') do - = link_to namespace_project_wikis_git_access_path(@project.namespace, @project) do - Git Access - - = render 'projects/wikis/new' diff --git a/app/views/projects/wikis/_sidebar.html.haml b/app/views/projects/wikis/_sidebar.html.haml new file mode 100644 index 00000000000..cad9c15a49e --- /dev/null +++ b/app/views/projects/wikis/_sidebar.html.haml @@ -0,0 +1,23 @@ +%aside.right-sidebar.right-sidebar-expanded.wiki-sidebar.js-wiki-sidebar + .block.wiki-sidebar-header.append-bottom-default + %a.gutter-toggle.pull-right.visible-xs-block.visible-sm-block.js-sidebar-wiki-toggle{ href: "#" } + = icon('angle-double-right') + + - git_access_url = namespace_project_wikis_git_access_path(@project.namespace, @project) + = link_to git_access_url, class: active_nav_link?(path: 'wikis#git_access') ? 'active' : '' do + = succeed ' ' do + = icon('cloud-download') + Clone repository + + .blocks-container + .block.block-first + %ul.wiki-pages + - @sidebar_wiki_pages.each do |wiki_page| + %li{ class: params[:id] == wiki_page.slug ? 'active' : '' } + = link_to namespace_project_wiki_path(@project.namespace, @project, wiki_page) do + = wiki_page.title.capitalize + .block + = link_to namespace_project_wikis_pages_path(@project.namespace, @project), class: 'btn btn-block' do + More Pages + += render 'projects/wikis/new' diff --git a/app/views/projects/wikis/edit.html.haml b/app/views/projects/wikis/edit.html.haml index 679d6018bef..8cf018da1b7 100644 --- a/app/views/projects/wikis/edit.html.haml +++ b/app/views/projects/wikis/edit.html.haml @@ -1,23 +1,35 @@ - @no_container = true - page_title "Edit", @page.title.capitalize, "Wiki" -= render 'nav' %div{ class: container_class } - .top-area + .wiki-page-header.has-sidebar-toggle + %button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" } + = icon('angle-double-left') + .nav-text - %strong + %h2.wiki-page-title - if @page.persisted? = link_to @page.title.capitalize, namespace_project_wiki_path(@project.namespace, @project, @page) - else = @page.title.capitalize - %span.light - · - Edit Page + %span.light + · + - if @page.persisted? + Edit Page + - else + Create Page .nav-controls - - 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 + - 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 + - if @page.persisted? + = link_to namespace_project_wiki_history_path(@project.namespace, @project, @page), class: "btn" do + Page History + - if can?(current_user, :admin_wiki, @project) + = link_to namespace_project_wiki_path(@project.namespace, @project, @page), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-danger" do + Delete = render 'form' + += render 'sidebar' diff --git a/app/views/projects/wikis/git_access.html.haml b/app/views/projects/wikis/git_access.html.haml index b8811a28dd6..e25d6a48573 100644 --- a/app/views/projects/wikis/git_access.html.haml +++ b/app/views/projects/wikis/git_access.html.haml @@ -1,34 +1,36 @@ - @no_container = true - page_title "Git Access", "Wiki" -= render 'nav' %div{ class: container_class } - .sub-header-block - %span.oneline - Git access for + .wiki-page-header.has-sidebar-toggle + %button.btn.btn-default.visible-xs.visible-sm.pull-right.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" } + = icon('angle-double-left') + + .git-access-header + Clone repository %strong= @project_wiki.path_with_namespace - .pull-right - = render "shared/clone_panel", project: @project_wiki + = render "shared/clone_panel", project: @project_wiki + + .wiki-git-access + %h3 Install Gollum + %pre.dark + :preserve + gem install gollum - .prepend-top-default - %fieldset - %legend Install Gollum: - %pre.dark - :preserve - gem install gollum + %h3 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} + %h3 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 += render 'sidebar' diff --git a/app/views/projects/wikis/history.html.haml b/app/views/projects/wikis/history.html.haml index 4c0b14e2c42..dd7213622c1 100644 --- a/app/views/projects/wikis/history.html.haml +++ b/app/views/projects/wikis/history.html.haml @@ -1,13 +1,16 @@ - page_title "History", @page.title.capitalize, "Wiki" -= render 'nav' + %div{ class: container_class } - .top-area + .wiki-page-header.has-sidebar-toggle + %button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" } + = icon('angle-double-left') + .nav-text - %strong + %h2.wiki-page-title = link_to @page.title.capitalize, namespace_project_wiki_path(@project.namespace, @project, @page) - %span.light - · - History + %span.light + · + History .table-holder %table.table @@ -35,3 +38,5 @@ %td %strong = @page.page.wiki.page(@page.page.name, commit.id).try(:format) + += render 'sidebar' diff --git a/app/views/projects/wikis/pages.html.haml b/app/views/projects/wikis/pages.html.haml index 9c10acd4cb6..e1eaffc6884 100644 --- a/app/views/projects/wikis/pages.html.haml +++ b/app/views/projects/wikis/pages.html.haml @@ -1,9 +1,18 @@ - @no_container = true - page_title "Pages", "Wiki" -= render 'nav' - %div{ class: container_class } + .wiki-page-header + + .nav-text + %h2.wiki-page-title + Wiki Pages + + .nav-controls + = link_to namespace_project_wikis_git_access_path(@project.namespace, @project), class: 'btn' do + = icon('cloud-download') + Clone repository + %ul.content-list - @wiki_pages.each do |wiki_page| %li diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml index 5cebb538cf5..1b6dceee241 100644 --- a/app/views/projects/wikis/show.html.haml +++ b/app/views/projects/wikis/show.html.haml @@ -1,15 +1,19 @@ - @no_container = true - page_title @page.title.capitalize, "Wiki" -= render 'nav' %div{ class: container_class } - .top-area + .wiki-page-header.has-sidebar-toggle + %button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" } + = icon('angle-double-left') + .nav-text - %strong= @page.title.capitalize + %h2.wiki-page-title= @page.title.capitalize %span.wiki-last-edit-by - · - last edited by #{@page.commit.author.name} #{time_ago_with_tooltip(@page.commit.authored_date)} + Last edited by + %strong + #{@page.commit.author.name} + #{time_ago_with_tooltip(@page.commit.authored_date)} .nav-controls = render 'main_links' @@ -19,8 +23,9 @@ 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) + += render 'sidebar' diff --git a/app/views/shared/_issues.html.haml b/app/views/shared/_issues.html.haml index baa6d5f8206..26b349e8a62 100644 --- a/app/views/shared/_issues.html.haml +++ b/app/views/shared/_issues.html.haml @@ -1,4 +1,4 @@ -- if @issues.reorder(nil).any? +- if @issues.to_a.any? - @issues.group_by(&:project).each do |group| .panel.panel-default.panel-small - project = group[0] diff --git a/app/views/shared/_merge_requests.html.haml b/app/views/shared/_merge_requests.html.haml index ca3178395c1..2f3605b4d27 100644 --- a/app/views/shared/_merge_requests.html.haml +++ b/app/views/shared/_merge_requests.html.haml @@ -1,4 +1,4 @@ -- if @merge_requests.reorder(nil).any? +- if @merge_requests.to_a.any? - @merge_requests.group_by(&:target_project).each do |group| .panel.panel-default.panel-small - project = group[0] diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index 3d515a05d46..2f05093f435 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -40,30 +40,7 @@ title: 'Moving an issue will copy the discussion to a different project and close it here. All participants will be notified of the new location.' } = icon('question-circle') -- if issuable.is_a?(MergeRequest) && !issuable.closed_without_fork? - %hr - - if @merge_request.new_record? - .form-group - = form.label :source_branch, class: 'control-label' - .col-sm-10 - .issuable-form-select-holder - = form.select(:source_branch, [@merge_request.source_branch], { }, { class: 'source_branch select2 span2', disabled: true }) - .form-group - = form.label :target_branch, class: 'control-label' - .col-sm-10 - .issuable-form-select-holder - = form.select(:target_branch, @merge_request.target_branches, { include_blank: true }, { class: 'target_branch select2 span2', disabled: @merge_request.new_record?, data: {placeholder: "Select branch"} }) - - if @merge_request.new_record? - - = link_to 'Change branches', mr_change_branches_path(@merge_request) - - if @merge_request.can_remove_source_branch?(current_user) - .form-group - .col-sm-10.col-sm-offset-2 - .checkbox - = label_tag 'merge_request[force_remove_source_branch]' do - = hidden_field_tag 'merge_request[force_remove_source_branch]', '0', id: nil - = check_box_tag 'merge_request[force_remove_source_branch]', '1', @merge_request.force_remove_source_branch? - Remove source branch when merge request is accepted. += render 'shared/issuable/form/branch_chooser', issuable: issuable, form: form - is_footer = !(issuable.is_a?(MergeRequest) && issuable.new_record?) .row-content-block{class: (is_footer ? "footer-block" : "middle-block")} diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 02427650219..958f8413e1d 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -144,16 +144,11 @@ .block.light.subscription{data: {url: toggle_subscription_path(issuable)}} .sidebar-collapsed-icon = icon('rss') - .title.hide-collapsed + %span.issuable-header-text.hide-collapsed.pull-left Notifications - subscribtion_status = subscribed ? 'subscribed' : 'unsubscribed' - %button.btn.btn-block.btn-default.js-subscribe-button.issuable-subscribe-button.hide-collapsed{ type: "button" } + %button.btn.btn-default.pull-right.js-subscribe-button.issuable-subscribe-button.hide-collapsed{ type: "button" } %span= subscribed ? 'Unsubscribe' : 'Subscribe' - .subscription-status.hide-collapsed{data: {status: subscribtion_status}} - .unsubscribed{class: ( 'hidden' if subscribed )} - You're not receiving notifications from this thread. - .subscribed{class: ( 'hidden' unless subscribed )} - You're receiving notifications because you're subscribed to this thread. - project_ref = cross_project_reference(@project, issuable) .block.project-reference @@ -170,6 +165,6 @@ new MilestoneSelect('{"namespace":"#{@project.namespace.path}","path":"#{@project.path}"}'); new LabelsSelect(); new IssuableContext('#{escape_javascript(current_user.to_json(only: [:username, :id, :name]))}'); - new Subscription('.subscription') + gl.Subscription.bindAll('.subscription'); new gl.DueDateSelectors(); sidebar = new Sidebar(); diff --git a/app/views/shared/issuable/form/_branch_chooser.html.haml b/app/views/shared/issuable/form/_branch_chooser.html.haml new file mode 100644 index 00000000000..b757893ea04 --- /dev/null +++ b/app/views/shared/issuable/form/_branch_chooser.html.haml @@ -0,0 +1,30 @@ +- issuable = local_assigns.fetch(:issuable) +- form = local_assigns.fetch(:form) + +- return unless issuable.is_a?(MergeRequest) +- return if issuable.closed_without_fork? + +%hr +- if issuable.new_record? + .form-group + = form.label :source_branch, class: 'control-label' + .col-sm-10 + .issuable-form-select-holder + = form.select(:source_branch, [issuable.source_branch], {}, { class: 'source_branch select2 span2', disabled: true }) +.form-group + = form.label :target_branch, class: 'control-label' + .col-sm-10 + .issuable-form-select-holder + = form.select(:target_branch, issuable.target_branches, { include_blank: true }, { class: 'target_branch select2 span2', disabled: issuable.new_record?, data: { placeholder: "Select branch" }}) + - if issuable.new_record? + + = link_to 'Change branches', mr_change_branches_path(issuable) + +- if issuable.can_remove_source_branch?(current_user) + .form-group + .col-sm-10.col-sm-offset-2 + .checkbox + = label_tag 'merge_request[force_remove_source_branch]' do + = hidden_field_tag 'merge_request[force_remove_source_branch]', '0', id: nil + = check_box_tag 'merge_request[force_remove_source_branch]', '1', issuable.force_remove_source_branch? + Remove source branch when merge request is accepted. diff --git a/app/workers/process_commit_worker.rb b/app/workers/process_commit_worker.rb index 071741fbacd..e9a5bd7f24e 100644 --- a/app/workers/process_commit_worker.rb +++ b/app/workers/process_commit_worker.rb @@ -10,9 +10,10 @@ class ProcessCommitWorker # project_id - The ID of the project this commit belongs to. # user_id - The ID of the user that pushed the commit. - # commit_sha - The SHA1 of the commit to process. + # commit_hash - Hash containing commit details to use for constructing a + # Commit object without having to use the Git repository. # default - The data was pushed to the default branch. - def perform(project_id, user_id, commit_sha, default = false) + def perform(project_id, user_id, commit_hash, default = false) project = Project.find_by(id: project_id) return unless project @@ -21,10 +22,7 @@ class ProcessCommitWorker return unless user - commit = find_commit(project, commit_sha) - - return unless commit - + commit = build_commit(project, commit_hash) author = commit.author || user process_commit_message(project, commit, user, author, default) @@ -59,9 +57,18 @@ class ProcessCommitWorker update_all(first_mentioned_in_commit_at: commit.committed_date) end - private + def build_commit(project, hash) + date_suffix = '_date' + + # When processing Sidekiq payloads various timestamps are stored as Strings. + # Commit in turn expects Time-like instances upon input, so we have to + # manually parse these values. + hash.each do |key, value| + if key.to_s.end_with?(date_suffix) && value.is_a?(String) + hash[key] = Time.parse(value) + end + end - def find_commit(project, sha) - project.commit(sha) + Commit.from_hash(hash, project) end end diff --git a/bin/changelog b/bin/changelog index b6586ebb6aa..e07b1ad237a 100755 --- a/bin/changelog +++ b/bin/changelog @@ -158,7 +158,7 @@ class ChangelogEntry end def branch_name - @branch_name ||= %x{git symbolic-ref HEAD}.strip.sub(%r{\Arefs/heads/}, '') + @branch_name ||= %x{git symbolic-ref --short HEAD}.strip end end diff --git a/changelogs/unreleased/18546-update-wiki-page-design.yml b/changelogs/unreleased/18546-update-wiki-page-design.yml new file mode 100644 index 00000000000..c76e17340f2 --- /dev/null +++ b/changelogs/unreleased/18546-update-wiki-page-design.yml @@ -0,0 +1,4 @@ +--- +title: Update wiki page design +merge_request: 7429 +author: diff --git a/changelogs/unreleased/22719-provide-a-new-gitlab-workhorse-install-rake-task-similar-to-gitlab-shell-install.yml b/changelogs/unreleased/22719-provide-a-new-gitlab-workhorse-install-rake-task-similar-to-gitlab-shell-install.yml new file mode 100644 index 00000000000..54bd313f075 --- /dev/null +++ b/changelogs/unreleased/22719-provide-a-new-gitlab-workhorse-install-rake-task-similar-to-gitlab-shell-install.yml @@ -0,0 +1,4 @@ +--- +title: New `gitlab:workhorse:install` rake task +merge_request: 6574 +author: diff --git a/changelogs/unreleased/22781-user-generated-permalinks.yml b/changelogs/unreleased/22781-user-generated-permalinks.yml new file mode 100644 index 00000000000..e46739e48e3 --- /dev/null +++ b/changelogs/unreleased/22781-user-generated-permalinks.yml @@ -0,0 +1,4 @@ +--- +title: Prevent DOM ID collisions resulting from user-generated content anchors +merge_request: 7631 +author: diff --git a/changelogs/unreleased/23500-enable-colorvariable.yml b/changelogs/unreleased/23500-enable-colorvariable.yml new file mode 100644 index 00000000000..98e22a934b8 --- /dev/null +++ b/changelogs/unreleased/23500-enable-colorvariable.yml @@ -0,0 +1,4 @@ +--- +title: Enable ColorVariable in scss-lint +merge_request: +author: Sam Rose diff --git a/changelogs/unreleased/23696-fix-diff-view-highlighting.yml b/changelogs/unreleased/23696-fix-diff-view-highlighting.yml new file mode 100644 index 00000000000..db523caffed --- /dev/null +++ b/changelogs/unreleased/23696-fix-diff-view-highlighting.yml @@ -0,0 +1,4 @@ +--- +title: Fix diff view permalink highlighting +merge_request: 7090 +author: diff --git a/changelogs/unreleased/23718-backup-rake-task-human-readable.yml b/changelogs/unreleased/23718-backup-rake-task-human-readable.yml new file mode 100644 index 00000000000..2e7583244ac --- /dev/null +++ b/changelogs/unreleased/23718-backup-rake-task-human-readable.yml @@ -0,0 +1,4 @@ +--- +title: Add Human Readable format for rake backup +merge_request: 7188 +author: David GerĹ‘ diff --git a/changelogs/unreleased/24281-issue-merge-request-sidebar-subscribe-button-style-improvement.yml b/changelogs/unreleased/24281-issue-merge-request-sidebar-subscribe-button-style-improvement.yml new file mode 100644 index 00000000000..2227c81bd34 --- /dev/null +++ b/changelogs/unreleased/24281-issue-merge-request-sidebar-subscribe-button-style-improvement.yml @@ -0,0 +1,4 @@ +--- +title: Remove the help text under the sidebar subscribe button and style it inline +merge_request: 7389 +author: diff --git a/changelogs/unreleased/24669-merge-request-dashboard-page-takes-over-a-minute-to-load.yml b/changelogs/unreleased/24669-merge-request-dashboard-page-takes-over-a-minute-to-load.yml new file mode 100644 index 00000000000..01b19a47ecd --- /dev/null +++ b/changelogs/unreleased/24669-merge-request-dashboard-page-takes-over-a-minute-to-load.yml @@ -0,0 +1,4 @@ +--- +title: Speed up issuable dashboards +merge_request: +author: diff --git a/changelogs/unreleased/24710-fix-generic-commit-status-table-row.yml b/changelogs/unreleased/24710-fix-generic-commit-status-table-row.yml new file mode 100644 index 00000000000..07cb53d5278 --- /dev/null +++ b/changelogs/unreleased/24710-fix-generic-commit-status-table-row.yml @@ -0,0 +1,4 @@ +--- +title: Update generic/external build status to match normal build status template +merge_request: 7811 +author: diff --git a/changelogs/unreleased/24726-remove-across-gitlab.yml b/changelogs/unreleased/24726-remove-across-gitlab.yml new file mode 100644 index 00000000000..6436e4b688f --- /dev/null +++ b/changelogs/unreleased/24726-remove-across-gitlab.yml @@ -0,0 +1,4 @@ +--- +title: 24726 Remove Across GitLab from side navigation +merge_request: +author: diff --git a/changelogs/unreleased/24813-project-members-with-developer-access-can-no-longer-create-tags.yml b/changelogs/unreleased/24813-project-members-with-developer-access-can-no-longer-create-tags.yml deleted file mode 100644 index 9254db40742..00000000000 --- a/changelogs/unreleased/24813-project-members-with-developer-access-can-no-longer-create-tags.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Pass tag SHA to post-receive hook when tag is created via UI -merge_request: 7700 -author: diff --git a/changelogs/unreleased/24814-pipeline-tabs.yml b/changelogs/unreleased/24814-pipeline-tabs.yml new file mode 100644 index 00000000000..f85e7576905 --- /dev/null +++ b/changelogs/unreleased/24814-pipeline-tabs.yml @@ -0,0 +1,4 @@ +--- +title: Fix Cicking on tabs on pipeline page should set URL +merge_request: 7709 +author: diff --git a/changelogs/unreleased/24844-environments-date.yml b/changelogs/unreleased/24844-environments-date.yml new file mode 100644 index 00000000000..2bc23d40a68 --- /dev/null +++ b/changelogs/unreleased/24844-environments-date.yml @@ -0,0 +1,4 @@ +--- +title: Fixes Environments displaying incorrect date since 8.14 upgrade +merge_request: +author: diff --git a/changelogs/unreleased/24860-actionview-template-error-undefined-method-size-for-nil-nilclass.yml b/changelogs/unreleased/24860-actionview-template-error-undefined-method-size-for-nil-nilclass.yml deleted file mode 100644 index 4b4aea79380..00000000000 --- a/changelogs/unreleased/24860-actionview-template-error-undefined-method-size-for-nil-nilclass.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Prevent error when submitting a merge request and pipeline is not defined -merge_request: 7707 -author: diff --git a/changelogs/unreleased/24921-hide-prompt-to-add-ssh-key-if-ssh-protocol-is-disabled.yml b/changelogs/unreleased/24921-hide-prompt-to-add-ssh-key-if-ssh-protocol-is-disabled.yml new file mode 100644 index 00000000000..4d4019e770e --- /dev/null +++ b/changelogs/unreleased/24921-hide-prompt-to-add-ssh-key-if-ssh-protocol-is-disabled.yml @@ -0,0 +1,4 @@ +--- +title: Don't display prompt to add SSH keys if SSH protocol is disabled +merge_request: 7840 +author: Andrew Smith (EspadaV8) diff --git a/changelogs/unreleased/25031-do-not-raise-error-in-autocomplete.yml b/changelogs/unreleased/25031-do-not-raise-error-in-autocomplete.yml new file mode 100644 index 00000000000..862de7c5db1 --- /dev/null +++ b/changelogs/unreleased/25031-do-not-raise-error-in-autocomplete.yml @@ -0,0 +1,4 @@ +--- +title: Do not raise error in AutocompleteController#users when not authorized +merge_request: 7817 +author: Semyon Pupkov diff --git a/changelogs/unreleased/25055-pipelines-info-missing-from-mr-widget.yml b/changelogs/unreleased/25055-pipelines-info-missing-from-mr-widget.yml deleted file mode 100644 index dad9db0ffef..00000000000 --- a/changelogs/unreleased/25055-pipelines-info-missing-from-mr-widget.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix pipelines info being hidden in merge request widget -merge_request: 7808 -author: diff --git a/changelogs/unreleased/25098-header-margins-on-pipeline-settings.yml b/changelogs/unreleased/25098-header-margins-on-pipeline-settings.yml new file mode 100644 index 00000000000..1799fad1631 --- /dev/null +++ b/changelogs/unreleased/25098-header-margins-on-pipeline-settings.yml @@ -0,0 +1,5 @@ +--- +title: Adjusted margins for Build Status and Coverage Report rows to match those of + the CI/CD Pipeline row +merge_request: +author: Ryan Harris diff --git a/changelogs/unreleased/25199-fix-broken-urls-in-help-page.yml b/changelogs/unreleased/25199-fix-broken-urls-in-help-page.yml new file mode 100644 index 00000000000..58efd9113f2 --- /dev/null +++ b/changelogs/unreleased/25199-fix-broken-urls-in-help-page.yml @@ -0,0 +1,4 @@ +--- +title: Don't change relative URLs to absolute URLs in the Help page +merge_request: +author: diff --git a/changelogs/unreleased/25251-actionview-template-error-undefined-method-text-for-nil-nilclass.yml b/changelogs/unreleased/25251-actionview-template-error-undefined-method-text-for-nil-nilclass.yml new file mode 100644 index 00000000000..7f1c417bc77 --- /dev/null +++ b/changelogs/unreleased/25251-actionview-template-error-undefined-method-text-for-nil-nilclass.yml @@ -0,0 +1,5 @@ +--- +title: 'Fixes "ActionView::Template::Error: undefined method `text?` for nil:NilClass" + on MR pages' +merge_request: +author: diff --git a/changelogs/unreleased/25264-ref-commit.yml b/changelogs/unreleased/25264-ref-commit.yml new file mode 100644 index 00000000000..13a33da9801 --- /dev/null +++ b/changelogs/unreleased/25264-ref-commit.yml @@ -0,0 +1,4 @@ +--- +title: Change ref property to commitRef in vue commit component +merge_request: 7901 +author: diff --git a/changelogs/unreleased/4269-public-api.yml b/changelogs/unreleased/4269-public-api.yml new file mode 100644 index 00000000000..560bc6a4f13 --- /dev/null +++ b/changelogs/unreleased/4269-public-api.yml @@ -0,0 +1,4 @@ +--- +title: Allow public access to some Project API endpoints +merge_request: 7843 +author: diff --git a/changelogs/unreleased/api-branch-status.yml b/changelogs/unreleased/api-branch-status.yml new file mode 100644 index 00000000000..c5763345a22 --- /dev/null +++ b/changelogs/unreleased/api-branch-status.yml @@ -0,0 +1,4 @@ +--- +title: 'API: Expose merge status for branch API' +merge_request: +author: Robert Schilling diff --git a/changelogs/unreleased/api-expose-commiter-details.yml b/changelogs/unreleased/api-expose-commiter-details.yml new file mode 100644 index 00000000000..5ee34adc5c9 --- /dev/null +++ b/changelogs/unreleased/api-expose-commiter-details.yml @@ -0,0 +1,4 @@ +--- +title: 'API: Expose committer details for commits' +merge_request: +author: Robert Schilling diff --git a/changelogs/unreleased/boards-issue-sorting.yml b/changelogs/unreleased/boards-issue-sorting.yml deleted file mode 100644 index fb7dc2f9190..00000000000 --- a/changelogs/unreleased/boards-issue-sorting.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fixed issue boards issue sorting when dragging issue into list -merge_request: -author: diff --git a/changelogs/unreleased/cleanup-common_utils-js.yml b/changelogs/unreleased/cleanup-common_utils-js.yml new file mode 100644 index 00000000000..54d81b76c28 --- /dev/null +++ b/changelogs/unreleased/cleanup-common_utils-js.yml @@ -0,0 +1,4 @@ +--- +title: Clean up common_utils.js +merge_request: 7318 +author: winniehell diff --git a/changelogs/unreleased/comments-fixture.yml b/changelogs/unreleased/comments-fixture.yml new file mode 100644 index 00000000000..824c1c88a60 --- /dev/null +++ b/changelogs/unreleased/comments-fixture.yml @@ -0,0 +1,4 @@ +--- +title: Replace static fixture for notes_spec +merge_request: 7683 +author: winniehell diff --git a/changelogs/unreleased/events-cache-invalidation.yml b/changelogs/unreleased/events-cache-invalidation.yml deleted file mode 100644 index 2b30f4dcbce..00000000000 --- a/changelogs/unreleased/events-cache-invalidation.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Remove caching of events data -merge_request: 6578 -author: diff --git a/changelogs/unreleased/fix-ca-no-date.yml b/changelogs/unreleased/fix-ca-no-date.yml deleted file mode 100644 index 6de4a56ac0d..00000000000 --- a/changelogs/unreleased/fix-ca-no-date.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix for error thrown in cycle analytics events if build has not started -merge_request: -author: diff --git a/changelogs/unreleased/fix-github-branch-formatter.yml b/changelogs/unreleased/fix-github-branch-formatter.yml new file mode 100644 index 00000000000..c8698f507de --- /dev/null +++ b/changelogs/unreleased/fix-github-branch-formatter.yml @@ -0,0 +1,4 @@ +--- +title: Fix branch validation for GitHub PR where repo/fork was renamed/deleted +merge_request: +author: diff --git a/changelogs/unreleased/fixed-commit-timeago.yml b/changelogs/unreleased/fixed-commit-timeago.yml deleted file mode 100644 index 295d8db63d0..00000000000 --- a/changelogs/unreleased/fixed-commit-timeago.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fixed commit timeago not rendering after initial page -merge_request: -author: diff --git a/changelogs/unreleased/issue_24363.yml b/changelogs/unreleased/issue_24363.yml new file mode 100644 index 00000000000..0298890b477 --- /dev/null +++ b/changelogs/unreleased/issue_24363.yml @@ -0,0 +1,4 @@ +--- +title: Fix appearance in error pages +merge_request: +author: Luis Alonso Chavez Armendariz diff --git a/changelogs/unreleased/mr-origin-7855.yml b/changelogs/unreleased/mr-origin-7855.yml new file mode 100644 index 00000000000..0fdc6153d55 --- /dev/null +++ b/changelogs/unreleased/mr-origin-7855.yml @@ -0,0 +1,4 @@ +--- +title: Provides a sensible default message when adding a README to a project +merge_request: 7903 +author: diff --git a/changelogs/unreleased/process-commit-worker-improvements.yml b/changelogs/unreleased/process-commit-worker-improvements.yml new file mode 100644 index 00000000000..0038c6e34e6 --- /dev/null +++ b/changelogs/unreleased/process-commit-worker-improvements.yml @@ -0,0 +1,4 @@ +--- +title: Pass commit data to ProcessCommitWorker to reduce Git overhead +merge_request: 7744 +author: diff --git a/changelogs/unreleased/refresh-authorizations-with-lease.yml b/changelogs/unreleased/refresh-authorizations-with-lease.yml deleted file mode 100644 index bb9b77018e3..00000000000 --- a/changelogs/unreleased/refresh-authorizations-with-lease.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Use a Redis lease for updating authorized projects -merge_request: 7733 -author: diff --git a/changelogs/unreleased/rephrase-system-notes.yml b/changelogs/unreleased/rephrase-system-notes.yml deleted file mode 100644 index e77c3a31cb4..00000000000 --- a/changelogs/unreleased/rephrase-system-notes.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Rephrase some system notes to be compatible with new system note style -merge_request: 7692 -author: diff --git a/changelogs/unreleased/resolve-discussions-timeago.yml b/changelogs/unreleased/resolve-discussions-timeago.yml deleted file mode 100644 index ffedeb93f1d..00000000000 --- a/changelogs/unreleased/resolve-discussions-timeago.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fixed timeago not rendering when resolving a discussion -merge_request: -author: diff --git a/changelogs/unreleased/right-sidebar-fixture.yml b/changelogs/unreleased/right-sidebar-fixture.yml new file mode 100644 index 00000000000..46a3e459fef --- /dev/null +++ b/changelogs/unreleased/right-sidebar-fixture.yml @@ -0,0 +1,4 @@ +--- +title: Replace static fixture for right_sidebar_spec +merge_request: 7687 +author: winniehell diff --git a/changelogs/unreleased/sh-update-sidekiq-cron.yml b/changelogs/unreleased/sh-update-sidekiq-cron.yml deleted file mode 100644 index d79ba817a18..00000000000 --- a/changelogs/unreleased/sh-update-sidekiq-cron.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Update Sidekiq-cron to fix compatibility issues with Sidekiq 4.2.1 -merge_request: -author: diff --git a/changelogs/unreleased/shortcuts-issuable-fixture.yml b/changelogs/unreleased/shortcuts-issuable-fixture.yml new file mode 100644 index 00000000000..88945600886 --- /dev/null +++ b/changelogs/unreleased/shortcuts-issuable-fixture.yml @@ -0,0 +1,4 @@ +--- +title: Replace static fixture for shortcuts_issuable_spec +merge_request: 7685 +author: winniehell diff --git a/changelogs/unreleased/timeout-merge-request-for-binary-file.yml b/changelogs/unreleased/timeout-merge-request-for-binary-file.yml deleted file mode 100644 index 5161265d1bd..00000000000 --- a/changelogs/unreleased/timeout-merge-request-for-binary-file.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Timeout creating and viewing merge request for binary file -merge_request: -author: diff --git a/changelogs/unreleased/update-api-spec-files.yml b/changelogs/unreleased/update-api-spec-files.yml new file mode 100644 index 00000000000..349d866cf22 --- /dev/null +++ b/changelogs/unreleased/update-api-spec-files.yml @@ -0,0 +1,4 @@ +--- +title: Update API spec files to describe the correct class +merge_request: +author: Livier diff --git a/changelogs/unreleased/use-st-commits-where-possible.yml b/changelogs/unreleased/use-st-commits-where-possible.yml new file mode 100644 index 00000000000..e4395461560 --- /dev/null +++ b/changelogs/unreleased/use-st-commits-where-possible.yml @@ -0,0 +1,5 @@ +--- +title: Replace references to MergeRequestDiff#commits with st_commits when we care + only about the number of commits +merge_request: 7668 +author: diff --git a/changelogs/unreleased/workhorse-v1-0-1.yml b/changelogs/unreleased/workhorse-v1-0-1.yml deleted file mode 100644 index c26c2d45b1d..00000000000 --- a/changelogs/unreleased/workhorse-v1-0-1.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Update GitLab Workhorse to v1.0.1 -merge_request: 7759 -author: diff --git a/changelogs/unreleased/zj-issue-new-over-issue-create.yml b/changelogs/unreleased/zj-issue-new-over-issue-create.yml new file mode 100644 index 00000000000..9dd463e4efa --- /dev/null +++ b/changelogs/unreleased/zj-issue-new-over-issue-create.yml @@ -0,0 +1,4 @@ +--- +title: Accept issue new as command to create an issue +merge_request: +author: diff --git a/config/initializers/ar_monkey_patch.rb b/config/initializers/ar_monkey_patch.rb index 0da584626ee..6979f4641b0 100644 --- a/config/initializers/ar_monkey_patch.rb +++ b/config/initializers/ar_monkey_patch.rb @@ -52,6 +52,23 @@ module ActiveRecord raise end end + + # This is patched because we need it to query `lock_version IS NULL` + # rather than `lock_version = 0` whenever lock_version is NULL. + def relation_for_destroy + return super unless locking_enabled? + + column_name = self.class.locking_column + super.where(self.class.arel_table[column_name].eq(self[column_name])) + end + end + + # This is patched because we want `lock_version` default to `NULL` + # rather than `0` + class LockingType < SimpleDelegator + def type_cast_from_database(value) + super + end end end end diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb index b87b31d9697..1d7a3f03ace 100644 --- a/config/initializers/sidekiq.rb +++ b/config/initializers/sidekiq.rb @@ -61,5 +61,5 @@ begin end end end -rescue Redis::BaseError, SocketError +rescue Redis::BaseError, SocketError, Errno::ENOENT, Errno::EAFNOSUPPORT, Errno::ECONNRESET, Errno::ECONNREFUSED end diff --git a/config/routes/project.rb b/config/routes/project.rb index 1336484a399..0754f0ec3b0 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -129,6 +129,7 @@ constraints(ProjectUrlConstrainer.new) do member do post :cancel post :retry + get :builds end end diff --git a/db/migrate/20130319214458_create_forked_project_links.rb b/db/migrate/20130319214458_create_forked_project_links.rb index 41b0b700a6f..065a5e08243 100644 --- a/db/migrate/20130319214458_create_forked_project_links.rb +++ b/db/migrate/20130319214458_create_forked_project_links.rb @@ -1,5 +1,7 @@ # rubocop:disable all class CreateForkedProjectLinks < ActiveRecord::Migration + DOWNTIME = false + def change create_table :forked_project_links do |t| t.integer :forked_to_project_id, null: false diff --git a/db/migrate/20130506090604_create_deploy_keys_projects.rb b/db/migrate/20130506090604_create_deploy_keys_projects.rb index f2e416d3b6f..8b9662a27c3 100644 --- a/db/migrate/20130506090604_create_deploy_keys_projects.rb +++ b/db/migrate/20130506090604_create_deploy_keys_projects.rb @@ -1,5 +1,7 @@ # rubocop:disable all class CreateDeployKeysProjects < ActiveRecord::Migration + DOWNTIME = false + def change create_table :deploy_keys_projects do |t| t.integer :deploy_key_id, null: false diff --git a/db/migrate/20130617095603_create_users_groups.rb b/db/migrate/20130617095603_create_users_groups.rb index cb098aa9bf9..4ba7d0c9461 100644 --- a/db/migrate/20130617095603_create_users_groups.rb +++ b/db/migrate/20130617095603_create_users_groups.rb @@ -1,5 +1,7 @@ # rubocop:disable all class CreateUsersGroups < ActiveRecord::Migration + DOWNTIME = false + def change create_table :users_groups do |t| t.integer :group_access, null: false diff --git a/db/migrate/20130711063759_create_project_group_links.rb b/db/migrate/20130711063759_create_project_group_links.rb index 8da7ff6f4cd..efccb2aa938 100644 --- a/db/migrate/20130711063759_create_project_group_links.rb +++ b/db/migrate/20130711063759_create_project_group_links.rb @@ -1,5 +1,7 @@ # rubocop:disable all class CreateProjectGroupLinks < ActiveRecord::Migration + DOWNTIME = false + def change create_table :project_group_links do |t| t.integer :project_id, null: false diff --git a/db/migrate/20131112114325_create_broadcast_messages.rb b/db/migrate/20131112114325_create_broadcast_messages.rb index 4ada40f1b66..ad2549e53af 100644 --- a/db/migrate/20131112114325_create_broadcast_messages.rb +++ b/db/migrate/20131112114325_create_broadcast_messages.rb @@ -1,5 +1,7 @@ # rubocop:disable all class CreateBroadcastMessages < ActiveRecord::Migration + DOWNTIME = false + def change create_table :broadcast_messages do |t| t.text :message, null: false diff --git a/db/migrate/20140122112253_create_merge_request_diffs.rb b/db/migrate/20140122112253_create_merge_request_diffs.rb index 68448c91529..6c7a92b6950 100644 --- a/db/migrate/20140122112253_create_merge_request_diffs.rb +++ b/db/migrate/20140122112253_create_merge_request_diffs.rb @@ -1,5 +1,7 @@ # rubocop:disable all class CreateMergeRequestDiffs < ActiveRecord::Migration + DOWNTIME = false + def up create_table :merge_request_diffs do |t| t.string :state, null: false, default: 'collected' diff --git a/db/migrate/20140209025651_create_emails.rb b/db/migrate/20140209025651_create_emails.rb index 48d14682628..51886f8fc89 100644 --- a/db/migrate/20140209025651_create_emails.rb +++ b/db/migrate/20140209025651_create_emails.rb @@ -1,10 +1,12 @@ # rubocop:disable all class CreateEmails < ActiveRecord::Migration + DOWNTIME = false + def change create_table :emails do |t| t.integer :user_id, null: false t.string :email, null: false - + t.timestamps null: true end diff --git a/db/migrate/20140625115202_create_users_star_projects.rb b/db/migrate/20140625115202_create_users_star_projects.rb index c50bc4bd614..d4f3fe5ac62 100644 --- a/db/migrate/20140625115202_create_users_star_projects.rb +++ b/db/migrate/20140625115202_create_users_star_projects.rb @@ -1,5 +1,7 @@ # rubocop:disable all class CreateUsersStarProjects < ActiveRecord::Migration + DOWNTIME = false + def change create_table :users_star_projects do |t| t.integer :project_id, null: false diff --git a/db/migrate/20140729134820_create_labels.rb b/db/migrate/20140729134820_create_labels.rb index 589aced0d76..66d20e741a6 100644 --- a/db/migrate/20140729134820_create_labels.rb +++ b/db/migrate/20140729134820_create_labels.rb @@ -1,5 +1,7 @@ # rubocop:disable all class CreateLabels < ActiveRecord::Migration + DOWNTIME = false + def change create_table :labels do |t| t.string :title diff --git a/db/migrate/20140729140420_create_label_links.rb b/db/migrate/20140729140420_create_label_links.rb index abdbaa1bd1a..dacd9f2e4b6 100644 --- a/db/migrate/20140729140420_create_label_links.rb +++ b/db/migrate/20140729140420_create_label_links.rb @@ -1,5 +1,7 @@ # rubocop:disable all class CreateLabelLinks < ActiveRecord::Migration + DOWNTIME = false + def change create_table :label_links do |t| t.integer :label_id diff --git a/db/migrate/20140914113604_add_members_table.rb b/db/migrate/20140914113604_add_members_table.rb index fb0876dd520..0f76bb0ef79 100644 --- a/db/migrate/20140914113604_add_members_table.rb +++ b/db/migrate/20140914113604_add_members_table.rb @@ -1,5 +1,7 @@ # rubocop:disable all class AddMembersTable < ActiveRecord::Migration + DOWNTIME = false + def change create_table :members do |t| t.integer :access_level, null: false diff --git a/db/migrate/20140914173417_remove_old_member_tables.rb b/db/migrate/20140914173417_remove_old_member_tables.rb index 067dc21ccf1..d2ab326ef1f 100644 --- a/db/migrate/20140914173417_remove_old_member_tables.rb +++ b/db/migrate/20140914173417_remove_old_member_tables.rb @@ -1,5 +1,7 @@ # rubocop:disable all class RemoveOldMemberTables < ActiveRecord::Migration + DOWNTIME = false + def up drop_table :users_groups drop_table :users_projects diff --git a/db/migrate/20141118150935_add_audit_event.rb b/db/migrate/20141118150935_add_audit_event.rb index f0452f46459..52d70b4a0ac 100644 --- a/db/migrate/20141118150935_add_audit_event.rb +++ b/db/migrate/20141118150935_add_audit_event.rb @@ -1,5 +1,7 @@ # rubocop:disable all class AddAuditEvent < ActiveRecord::Migration + DOWNTIME = false + def change create_table :audit_events do |t| t.integer :author_id, null: false diff --git a/db/migrate/20141216155758_create_doorkeeper_tables.rb b/db/migrate/20141216155758_create_doorkeeper_tables.rb index 1c4d32e133c..17e45a77291 100644 --- a/db/migrate/20141216155758_create_doorkeeper_tables.rb +++ b/db/migrate/20141216155758_create_doorkeeper_tables.rb @@ -1,5 +1,7 @@ # rubocop:disable all class CreateDoorkeeperTables < ActiveRecord::Migration + DOWNTIME = false + def change create_table :oauth_applications do |t| t.string :name, null: false diff --git a/db/migrate/20150108073740_create_application_settings.rb b/db/migrate/20150108073740_create_application_settings.rb index c26a7c39574..0e4c66ca8c0 100644 --- a/db/migrate/20150108073740_create_application_settings.rb +++ b/db/migrate/20150108073740_create_application_settings.rb @@ -1,5 +1,7 @@ # rubocop:disable all class CreateApplicationSettings < ActiveRecord::Migration + DOWNTIME = false + def change create_table :application_settings do |t| t.integer :default_projects_limit diff --git a/db/migrate/20150313012111_create_subscriptions_table.rb b/db/migrate/20150313012111_create_subscriptions_table.rb index 0977c9adfec..a9a8435330d 100644 --- a/db/migrate/20150313012111_create_subscriptions_table.rb +++ b/db/migrate/20150313012111_create_subscriptions_table.rb @@ -1,15 +1,17 @@ # rubocop:disable all class CreateSubscriptionsTable < ActiveRecord::Migration + DOWNTIME = false + def change create_table :subscriptions do |t| t.integer :user_id t.references :subscribable, polymorphic: true t.boolean :subscribed - + t.timestamps null: true end - add_index :subscriptions, + add_index :subscriptions, [:subscribable_id, :subscribable_type, :user_id], unique: true, name: 'subscriptions_user_id_and_ref_fields' diff --git a/db/migrate/20150806104937_create_abuse_reports.rb b/db/migrate/20150806104937_create_abuse_reports.rb index 9f1512db862..52aed9e1d1d 100644 --- a/db/migrate/20150806104937_create_abuse_reports.rb +++ b/db/migrate/20150806104937_create_abuse_reports.rb @@ -1,5 +1,7 @@ # rubocop:disable all class CreateAbuseReports < ActiveRecord::Migration + DOWNTIME = false + def change create_table :abuse_reports do |t| t.integer :reporter_id diff --git a/db/migrate/20151103134857_create_lfs_objects.rb b/db/migrate/20151103134857_create_lfs_objects.rb index fadaf637cec..db6fa27199b 100644 --- a/db/migrate/20151103134857_create_lfs_objects.rb +++ b/db/migrate/20151103134857_create_lfs_objects.rb @@ -1,5 +1,7 @@ # rubocop:disable all class CreateLfsObjects < ActiveRecord::Migration + DOWNTIME = false + def change create_table :lfs_objects do |t| t.string :oid, null: false, unique: true diff --git a/db/migrate/20151103134958_create_lfs_objects_projects.rb b/db/migrate/20151103134958_create_lfs_objects_projects.rb index e830cbe4656..5af1c39fd9c 100644 --- a/db/migrate/20151103134958_create_lfs_objects_projects.rb +++ b/db/migrate/20151103134958_create_lfs_objects_projects.rb @@ -1,5 +1,7 @@ # rubocop:disable all class CreateLfsObjectsProjects < ActiveRecord::Migration + DOWNTIME = false + def change create_table :lfs_objects_projects do |t| t.integer :lfs_object_id, null: false diff --git a/db/migrate/20151105094515_create_releases.rb b/db/migrate/20151105094515_create_releases.rb index 87f692c64d0..34dd7a10942 100644 --- a/db/migrate/20151105094515_create_releases.rb +++ b/db/migrate/20151105094515_create_releases.rb @@ -1,5 +1,7 @@ # rubocop:disable all class CreateReleases < ActiveRecord::Migration + DOWNTIME = false + def change create_table :releases do |t| t.string :tag diff --git a/db/migrate/20160212123307_create_tasks.rb b/db/migrate/20160212123307_create_tasks.rb index 8b5b1dd694d..cd3ad0e4cd8 100644 --- a/db/migrate/20160212123307_create_tasks.rb +++ b/db/migrate/20160212123307_create_tasks.rb @@ -1,5 +1,7 @@ # rubocop:disable all class CreateTasks < ActiveRecord::Migration + DOWNTIME = false + def change create_table :tasks do |t| t.references :user, null: false, index: true diff --git a/db/migrate/20160416180807_add_award_emoji.rb b/db/migrate/20160416180807_add_award_emoji.rb index c0957f028a8..0d252e5044e 100644 --- a/db/migrate/20160416180807_add_award_emoji.rb +++ b/db/migrate/20160416180807_add_award_emoji.rb @@ -1,5 +1,7 @@ # rubocop:disable all class AddAwardEmoji < ActiveRecord::Migration + DOWNTIME = false + def change create_table :award_emoji do |t| t.string :name diff --git a/db/migrate/20161124141322_migrate_process_commit_worker_jobs.rb b/db/migrate/20161124141322_migrate_process_commit_worker_jobs.rb new file mode 100644 index 00000000000..453a44e271a --- /dev/null +++ b/db/migrate/20161124141322_migrate_process_commit_worker_jobs.rb @@ -0,0 +1,92 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class MigrateProcessCommitWorkerJobs < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + class Project < ActiveRecord::Base + def self.find_including_path(id) + select("projects.*, CONCAT(namespaces.path, '/', projects.path) AS path_with_namespace"). + joins('INNER JOIN namespaces ON namespaces.id = projects.namespace_id'). + find_by(id: id) + end + + def repository_storage_path + Gitlab.config.repositories.storages[repository_storage] + end + + def repository_path + File.join(repository_storage_path, read_attribute(:path_with_namespace) + '.git') + end + + def repository + @repository ||= Rugged::Repository.new(repository_path) + end + end + + DOWNTIME = true + DOWNTIME_REASON = 'Existing workers will error until they are using a newer version of the code' + + disable_ddl_transaction! + + def up + Sidekiq.redis do |redis| + new_jobs = [] + + while job = redis.lpop('queue:process_commit') + payload = JSON.load(job) + project = Project.find_including_path(payload['args'][0]) + + next unless project + + begin + commit = project.repository.lookup(payload['args'][2]) + rescue Rugged::OdbError + next + end + + hash = { + id: commit.oid, + message: commit.message, + parent_ids: commit.parent_ids, + authored_date: commit.author[:time], + author_name: commit.author[:name], + author_email: commit.author[:email], + committed_date: commit.committer[:time], + committer_email: commit.committer[:email], + committer_name: commit.committer[:name] + } + + payload['args'][2] = hash + + new_jobs << JSON.dump(payload) + end + + redis.multi do |multi| + new_jobs.each do |j| + multi.lpush('queue:process_commit', j) + end + end + end + end + + def down + Sidekiq.redis do |redis| + new_jobs = [] + + while job = redis.lpop('queue:process_commit') + payload = JSON.load(job) + + payload['args'][2] = payload['args'][2]['id'] + + new_jobs << JSON.dump(payload) + end + + redis.multi do |multi| + new_jobs.each do |j| + multi.lpush('queue:process_commit', j) + end + end + end + end +end diff --git a/doc/administration/build_artifacts.md b/doc/administration/build_artifacts.md index 64353f7282b..3ba8387c7f0 100644 --- a/doc/administration/build_artifacts.md +++ b/doc/administration/build_artifacts.md @@ -84,7 +84,7 @@ _The artifacts are stored by default in ## Set the maximum file size of the artifacts Provided the artifacts are enabled, you can change the maximum file size of the -artifacts through the [Admin area settings](../user/admin_area/settings/continuous_integration#maximum-artifacts-size). +artifacts through the [Admin area settings](../user/admin_area/settings/continuous_integration.md#maximum-artifacts-size). [reconfigure gitlab]: restart_gitlab.md "How to restart GitLab" [restart gitlab]: restart_gitlab.md "How to restart GitLab" diff --git a/doc/api/branches.md b/doc/api/branches.md index 07dfa5d4d7f..ffcfea41453 100644 --- a/doc/api/branches.md +++ b/doc/api/branches.md @@ -22,6 +22,7 @@ Example response: [ { "name": "master", + "merged": false, "protected": true, "developers_can_push": false, "developers_can_merge": false, @@ -65,6 +66,7 @@ Example response: ```json { "name": "master", + "merged": false, "protected": true, "developers_can_push": false, "developers_can_merge": false, @@ -123,6 +125,7 @@ Example response: ] }, "name": "master", + "merged": false, "protected": true, "developers_can_push": true, "developers_can_merge": true @@ -166,6 +169,7 @@ Example response: ] }, "name": "master", + "merged": false, "protected": false, "developers_can_push": false, "developers_can_merge": false @@ -206,6 +210,7 @@ Example response: ] }, "name": "newbranch", + "merged": false, "protected": false, "developers_can_push": false, "developers_can_merge": false diff --git a/doc/api/commits.md b/doc/api/commits.md index e1ed99d98d3..0170af00e0e 100644 --- a/doc/api/commits.md +++ b/doc/api/commits.md @@ -29,6 +29,8 @@ Example response: "title": "Replace sanitize with escape once", "author_name": "Dmitriy Zaporozhets", "author_email": "dzaporozhets@sphereconsultinginc.com", + "committer_name": "Administrator", + "committer_email": "admin@example.com", "created_at": "2012-09-20T11:50:22+03:00", "message": "Replace sanitize with escape once", "allow_failure": false @@ -39,6 +41,8 @@ Example response: "title": "Sanitize for network graph", "author_name": "randx", "author_email": "dmitriy.zaporozhets@gmail.com", + "committer_name": "Dmitriy", + "committer_email": "dmitriy.zaporozhets@gmail.com", "created_at": "2012-09-20T09:06:12+03:00", "message": "Sanitize for network graph", "allow_failure": false @@ -115,6 +119,8 @@ Example response: "title": "some commit message", "author_name": "Dmitriy Zaporozhets", "author_email": "dzaporozhets@sphereconsultinginc.com", + "committer_name": "Dmitriy Zaporozhets", + "committer_email": "dzaporozhets@sphereconsultinginc.com", "created_at": "2016-09-20T09:26:24.000-07:00", "message": "some commit message", "parent_ids": [ @@ -159,6 +165,8 @@ Example response: "title": "Sanitize for network graph", "author_name": "randx", "author_email": "dmitriy.zaporozhets@gmail.com", + "committer_name": "Dmitriy", + "committer_email": "dmitriy.zaporozhets@gmail.com", "created_at": "2012-09-20T09:06:12+03:00", "message": "Sanitize for network graph", "committed_date": "2012-09-20T09:06:12+03:00", diff --git a/doc/api/projects.md b/doc/api/projects.md index de57f91bb8e..132be644b59 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -626,6 +626,7 @@ Parameters: | `path` | string | no | Custom repository name for new project. By default generated based on name | | `default_branch` | string | no | `master` by default | | `namespace_id` | integer | no | Namespace for the new project (defaults to the current user's namespace) | +| `default_branch` | string | no | `master` by default | | `description` | string | no | Short project description | | `issues_enabled` | boolean | no | Enable issues for this project | | `merge_requests_enabled` | boolean | no | Enable merge requests for this project | diff --git a/doc/api/repository_files.md b/doc/api/repository_files.md index 1bc6a24e914..b8c9eb2c9a8 100644 --- a/doc/api/repository_files.md +++ b/doc/api/repository_files.md @@ -60,7 +60,7 @@ Parameters: - `file_path` (required) - Full path to new file. Ex. lib/class.rb - `branch_name` (required) - The name of branch -- `encoding` (optional) - 'text' or 'base64'. Text is default. +- `encoding` (optional) - Change encoding to 'base64'. Default is text. - `author_email` (optional) - Specify the commit author's email address - `author_name` (optional) - Specify the commit author's name - `content` (required) - File content @@ -89,7 +89,7 @@ Parameters: - `file_path` (required) - Full path to file. Ex. lib/class.rb - `branch_name` (required) - The name of branch -- `encoding` (optional) - 'text' or 'base64'. Text is default. +- `encoding` (optional) - Change encoding to 'base64'. Default is text. - `author_email` (optional) - Specify the commit author's email address - `author_name` (optional) - Specify the commit author's name - `content` (required) - New file content diff --git a/doc/ci/README.md b/doc/ci/README.md index 545cc72682d..db236ce2a66 100644 --- a/doc/ci/README.md +++ b/doc/ci/README.md @@ -21,6 +21,7 @@ - [CI services (linked docker containers)](services/README.md) - [CI/CD pipelines settings](../user/project/pipelines/settings.md) - [Review Apps](review_apps/index.md) +- [Git submodules](git_submodules.md) Using Git submodules in your CI jobs ## Breaking changes diff --git a/doc/ci/docker/using_docker_build.md b/doc/ci/docker/using_docker_build.md index 89088cf9b83..28141cced3b 100644 --- a/doc/ci/docker/using_docker_build.md +++ b/doc/ci/docker/using_docker_build.md @@ -270,12 +270,16 @@ which can be avoided if a different driver is used, for example `overlay`. ## Using the GitLab Container Registry -> **Note:** -This feature requires GitLab 8.8 and GitLab Runner 1.2. - -Once you've built a Docker image, you can push it up to the built-in [GitLab Container Registry](../../user/project/container_registry.md). For example, if you're using -docker-in-docker on your runners, this is how your `.gitlab-ci.yml` could look: +> **Notes:** +- This feature requires GitLab 8.8 and GitLab Runner 1.2. +- Starting from GitLab 8.12, if you have 2FA enabled in your account, you need + to pass a personal access token instead of your password in order to login to + GitLab's Container Registry. +Once you've built a Docker image, you can push it up to the built-in +[GitLab Container Registry](../../user/project/container_registry.md). For example, +if you're using docker-in-docker on your runners, this is how your `.gitlab-ci.yml` +could look like: ```yaml build: @@ -354,10 +358,20 @@ deploy: ``` Some things you should be aware of when using the Container Registry: -* You must log in to the container registry before running commands. Putting this in `before_script` will run it before each build job. -* Using `docker build --pull` makes sure that Docker fetches any changes to base images before building just in case your cache is stale. It takes slightly longer, but means you don’t get stuck without security patches to base images. -* Doing an explicit `docker pull` before each `docker run` makes sure to fetch the latest image that was just built. This is especially important if you are using multiple runners that cache images locally. Using the git SHA in your image tag makes this less necessary since each build will be unique and you shouldn't ever have a stale image, but it's still possible if you re-build a given commit after a dependency has changed. -* You don't want to build directly to `latest` in case there are multiple builds happening simultaneously. + +- You must log in to the container registry before running commands. Putting + this in `before_script` will run it before each build job. +- Using `docker build --pull` makes sure that Docker fetches any changes to base + images before building just in case your cache is stale. It takes slightly + longer, but means you don’t get stuck without security patches to base images. +- Doing an explicit `docker pull` before each `docker run` makes sure to fetch + the latest image that was just built. This is especially important if you are + using multiple runners that cache images locally. Using the git SHA in your + image tag makes this less necessary since each build will be unique and you + shouldn't ever have a stale image, but it's still possible if you re-build a + given commit after a dependency has changed. +- You don't want to build directly to `latest` in case there are multiple builds + happening simultaneously. [docker-in-docker]: https://blog.docker.com/2013/09/docker-can-now-run-within-docker/ [docker-cap]: https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities diff --git a/doc/ci/git_submodules.md b/doc/ci/git_submodules.md new file mode 100644 index 00000000000..1d782200cca --- /dev/null +++ b/doc/ci/git_submodules.md @@ -0,0 +1,86 @@ +# Using Git submodules with GitLab CI + +> **Notes:** +- GitLab 8.12 introduced a new [CI build permissions model][newperms] and you + are encouraged to upgrade your GitLab instance if you haven't done already. + If you are **not** using GitLab 8.12 or higher, you would need to work your way + around submodules in order to access the sources of e.g., `gitlab.com/group/project` + with the use of [SSH keys](ssh_keys/README.md). +- With GitLab 8.12 onward, your permissions are used to evaluate what a CI build + can access. More information about how this system works can be found in the + [Build permissions model](../user/permissions.md#builds-permissions). +- The HTTP(S) Git protocol [must be enabled][gitpro] in your GitLab instance. + +## Configuring the `.gitmodules` file + +If dealing with [Git submodules][gitscm], your project will probably have a file +named `.gitmodules`. + +Let's consider the following example: + +1. Your project is located at `https://gitlab.com/secret-group/my-project`. +1. To checkout your sources you usually use an SSH address like + `git@gitlab.com:secret-group/my-project.git`. +1. Your project depends on `https://gitlab.com/group/project`, which you want + to include as a submodule. + +If you are using GitLab 8.12+ and your submodule is on the same GitLab server, +you must update your `.gitmodules` file to use **relative URLs**. +Since Git allows the usage of relative URLs for your `.gitmodules` configuration, +this easily allows you to use HTTP(S) for cloning all your CI builds and SSH +for all your local checkouts. The `.gitmodules` would look like: + +```ini +[submodule "project"] + path = project + url = ../../group/project.git +``` + +The above configuration will instruct Git to automatically deduce the URL that +should be used when cloning sources. Whether you use HTTP(S) or SSH, Git will use +that same channel and it will allow to make all your CI builds use HTTP(S) +(because GitLab CI only uses HTTP(S) for cloning your sources), and all your local +clones will continue using SSH. + +For all other submodules not located on the same GitLab server, use the full +HTTP(S) protocol URL: + +```ini +[submodule "project-x"] + path = project-x + url = https://gitserver.com/group/project-x.git +``` + +Once `.gitmodules` is correctly configured, you can move on to +[configuring your `.gitlab-ci.yml`](#using-git-submodules-in-your-ci-jobs). + +## Using Git submodules in your CI jobs + +There are a few steps you need to take in order to make submodules work +correctly with your CI builds: + +1. First, make sure you have used [relative URLs](#configuring-the-gitmodules-file) + for the submodules located in the same GitLab server. +1. Then, use `git submodule sync/update` in `before_script`: + + ```yaml + before_script: + - git submodule sync --recursive + - git submodule update --init --recursive + ``` + + `--recursive` should be used in either both or none (`sync/update`) depending on + whether you have recursive submodules. + +The rationale to set the `sync` and `update` in `before_script` is because of +the way Git submodules work. On a fresh Runner workspace, Git will set the +submodule URL including the token in `.git/config` +(or `.git/modules/<submodule>/config`) based on `.gitmodules` and the current +remote URL. On subsequent builds on the same Runner, `.git/config` is cached +and already contains a full URL for the submodule, corresponding to the previous +build, and to **a token from a previous build**. `sync` allows to force updating +the full URL. + +[gitpro]: ../user/admin_area/settings/visibility_and_access_controls.md#enabled-git-access-protocols +[gitscm]: https://git-scm.com/book/en/v2/Git-Tools-Submodules "Git submodules documentation" +[newperms]: ../user/project/new_ci_build_permissions_model.md diff --git a/doc/ci/triggers/README.md b/doc/ci/triggers/README.md index cf7c55f75f2..efca05af7b8 100644 --- a/doc/ci/triggers/README.md +++ b/doc/ci/triggers/README.md @@ -6,7 +6,7 @@ GitLab 8.12 has a completely redesigned build permissions system. Read all about the [new model and its implications](../../user/project/new_ci_build_permissions_model.md#build-triggers). -Triggers can be used to force a rebuild of a specific branch, tag or commit, +Triggers can be used to force a rebuild of a specific `ref` (branch or tag) with an API call. ## Add a trigger @@ -29,6 +29,10 @@ irreversible. ## Trigger a build +> **Note**: +Valid refs are only the branches and tags. If you pass a commit SHA as a ref, +it will not trigger a build. + To trigger a build you need to send a `POST` request to GitLab's API endpoint: ``` @@ -36,8 +40,8 @@ POST /projects/:id/trigger/builds ``` The required parameters are the trigger's `token` and the Git `ref` on which -the trigger will be performed. Valid refs are the branch, the tag or the commit -SHA. The `:id` of a project can be found by [querying the API](../../api/projects.md) +the trigger will be performed. Valid refs are the branch and the tag. The `:id` +of a project can be found by [querying the API](../../api/projects.md) or by visiting the **Triggers** page which provides self-explanatory examples. When a rebuild is triggered, the information is exposed in GitLab's UI under diff --git a/doc/development/code_review.md b/doc/development/code_review.md index 4bf0e7197e9..1ef34c79971 100644 --- a/doc/development/code_review.md +++ b/doc/development/code_review.md @@ -74,6 +74,32 @@ experience, refactors the existing code). Then: - If you set the MR to "Merge When Pipeline Succeeds", you should take over subsequent revisions for anything that would be spotted after that. +## The right balance + +One of the most difficult things during code review is finding the right +balance in how deep the reviewer can interfere with the code created by a +reviewee. + +- Learning how to find the right balance takes time; that is why we have + minibosses that become merge request endbosses after some time spent on + reviewing merge requests. +- Finding bugs and improving code style is important, but thinking about good + design is important as well. Building abstractions and good design is what + makes it possible to hide complexity and makes future changes easier. +- Asking the reviewee to change the design sometimes means the complete rewrite + of the contributed code. It's usually a good idea to ask another merge + request endboss before doing it, but have the courage to do it when you + believe it is important. +- There is a difference in doing things right and doing things right now. + Ideally, we should do the former, but in the real world we need the latter as + well. A good example is a security fix which should be released as soon as + possible. Asking the reviewee to do the major refactoring in the merge + request that is an urgent fix should be avoided. +- Doing things well today is usually better than doing something perfectly + tomorrow. Shipping a kludge today is usually worse than doing something well + tomorrow. When you are not able to find the right balance, ask other people + about their opinion. + ## Credits Largely based on the [thoughtbot code review guide]. diff --git a/doc/development/gotchas.md b/doc/development/gotchas.md index 7bfc9cb361f..0f78e8238af 100644 --- a/doc/development/gotchas.md +++ b/doc/development/gotchas.md @@ -141,51 +141,3 @@ in an initializer._ ### Further reading - Stack Overflow: [Why you should not write inline JavaScript](http://programmers.stackexchange.com/questions/86589/why-should-i-avoid-inline-scripting) - -## ID-based CSS selectors need to be a bit more specific - -Normally, because HTML `id` attributes need to be unique to the page, it's -perfectly fine to write some JavaScript like the following: - -```javascript -$('#js-my-selector').hide(); -``` - -However, there's a feature of GitLab's Markdown processing that [automatically -adds anchors to header elements][ToC Processing], with the `id` attribute being -automatically generated based on the content of the header. - -Unfortunately, this feature makes it possible for user-generated content to -create a header element with the same `id` attribute we're using in our -selector, potentially breaking the JavaScript behavior. A user could break the -above example with the following Markdown: - -```markdown -## JS My Selector -``` - -Which gets converted to the following HTML: - -```html -<h2> - <a id="js-my-selector" class="anchor" href="#js-my-selector" aria-hidden="true"></a> - JS My Selector -</h2> -``` - -[ToC Processing]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-4-stable/lib/banzai/filter/table_of_contents_filter.rb#L31-37 - -### Solution - -The current recommended fix for this is to make our selectors slightly more -specific: - -```javascript -$('div#js-my-selector').hide(); -``` - -### Further reading - -- Issue: [Merge request ToC anchor conflicts with tabs](https://gitlab.com/gitlab-org/gitlab-ce/issues/3908) -- Merge Request: [Make tab target selectors less naive](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2023) -- Merge Request: [Make cross-project reference's clipboard target less naive](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2024) diff --git a/doc/install/installation.md b/doc/install/installation.md index ee02d6024d9..4b0c585e51e 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -175,7 +175,7 @@ We recommend using a PostgreSQL database. For MySQL check the ```bash sudo -u postgres psql -d template1 -c "CREATE USER git CREATEDB;" ``` - + 1. Create the `pg_trgm` extension (required for GitLab 8.6+): ```bash @@ -396,15 +396,13 @@ GitLab Shell is an SSH access and repository management software developed speci ### Install gitlab-workhorse -GitLab-Workhorse uses [GNU Make](https://www.gnu.org/software/make/). -If you are not using Linux you may have to run `gmake` instead of -`make` below. +GitLab-Workhorse uses [GNU Make](https://www.gnu.org/software/make/). The +following command-line will install GitLab-Workhorse in `/home/git/gitlab-workhorse` +which is the recommended location. - cd /home/git - sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git - cd gitlab-workhorse - sudo -u git -H git checkout v1.0.1 - sudo -u git -H make + cd /home/git/gitlab + + sudo -u git -H bundle exec rake "gitlab:workhorse:install[/home/git/gitlab-workhorse]" RAILS_ENV=production ### Initialize Database and Activate Advanced Features diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md index 17485b11c09..f42bb6a81a2 100644 --- a/doc/raketasks/backup_restore.md +++ b/doc/raketasks/backup_restore.md @@ -353,7 +353,7 @@ restore: ```shell # This command will overwrite the contents of your GitLab database! -sudo gitlab-rake gitlab:backup:restore BACKUP=1393513186 +sudo gitlab-rake gitlab:backup:restore BACKUP=1393513186_2014_02_27 ``` Restart and check GitLab: diff --git a/doc/update/8.12-to-8.13.md b/doc/update/8.12-to-8.13.md index c0084d9d59c..8c0d3f78b55 100644 --- a/doc/update/8.12-to-8.13.md +++ b/doc/update/8.12-to-8.13.md @@ -166,7 +166,7 @@ See [smtp_settings.rb.sample] as an example. Ensure you're still up-to-date with the latest init script changes: sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab - + For Ubuntu 16.04.1 LTS: sudo systemctl daemon-reload diff --git a/doc/update/8.14-to-8.15.md b/doc/update/8.14-to-8.15.md new file mode 100644 index 00000000000..576b943b98c --- /dev/null +++ b/doc/update/8.14-to-8.15.md @@ -0,0 +1,202 @@ +# From 8.14 to 8.15 + +Make sure you view this update guide from the tag (version) of GitLab you would +like to install. In most cases this should be the highest numbered production +tag (without rc in it). You can select the tag in the version dropdown at the +top left corner of GitLab (below the menu bar). + +If the highest number stable branch is unclear please check the +[GitLab Blog](https://about.gitlab.com/blog/archives.html) for installation +guide links by version. + +### 1. Stop server + + sudo service gitlab stop + +### 2. Backup + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 3. Update Ruby + +We will continue supporting Ruby < 2.3 for the time being but we recommend you +upgrade to Ruby 2.3 if you're running a source installation, as this is the same +version that ships with our Omnibus package. + +You can check which version you are running with `ruby -v`. + +Download and compile Ruby: + +```bash +mkdir /tmp/ruby && cd /tmp/ruby +curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.1.tar.gz +echo 'c39b4001f7acb4e334cb60a0f4df72d434bef711 ruby-2.3.1.tar.gz' | shasum --check - && tar xzf ruby-2.3.1.tar.gz +cd ruby-2.3.1 +./configure --disable-install-rdoc +make +sudo make install +``` + +Install Bundler: + +```bash +sudo gem install bundler --no-ri --no-rdoc +``` + +### 4. Get latest code + +```bash +sudo -u git -H git fetch --all +sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically +``` + +For GitLab Community Edition: + +```bash +sudo -u git -H git checkout 8-15-stable +``` + +OR + +For GitLab Enterprise Edition: + +```bash +sudo -u git -H git checkout 8-15-stable-ee +``` + +### 5. Update gitlab-shell + +```bash +cd /home/git/gitlab-shell +sudo -u git -H git fetch --all --tags +sudo -u git -H git checkout v4.0.0 +``` + +### 6. Update gitlab-workhorse + +Install and compile gitlab-workhorse. This requires +[Go 1.5](https://golang.org/dl) which should already be on your system from +GitLab 8.1. + +```bash +sudo -u git -H bundle exec rake "gitlab:workhorse:install[/home/git/gitlab-workhorse]" RAILS_ENV=production +``` + +### 7. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL installations (note: the line below states '--without postgres') +sudo -u git -H bundle install --without postgres development test --deployment + +# PostgreSQL installations (note: the line below states '--without mysql') +sudo -u git -H bundle install --without mysql development test --deployment + +# Optional: clean up old gems +sudo -u git -H bundle clean + +# Run database migrations +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production + +# Clean up assets and cache +sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production +``` + +### 8. Update configuration files + +#### New configuration options for `gitlab.yml` + +There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them manually to your current `gitlab.yml`: + +```sh +git diff origin/8-13-stable:config/gitlab.yml.example origin/8-15-stable:config/gitlab.yml.example +``` + +#### Git configuration + +Configure Git to generate packfile bitmaps (introduced in Git 2.0) on +the GitLab server during `git gc`. + +```sh +sudo -u git -H git config --global repack.writeBitmaps true +``` + +#### Nginx configuration + +Ensure you're still up-to-date with the latest NGINX configuration changes: + +```sh +# For HTTPS configurations +git diff origin/8-13-stable:lib/support/nginx/gitlab-ssl origin/8-15-stable:lib/support/nginx/gitlab-ssl + +# For HTTP configurations +git diff origin/8-13-stable:lib/support/nginx/gitlab origin/8-15-stable:lib/support/nginx/gitlab +``` + +If you are using Apache instead of NGINX please see the updated [Apache templates]. +Also note that because Apache does not support upstreams behind Unix sockets you +will need to let gitlab-workhorse listen on a TCP port. You can do this +via [/etc/default/gitlab]. + +[Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache +[/etc/default/gitlab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-15-stable/lib/support/init.d/gitlab.default.example#L38 + +#### SMTP configuration + +If you're installing from source and use SMTP to deliver mail, you will need to add the following line +to config/initializers/smtp_settings.rb: + +```ruby +ActionMailer::Base.delivery_method = :smtp +``` + +See [smtp_settings.rb.sample] as an example. + +[smtp_settings.rb.sample]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-15-stable/config/initializers/smtp_settings.rb.sample#L13 + +#### Init script + +Ensure you're still up-to-date with the latest init script changes: + + sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab + +For Ubuntu 16.04.1 LTS: + + sudo systemctl daemon-reload + +### 9. Start application + + sudo service gitlab start + sudo service nginx restart + +### 10. Check application status + +Check if GitLab and its environment are configured correctly: + + sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production + +To make sure you didn't miss anything run a more thorough check: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations, the upgrade is complete! + +## Things went south? Revert to previous version (8.14) + +### 1. Revert the code to the previous version + +Follow the [upgrade guide from 8.13 to 8.14](8.13-to-8.14.md), except for the +database migration (the backup is already migrated to the previous version). + +### 2. Restore from the backup + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production +``` + +If you have more than one backup `*.tar` file(s) please add `BACKUP=timestamp_of_backup` to the command above. diff --git a/doc/update/patch_versions.md b/doc/update/patch_versions.md index 60729316cde..685972cfb41 100644 --- a/doc/update/patch_versions.md +++ b/doc/update/patch_versions.md @@ -1,7 +1,10 @@ # Universal update guide for patch versions -*Make sure you view this [upgrade guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/update/patch_versions.md) from the `master` branch for the most up to date instructions.* -For example from 7.14.0 to 7.14.3, also see the [semantic versioning specification](http://semver.org/). +## Select Version to Install + +Make sure you view [this update guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/update/patch_versions.md) from the tag (version) of GitLab you would like to install. +In most cases this should be the highest numbered production tag (without rc in it). +You can select the tag in the version dropdown in the top left corner of GitLab (below the menu bar). ### 0. Backup @@ -45,10 +48,9 @@ sudo -u git -H git checkout v`cat /home/git/gitlab/GITLAB_SHELL_VERSION` -b v`ca ### 4. Update gitlab-workhorse to the corresponding version ```bash -cd /home/git/gitlab-workhorse -sudo -u git -H git fetch -sudo -u git -H git checkout v`cat /home/git/gitlab/GITLAB_WORKHORSE_VERSION` -b v`cat /home/git/gitlab/GITLAB_WORKHORSE_VERSION` -sudo -u git -H make +cd /home/git/gitlab + +sudo -u git -H bundle exec rake "gitlab:workhorse:install[/home/git/gitlab-workhorse]" RAILS_ENV=production ``` ### 5. Install libs, migrations, etc. diff --git a/doc/user/markdown.md b/doc/user/markdown.md index 162d1bd7ed4..85b165ac44f 100644 --- a/doc/user/markdown.md +++ b/doc/user/markdown.md @@ -604,7 +604,7 @@ This line is separated from the one above by two newlines, so it will be a *sepa This line is also a separate paragraph, but... This line is only separated by a single newline, so it's a separate line in the *same paragraph*. -This line is also a separate paragraph, and... +This line is also a separate paragraph, and... This line is on its own line, because the previous line ends with two spaces. ``` @@ -616,7 +616,7 @@ This line is separated from the one above by two newlines, so it will be a *sepa This line is also begins a separate paragraph, but... This line is only separated by a single newline, so it's a separate line in the *same paragraph*. -This line is also a separate paragraph, and... +This line is also a separate paragraph, and... This line is on its own line, because the previous line ends with two spaces. diff --git a/doc/user/permissions.md b/doc/user/permissions.md index cea78864df2..39fe2409a29 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -141,10 +141,9 @@ instance and project. In addition, all admins can use the admin interface under | See events in the system | | | | âś“ | | Admin interface | | | | âś“ | -### Build permissions - -> Changed in GitLab 8.12. +### Builds permissions +>**Note:** GitLab 8.12 has a completely redesigned build permissions system. Read all about the [new model and its implications][new-mod]. diff --git a/doc/user/project/container_registry.md b/doc/user/project/container_registry.md index b205fea2c40..47a4a3f85d0 100644 --- a/doc/user/project/container_registry.md +++ b/doc/user/project/container_registry.md @@ -4,13 +4,15 @@ --- -> **Note** -Docker Registry manifest `v1` support was added in GitLab 8.9 to support Docker -versions earlier than 1.10. -> -This document is about the user guide. To learn how to enable GitLab Container -Registry across your GitLab instance, visit the -[administrator documentation](../../administration/container_registry.md). +>**Notes:** +- Docker Registry manifest `v1` support was added in GitLab 8.9 to support Docker + versions earlier than 1.10. +- This document is about the user guide. To learn how to enable GitLab Container + Registry across your GitLab instance, visit the + [administrator documentation](../../administration/container_registry.md). +- Starting from GitLab 8.12, if you have 2FA enabled in your account, you need + to pass a personal access token instead of your password in order to login to + GitLab's Container Registry. With the Docker Container Registry integrated into GitLab, every project can have its own space to store its Docker images. diff --git a/doc/user/project/new_ci_build_permissions_model.md b/doc/user/project/new_ci_build_permissions_model.md index 60b7bec2ba7..320faff65c5 100644 --- a/doc/user/project/new_ci_build_permissions_model.md +++ b/doc/user/project/new_ci_build_permissions_model.md @@ -34,9 +34,9 @@ as created be the pusher (local push or via the UI) and any build created in thi pipeline will have the permissions of the pusher. This allows us to make it really easy to evaluate the access for all projects -that have Git submodules or are using container images that the pusher would -have access too. **The permission is granted only for time that build is running. -The access is revoked after the build is finished.** +that have [Git submodules][gitsub] or are using container images that the pusher +would have access too. **The permission is granted only for time that build is +running. The access is revoked after the build is finished.** ## Types of users @@ -141,7 +141,7 @@ with GitLab 8.12. With the new build permissions model, there is now an easy way to access all dependent source code in a project. That way, we can: -1. Access a project's Git submodules +1. Access a project's [Git submodules][gitsub] 1. Access private container images 1. Access project's and submodule LFS objects @@ -179,119 +179,25 @@ As a user: ### Git submodules -> -It often happens that while working on one project, you need to use another -project from within it; perhaps it’s a library that a third party developed or -you’re developing a project separately and are using it in multiple parent -projects. -A common issue arises in these scenarios: you want to be able to treat the two -projects as separate yet still be able to use one from within the other. -> -_Excerpt from the [Git website][git-scm] about submodules._ - -If dealing with submodules, your project will probably have a file named -`.gitmodules`. And this is how it usually looks like: - -``` -[submodule "tools"] - path = tools - url = git@gitlab.com/group/tools.git -``` - -> **Note:** -If you are **not** using GitLab 8.12 or higher, you would need to work your way -around this issue in order to access the sources of `gitlab.com/group/tools` -(e.g., use [SSH keys](../ssh_keys/README.md)). -> -With GitLab 8.12 onward, your permissions are used to evaluate what a CI build -can access. More information about how this system works can be found in the -[Build permissions model](../../user/permissions.md#builds-permissions). - -To make use of the new changes, you have to update your `.gitmodules` file to -use a relative URL. - -Let's consider the following example: - -1. Your project is located at `https://gitlab.com/secret-group/my-project`. -1. To checkout your sources you usually use an SSH address like - `git@gitlab.com:secret-group/my-project.git`. -1. Your project depends on `https://gitlab.com/group/tools`. -1. You have the `.gitmodules` file with above content. - -Since Git allows the usage of relative URLs for your `.gitmodules` configuration, -this easily allows you to use HTTP for cloning all your CI builds and SSH -for all your local checkouts. - -For example, if you change the `url` of your `tools` dependency, from -`git@gitlab.com/group/tools.git` to `../../group/tools.git`, this will instruct -Git to automatically deduce the URL that should be used when cloning sources. -Whether you use HTTP or SSH, Git will use that same channel and it will allow -to make all your CI builds use HTTPS (because GitLab CI uses HTTPS for cloning -your sources), and all your local clones will continue using SSH. - -Given the above explanation, your `.gitmodules` file should eventually look -like this: - -``` -[submodule "tools"] - path = tools - url = ../../group/tools.git -``` - -However, you have to explicitly tell GitLab CI to clone your submodules as this -is not done automatically. You can achieve that by adding a `before_script` -section to your `.gitlab-ci.yml`: - -``` -before_script: - - git submodule update --init --recursive - -test: - script: - - run-my-tests -``` - -This will make GitLab CI initialize (fetch) and update (checkout) all your -submodules recursively. - -If Git does not use the newly added relative URLs but still uses your old URLs, -you might need to add `git submodule sync --recursive` to your `.gitlab-ci.yml`, -prior to running `git submodule update --init --recursive`. This transfers the -changes from your `.gitmodules` file into the `.git` folder, which is kept by -runners between runs. - -In case your environment or your Docker image doesn't have Git installed, -you have to either ask your Administrator or install the missing dependency -yourself: - -``` -# Debian / Ubuntu -before_script: - - apt-get update -y - - apt-get install -y git-core - - git submodule update --init --recursive - -# CentOS / RedHat -before_script: - - yum install git - - git submodule update --init --recursive - -# Alpine -before_script: - - apk add -U git - - git submodule update --init --recursive -``` +To properly configure submodules with GitLab CI, read the +[Git submodules documentation][gitsub]. ### Container Registry With the update permission model we also extended the support for accessing Container Registries for private projects. -> **Note:** -As GitLab Runner 1.6 doesn't yet incorporate the introduced changes for -permissions, this makes the `image:` directive to not work with private projects -automatically. The manual configuration by an Administrator is required to use -private images. We plan to remove that limitation in one of the upcoming releases. +> **Notes:** +- GitLab Runner versions prior to 1.8 don't incorporate the introduced changes + for permissions. This makes the `image:` directive to not work with private + projects automatically and it needs to be configured manually on Runner's host + with a predefined account (for example administrator's personal account with + access token created explicitly for this purpose). This issue is resolved with + latest changes in GitLab Runner 1.8 which receives GitLab credentials with + build data. +- Starting with GitLab 8.12, if you have 2FA enabled in your account, you need + to pass a personal access token instead of your password in order to login to + GitLab's Container Registry. Your builds can access all container images that you would normally have access to. The only implication is that you can push to the Container Registry of the @@ -310,7 +216,7 @@ test: [build permissions]: ../permissions.md#builds-permissions [comment]: https://gitlab.com/gitlab-org/gitlab-ce/issues/22484#note_16648302 [ext]: ../permissions.md#external-users -[git-scm]: https://git-scm.com/book/en/v2/Git-Tools-Submodules +[gitsub]: ../../ci/git_submodules.md [https]: ../admin_area/settings/visibility_and_access_controls.md#enabled-git-access-protocols [triggers]: ../../ci/triggers/README.md [update-docs]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update diff --git a/doc/web_hooks/ssl.png b/doc/web_hooks/ssl.png Binary files differindex a552888ed96..21ddec4ebdf 100644 --- a/doc/web_hooks/ssl.png +++ b/doc/web_hooks/ssl.png diff --git a/doc/web_hooks/web_hooks.md b/doc/web_hooks/web_hooks.md index cd37189fdd2..1659dd1f6cb 100644 --- a/doc/web_hooks/web_hooks.md +++ b/doc/web_hooks/web_hooks.md @@ -1,17 +1,21 @@ # Webhooks -_**Note:** -Starting from GitLab 8.5:_ +>**Note:** +Starting from GitLab 8.5: +- the `repository` key is deprecated in favor of the `project` key +- the `project.ssh_url` key is deprecated in favor of the `project.git_ssh_url` key +- the `project.http_url` key is deprecated in favor of the `project.git_http_url` key -- _the `repository` key is deprecated in favor of the `project` key_ -- _the `project.ssh_url` key is deprecated in favor of the `project.git_ssh_url` key_ -- _the `project.http_url` key is deprecated in favor of the `project.git_http_url` key_ +Project webhooks allow you to trigger a URL if for example new code is pushed or +a new issue is created. You can configure webhooks to listen for specific events +like pushes, issues or merge requests. GitLab will send a POST request with data +to the webhook URL. -Project webhooks allow you to trigger an URL if new code is pushed or a new issue is created. +Webhooks can be used to update an external issue tracker, trigger CI builds, +update a backup mirror, or even deploy to your production server. -You can configure webhooks to listen for specific events like pushes, issues or merge requests. GitLab will send a POST request with data to the webhook URL. - -Webhooks can be used to update an external issue tracker, trigger CI builds, update a backup mirror, or even deploy to your production server. +Navigate to the webhooks page by choosing **Webhooks** from your project's +settings which can be found under the wheel icon in the upper right corner. ## Webhook endpoint tips @@ -26,21 +30,27 @@ GitLab webhooks keep in mind the following things: you are writing a low-level hook this is important to remember. - GitLab ignores the HTTP status code returned by your endpoint. -## Secret Token +## Secret token -If you specify a secret token, it will be sent with the hook request in the `X-Gitlab-Token` HTTP header. Your webhook endpoint can check that to verify that the request is legitimate. +If you specify a secret token, it will be sent with the hook request in the +`X-Gitlab-Token` HTTP header. Your webhook endpoint can check that to verify +that the request is legitimate. -## SSL Verification +## SSL verification By default, the SSL certificate of the webhook endpoint is verified based on -an internal list of Certificate Authorities, -which means the certificate cannot be self-signed. +an internal list of Certificate Authorities, which means the certificate cannot +be self-signed. You can turn this off in the webhook settings in your GitLab projects. ![SSL Verification](ssl.png) -## Push events +## Events + +Below are described the supported events. + +### Push events Triggered when you push to the repository except when pushing tags. @@ -121,7 +131,7 @@ X-Gitlab-Event: Push Hook } ``` -## Tag events +### Tag events Triggered when you create (or delete) tags to the repository. @@ -174,7 +184,7 @@ X-Gitlab-Event: Tag Push Hook } ``` -## Issues events +### Issues events Triggered when a new issue is created or an existing issue was updated/closed/reopened. @@ -240,7 +250,7 @@ X-Gitlab-Event: Issue Hook } } ``` -## Comment events +### Comment events Triggered when a new comment is made on commits, merge requests, issues, and code snippets. The note data will be stored in `object_attributes` (e.g. `note`, `noteable_type`). The @@ -253,7 +263,7 @@ Valid target types: 3. `issue` 4. `snippet` -### Comment on commit +#### Comment on commit **Request header**: @@ -332,7 +342,7 @@ X-Gitlab-Event: Note Hook } ``` -### Comment on merge request +#### Comment on merge request **Request header**: @@ -459,7 +469,7 @@ X-Gitlab-Event: Note Hook } ``` -### Comment on issue +#### Comment on issue **Request header**: @@ -534,7 +544,7 @@ X-Gitlab-Event: Note Hook } ``` -### Comment on code snippet +#### Comment on code snippet **Request header**: @@ -607,7 +617,7 @@ X-Gitlab-Event: Note Hook } ``` -## Merge request events +### Merge request events Triggered when a new merge request is created, an existing merge request was updated/merged/closed or a commit is added in the source branch. @@ -699,7 +709,7 @@ X-Gitlab-Event: Merge Request Hook } ``` -## Wiki Page events +### Wiki Page events Triggered when a wiki page is created or edited. @@ -737,9 +747,9 @@ X-Gitlab-Event: Wiki Page Hook }, "wiki": { "web_url": "http://example.com/root/awesome-project/wikis/home", - "git_ssh_url": "git@example.com:root/awesome-project.wiki.git", - "git_http_url": "http://example.com/root/awesome-project.wiki.git", - "path_with_namespace": "root/awesome-project.wiki", + "git_ssh_url": "git@example.com:root/awesome-project.wiki.git", + "git_http_url": "http://example.com/root/awesome-project.wiki.git", + "path_with_namespace": "root/awesome-project.wiki", "default_branch": "master" }, "object_attributes": { @@ -754,7 +764,7 @@ X-Gitlab-Event: Wiki Page Hook } ``` -## Pipeline events +### Pipeline events Triggered on status change of Pipeline. @@ -922,8 +932,7 @@ X-Gitlab-Event: Pipeline Hook } ``` - -## Build events +### Build events Triggered on status change of a Build. @@ -935,7 +944,7 @@ X-Gitlab-Event: Build Hook **Request Body**: -``` +```json { "object_kind": "build", "ref": "gitlab-script-trigger", @@ -980,12 +989,13 @@ X-Gitlab-Event: Build Hook } ``` -#### Example webhook receiver +## Example webhook receiver If you want to see GitLab's webhooks in action for testing purposes you can use -a simple echo script running in a console session. +a simple echo script running in a console session. For the following script to +work you need to have Ruby installed. -Save the following file as `print_http_body.rb`. +Save the following file as `print_http_body.rb`: ```ruby require 'webrick' @@ -1005,7 +1015,8 @@ Pick an unused port (e.g. 8000) and start the script: `ruby print_http_body.rb 8000`. Then add your server as a webhook receiver in GitLab as `http://my.host:8000/`. -When you press 'Test Hook' in GitLab, you should see something like this in the console. +When you press 'Test Hook' in GitLab, you should see something like this in the +console: ``` {"before":"077a85dd266e6f3573ef7e9ef8ce3343ad659c4e","after":"95cd4a99e93bc4bbabacfa2cd10e6725b1403c60",<SNIP>} diff --git a/features/project/wiki.feature b/features/project/wiki.feature index 63ce3ccb536..a04228de03b 100644 --- a/features/project/wiki.feature +++ b/features/project/wiki.feature @@ -49,7 +49,6 @@ Feature: Project Wiki Scenario: View all pages Given I have an existing wiki page And I browse to that Wiki page - And I click on the "Pages" button Then I should see the existing page in the pages list Scenario: File exists in wiki repo @@ -72,13 +71,11 @@ Feature: Project Wiki @javascript Scenario: New Wiki page that has a path Given I create a New page with paths - And I click on the "Pages" button Then I should see non-escaped link in the pages list @javascript Scenario: Edit Wiki page that has a path Given I create a New page with paths - And I click on the "Pages" button And I edit the Wiki page with a path Then I should see a non-escaped path And I should see the Editing page @@ -88,7 +85,6 @@ Feature: Project Wiki @javascript Scenario: View the page history of a Wiki page that has a path Given I create a New page with paths - And I click on the "Pages" button And I view the page history of a Wiki page that has a path Then I should see a non-escaped path And I should see the page history @@ -96,7 +92,6 @@ Feature: Project Wiki @javascript Scenario: View an old page version of a Wiki page Given I create a New page with paths - And I click on the "Pages" button And I edit the Wiki page with a path Then I should see a non-escaped path And I should see the Editing page diff --git a/features/steps/project/source/markdown_render.rb b/features/steps/project/source/markdown_render.rb index 2134dae168a..dee6a8a5558 100644 --- a/features/steps/project/source/markdown_render.rb +++ b/features/steps/project/source/markdown_render.rb @@ -241,7 +241,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps page.within(:css, ".nav-text") do expect(page).to have_content "Test" - expect(page).to have_content "Edit Page" + expect(page).to have_content "Create Page" end end @@ -258,7 +258,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps expect(current_path).to eq namespace_project_wiki_path(@project.namespace, @project, "api") page.within(:css, ".nav-text") do - expect(page).to have_content "Edit" + expect(page).to have_content "Create" expect(page).to have_content "Api" end end @@ -271,7 +271,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps expect(current_path).to eq namespace_project_wiki_path(@project.namespace, @project, "raketasks") page.within(:css, ".nav-text") do - expect(page).to have_content "Edit" + expect(page).to have_content "Create" expect(page).to have_content "Rake" end end diff --git a/features/steps/project/wiki.rb b/features/steps/project/wiki.rb index 07a955b1a14..4cb0a21fbb4 100644 --- a/features/steps/project/wiki.rb +++ b/features/steps/project/wiki.rb @@ -29,7 +29,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps expect(page).to have_content "link test" click_link "link test" - expect(page).to have_content "Edit Page" + expect(page).to have_content "Create Page" end step 'I have an existing Wiki page' do @@ -80,13 +80,9 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps expect(page).to have_content "Page was successfully deleted" end - step 'I click on the "Pages" button' do - click_on "Pages" - end - step 'I should see the existing page in the pages list' do expect(page).to have_content current_user.name - expect(page).to have_content @page.title + expect(find('.wiki-pages')).to have_content @page.title.capitalize end step 'I have an existing Wiki page with images linked on page' do @@ -125,7 +121,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps step 'I should see the new wiki page form' do expect(current_path).to match('wikis/image.jpg') expect(page).to have_content('New Wiki Page') - expect(page).to have_content('Edit Page') + expect(page).to have_content('Create Page') end step 'I create a New page with paths' do @@ -142,8 +138,8 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps end step 'I edit the Wiki page with a path' do - expect(page).to have_content('three') - click_on 'three' + expect(find('.wiki-pages')).to have_content('Three') + click_on 'Three' expect(find('.nav-text')).to have_content('Three') click_on 'Edit' end @@ -157,7 +153,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps end step 'I view the page history of a Wiki page that has a path' do - click_on 'three' + click_on 'Three' click_on 'Page History' end diff --git a/features/steps/shared/markdown.rb b/features/steps/shared/markdown.rb index 56b36f7c46c..a036d9b884f 100644 --- a/features/steps/shared/markdown.rb +++ b/features/steps/shared/markdown.rb @@ -2,7 +2,7 @@ module SharedMarkdown include Spinach::DSL def header_should_have_correct_id_and_link(level, text, id, parent = ".wiki") - node = find("#{parent} h#{level} a##{id}") + node = find("#{parent} h#{level} a#user-content-#{id}") expect(node[:href]).to eq "##{id}" # Work around a weird Capybara behavior where calling `parent` on a node diff --git a/lib/api/entities.rb b/lib/api/entities.rb index fdb19558c1c..899d68bc6c7 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -141,8 +141,12 @@ module API options[:project].repository.commit(repo_branch.dereferenced_target) end + expose :merged do |repo_branch, options| + options[:project].repository.merged_to_root_ref?(repo_branch.name) + end + expose :protected do |repo_branch, options| - options[:project].protected_branch? repo_branch.name + options[:project].protected_branch?(repo_branch.name) end expose :developers_can_push do |repo_branch, options| @@ -170,6 +174,7 @@ module API class RepoCommit < Grape::Entity expose :id, :short_id, :title, :author_name, :author_email, :created_at + expose :committer_name, :committer_email expose :safe_message, as: :message end diff --git a/lib/api/files.rb b/lib/api/files.rb index 96510e651a3..28f306e45f3 100644 --- a/lib/api/files.rb +++ b/lib/api/files.rb @@ -23,140 +23,107 @@ module API branch_name: attrs[:branch_name] } end + + params :simple_file_params do + requires :file_path, type: String, desc: 'The path to new file. Ex. lib/class.rb' + requires :branch_name, type: String, desc: 'The name of branch' + requires :commit_message, type: String, desc: 'Commit Message' + optional :author_email, type: String, desc: 'The email of the author' + optional :author_name, type: String, desc: 'The name of the author' + end + + params :extended_file_params do + use :simple_file_params + requires :content, type: String, desc: 'File content' + optional :encoding, type: String, values: %w[base64], desc: 'File encoding' + end end + params do + requires :id, type: String, desc: 'The project ID' + end resource :projects do - # Get file from repository - # File content is Base64 encoded - # - # Parameters: - # file_path (required) - The path to the file. Ex. lib/class.rb - # ref (required) - The name of branch, tag or commit - # - # Example Request: - # GET /projects/:id/repository/files - # - # Example response: - # { - # "file_name": "key.rb", - # "file_path": "app/models/key.rb", - # "size": 1476, - # "encoding": "base64", - # "content": "IyA9PSBTY2hlbWEgSW5mb3...", - # "ref": "master", - # "blob_id": "79f7bbd25901e8334750839545a9bd021f0e4c83", - # "commit_id": "d5a3ff139356ce33e37e73add446f16869741b50", - # "last_commit_id": "570e7b2abdd848b95f2f578043fc23bd6f6fd24d", - # } - # + desc 'Get a file from repository' + params do + requires :file_path, type: String, desc: 'The path to the file. Ex. lib/class.rb' + requires :ref, type: String, desc: 'The name of branch, tag, or commit' + end get ":id/repository/files" do authorize! :download_code, user_project - required_attributes! [:file_path, :ref] - attrs = attributes_for_keys [:file_path, :ref] - ref = attrs.delete(:ref) - file_path = attrs.delete(:file_path) - - commit = user_project.commit(ref) - not_found! 'Commit' unless commit + commit = user_project.commit(params[:ref]) + not_found!('Commit') unless commit repo = user_project.repository - blob = repo.blob_at(commit.sha, file_path) + blob = repo.blob_at(commit.sha, params[:file_path]) + not_found!('File') unless blob - if blob - blob.load_all_data!(repo) - status(200) + blob.load_all_data!(repo) + status(200) - { - file_name: blob.name, - file_path: blob.path, - size: blob.size, - encoding: "base64", - content: Base64.strict_encode64(blob.data), - ref: ref, - blob_id: blob.id, - commit_id: commit.id, - last_commit_id: repo.last_commit_for_path(commit.sha, file_path).id - } - else - not_found! 'File' - end + { + file_name: blob.name, + file_path: blob.path, + size: blob.size, + encoding: "base64", + content: Base64.strict_encode64(blob.data), + ref: params[:ref], + blob_id: blob.id, + commit_id: commit.id, + last_commit_id: repo.last_commit_for_path(commit.sha, params[:file_path]).id + } end - # Create new file in repository - # - # Parameters: - # file_path (required) - The path to new file. Ex. lib/class.rb - # branch_name (required) - The name of branch - # content (required) - File content - # commit_message (required) - Commit message - # - # Example Request: - # POST /projects/:id/repository/files - # + desc 'Create new file in repository' + params do + use :extended_file_params + end post ":id/repository/files" do authorize! :push_code, user_project - required_attributes! [:file_path, :branch_name, :content, :commit_message] - attrs = attributes_for_keys [:file_path, :branch_name, :content, :commit_message, :encoding, :author_email, :author_name] - result = ::Files::CreateService.new(user_project, current_user, commit_params(attrs)).execute + file_params = declared_params(include_missing: false) + result = ::Files::CreateService.new(user_project, current_user, commit_params(file_params)).execute if result[:status] == :success status(201) - commit_response(attrs) + commit_response(file_params) else render_api_error!(result[:message], 400) end end - # Update existing file in repository - # - # Parameters: - # file_path (optional) - The path to file. Ex. lib/class.rb - # branch_name (required) - The name of branch - # content (required) - File content - # commit_message (required) - Commit message - # - # Example Request: - # PUT /projects/:id/repository/files - # + desc 'Update existing file in repository' + params do + use :extended_file_params + end put ":id/repository/files" do authorize! :push_code, user_project - required_attributes! [:file_path, :branch_name, :content, :commit_message] - attrs = attributes_for_keys [:file_path, :branch_name, :content, :commit_message, :encoding, :author_email, :author_name] - result = ::Files::UpdateService.new(user_project, current_user, commit_params(attrs)).execute + file_params = declared_params(include_missing: false) + result = ::Files::UpdateService.new(user_project, current_user, commit_params(file_params)).execute if result[:status] == :success status(200) - commit_response(attrs) + commit_response(file_params) else http_status = result[:http_status] || 400 render_api_error!(result[:message], http_status) end end - # Delete existing file in repository - # - # Parameters: - # file_path (optional) - The path to file. Ex. lib/class.rb - # branch_name (required) - The name of branch - # content (required) - File content - # commit_message (required) - Commit message - # - # Example Request: - # DELETE /projects/:id/repository/files - # + desc 'Delete an existing file in repository' + params do + use :simple_file_params + end delete ":id/repository/files" do authorize! :push_code, user_project - required_attributes! [:file_path, :branch_name, :commit_message] - attrs = attributes_for_keys [:file_path, :branch_name, :commit_message, :author_email, :author_name] - result = ::Files::DeleteService.new(user_project, current_user, commit_params(attrs)).execute + file_params = declared_params(include_missing: false) + result = ::Files::DeleteService.new(user_project, current_user, commit_params(file_params)).execute if result[:status] == :success status(200) - commit_response(attrs) + commit_response(file_params) else render_api_error!(result[:message], 400) end diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 34d9c3c6932..7f94ede7940 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -141,6 +141,10 @@ module API unauthorized! unless current_user end + def authenticate_non_get! + authenticate! unless %w[GET HEAD].include?(route.route_method) + end + def authenticate_by_gitlab_shell_token! input = params['secret_token'].try(:chomp) unless Devise.secure_compare(secret_token, input) @@ -149,6 +153,7 @@ module API end def authenticated_as_admin! + authenticate! forbidden! unless current_user.is_admin? end @@ -308,11 +313,6 @@ module API # Projects helpers def filter_projects(projects) - # If the archived parameter is passed, limit results accordingly - if params[:archived].present? - projects = projects.where(archived: to_boolean(params[:archived])) - end - if params[:search].present? projects = projects.search(params[:search]) end @@ -321,25 +321,8 @@ module API projects = projects.search_by_visibility(params[:visibility]) end - projects.reorder(project_order_by => project_sort) - end - - def project_order_by - order_fields = %w(id name path created_at updated_at last_activity_at) - - if order_fields.include?(params['order_by']) - params['order_by'] - else - 'created_at' - end - end - - def project_sort - if params["sort"] == 'asc' - :asc - else - :desc - end + projects = projects.where(archived: params[:archived]) + projects.reorder(params[:order_by] => params[:sort]) end # file helpers diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 2ea3c433ae2..2929d2157dc 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -1,293 +1,295 @@ module API # Projects API class Projects < Grape::API - before { authenticate! } + include PaginationParams + + before { authenticate_non_get! } + + helpers do + params :optional_params do + optional :description, type: String, desc: 'The description of the project' + optional :issues_enabled, type: Boolean, desc: 'Flag indication if the issue tracker is enabled' + optional :merge_requests_enabled, type: Boolean, desc: 'Flag indication if merge requests are enabled' + optional :wiki_enabled, type: Boolean, desc: 'Flag indication if the wiki is enabled' + optional :builds_enabled, type: Boolean, desc: 'Flag indication if builds are enabled' + optional :snippets_enabled, type: Boolean, desc: 'Flag indication if snippets are enabled' + optional :shared_runners_enabled, type: Boolean, desc: 'Flag indication if shared runners are enabled for that project' + optional :container_registry_enabled, type: Boolean, desc: 'Flag indication if the container registry is enabled for that project' + optional :lfs_enabled, type: Boolean, desc: 'Flag indication if Git LFS is enabled for that project' + optional :public, type: Boolean, desc: 'Create a public project. The same as visibility_level = 20.' + optional :visibility_level, type: Integer, values: [ + Gitlab::VisibilityLevel::PRIVATE, + Gitlab::VisibilityLevel::INTERNAL, + Gitlab::VisibilityLevel::PUBLIC ], desc: 'Create a public project. The same as visibility_level = 20.' + optional :public_builds, type: Boolean, desc: 'Perform public builds' + optional :request_access_enabled, type: Boolean, desc: 'Allow users to request member access' + optional :only_allow_merge_if_build_succeeds, type: Boolean, desc: 'Only allow to merge if builds succeed' + optional :only_allow_merge_if_all_discussions_are_resolved, type: Boolean, desc: 'Only allow to merge if all discussions are resolved' + end - resource :projects, requirements: { id: /[^\/]+/ } do - helpers do - def map_public_to_visibility_level(attrs) - publik = attrs.delete(:public) - if publik.present? && !attrs[:visibility_level].present? - publik = to_boolean(publik) - # Since setting the public attribute to private could mean either - # private or internal, use the more conservative option, private. - attrs[:visibility_level] = (publik == true) ? Gitlab::VisibilityLevel::PUBLIC : Gitlab::VisibilityLevel::PRIVATE - end - attrs + def map_public_to_visibility_level(attrs) + publik = attrs.delete(:public) + if !publik.nil? && !attrs[:visibility_level].present? + # Since setting the public attribute to private could mean either + # private or internal, use the more conservative option, private. + attrs[:visibility_level] = (publik == true) ? Gitlab::VisibilityLevel::PUBLIC : Gitlab::VisibilityLevel::PRIVATE end + attrs end + end - # Get a projects list for authenticated user - # - # Example Request: - # GET /projects - get do - projects = current_user.authorized_projects - projects = filter_projects(projects) - projects = paginate projects - entity = params[:simple] ? Entities::BasicProjectDetails : Entities::ProjectWithAccess + resource :projects do + helpers do + params :sort_params do + optional :order_by, type: String, values: %w[id name path created_at updated_at last_activity_at], + default: 'created_at', desc: 'Return projects ordered by field' + optional :sort, type: String, values: %w[asc desc], default: 'desc', + desc: 'Return projects sorted in ascending and descending order' + end - present projects, with: entity, user: current_user + params :filter_params do + optional :archived, type: Boolean, default: false, desc: 'Limit by archived status' + optional :visibility, type: String, values: %w[public internal private], + desc: 'Limit by visibility' + optional :search, type: String, desc: 'Return list of authorized projects matching the search criteria' + use :sort_params + end + + params :create_params do + optional :namespace_id, type: Integer, desc: 'Namespace ID for the new project. Default to the user namespace.' + optional :import_url, type: String, desc: 'URL from which the project is imported' + end end - # Get a list of visible projects for authenticated user - # - # Example Request: - # GET /projects/visible + desc 'Get a list of visible projects for authenticated user' do + success Entities::BasicProjectDetails + end + params do + optional :simple, type: Boolean, default: false, + desc: 'Return only the ID, URL, name, and path of each project' + use :filter_params + use :pagination + end get '/visible' do projects = ProjectsFinder.new.execute(current_user) projects = filter_projects(projects) - projects = paginate projects + entity = params[:simple] || !current_user ? Entities::BasicProjectDetails : Entities::ProjectWithAccess + + present paginate(projects), with: entity, user: current_user + end + + desc 'Get a projects list for authenticated user' do + success Entities::BasicProjectDetails + end + params do + optional :simple, type: Boolean, default: false, + desc: 'Return only the ID, URL, name, and path of each project' + use :filter_params + use :pagination + end + get do + authenticate! + + projects = current_user.authorized_projects + projects = filter_projects(projects) entity = params[:simple] ? Entities::BasicProjectDetails : Entities::ProjectWithAccess - present projects, with: entity, user: current_user + present paginate(projects), with: entity, user: current_user end - # Get an owned projects list for authenticated user - # - # Example Request: - # GET /projects/owned + desc 'Get an owned projects list for authenticated user' do + success Entities::BasicProjectDetails + end + params do + use :filter_params + use :pagination + end get '/owned' do + authenticate! + projects = current_user.owned_projects projects = filter_projects(projects) - projects = paginate projects - present projects, with: Entities::ProjectWithAccess, user: current_user + + present paginate(projects), with: Entities::ProjectWithAccess, user: current_user end - # Gets starred project for the authenticated user - # - # Example Request: - # GET /projects/starred + desc 'Gets starred project for the authenticated user' do + success Entities::BasicProjectDetails + end + params do + use :filter_params + use :pagination + end get '/starred' do + authenticate! + projects = current_user.viewable_starred_projects projects = filter_projects(projects) - projects = paginate projects - present projects, with: Entities::Project, user: current_user + + present paginate(projects), with: Entities::Project, user: current_user end - # Get all projects for admin user - # - # Example Request: - # GET /projects/all + desc 'Get all projects for admin user' do + success Entities::BasicProjectDetails + end + params do + use :filter_params + use :pagination + end get '/all' do authenticated_as_admin! + projects = Project.all projects = filter_projects(projects) - projects = paginate projects - present projects, with: Entities::ProjectWithAccess, user: current_user + + present paginate(projects), with: Entities::ProjectWithAccess, user: current_user end - # Get a single project - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # GET /projects/:id - get ":id" do - present user_project, with: Entities::ProjectWithAccess, user: current_user, - user_can_admin_project: can?(current_user, :admin_project, user_project) + desc 'Search for projects the current user has access to' do + success Entities::Project + end + params do + requires :query, type: String, desc: 'The project name to be searched' + use :sort_params + use :pagination + end + get "/search/:query" do + search_service = Search::GlobalService.new(current_user, search: params[:query]).execute + projects = search_service.objects('projects', params[:page]) + projects = projects.reorder(params[:order_by] => params[:sort]) + + present paginate(projects), with: Entities::Project end - # Get events for a single project - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # GET /projects/:id/events - get ":id/events" do - events = paginate user_project.events.recent - present events, with: Entities::Event - end - - # Create new project - # - # Parameters: - # name (required) - name for new project - # description (optional) - short project description - # issues_enabled (optional) - # merge_requests_enabled (optional) - # builds_enabled (optional) - # wiki_enabled (optional) - # snippets_enabled (optional) - # container_registry_enabled (optional) - # shared_runners_enabled (optional) - # namespace_id (optional) - defaults to user namespace - # public (optional) - if true same as setting visibility_level = 20 - # visibility_level (optional) - 0 by default - # import_url (optional) - # public_builds (optional) - # lfs_enabled (optional) - # request_access_enabled (optional) - Allow users to request member access - # Example Request - # POST /projects + desc 'Create new project' do + success Entities::Project + end + params do + requires :name, type: String, desc: 'The name of the project' + optional :path, type: String, desc: 'The path of the repository' + use :optional_params + use :create_params + end post do - required_attributes! [:name] - attrs = attributes_for_keys [:builds_enabled, - :container_registry_enabled, - :description, - :import_url, - :issues_enabled, - :lfs_enabled, - :merge_requests_enabled, - :name, - :namespace_id, - :only_allow_merge_if_build_succeeds, - :path, - :public, - :public_builds, - :request_access_enabled, - :shared_runners_enabled, - :snippets_enabled, - :visibility_level, - :wiki_enabled, - :only_allow_merge_if_all_discussions_are_resolved] - attrs = map_public_to_visibility_level(attrs) - @project = ::Projects::CreateService.new(current_user, attrs).execute - if @project.saved? - present @project, with: Entities::Project, - user_can_admin_project: can?(current_user, :admin_project, @project) + attrs = map_public_to_visibility_level(declared_params(include_missing: false)) + project = ::Projects::CreateService.new(current_user, attrs).execute + + if project.saved? + present project, with: Entities::Project, + user_can_admin_project: can?(current_user, :admin_project, project) else - if @project.errors[:limit_reached].present? - error!(@project.errors[:limit_reached], 403) + if project.errors[:limit_reached].present? + error!(project.errors[:limit_reached], 403) end - render_validation_error!(@project) + render_validation_error!(project) end end - # Create new project for a specified user. Only available to admin users. - # - # Parameters: - # user_id (required) - The ID of a user - # name (required) - name for new project - # description (optional) - short project description - # default_branch (optional) - 'master' by default - # issues_enabled (optional) - # merge_requests_enabled (optional) - # builds_enabled (optional) - # wiki_enabled (optional) - # snippets_enabled (optional) - # container_registry_enabled (optional) - # shared_runners_enabled (optional) - # public (optional) - if true same as setting visibility_level = 20 - # visibility_level (optional) - # import_url (optional) - # public_builds (optional) - # lfs_enabled (optional) - # request_access_enabled (optional) - Allow users to request member access - # Example Request - # POST /projects/user/:user_id + desc 'Create new project for a specified user. Only available to admin users.' do + success Entities::Project + end + params do + requires :name, type: String, desc: 'The name of the project' + requires :user_id, type: Integer, desc: 'The ID of a user' + optional :default_branch, type: String, desc: 'The default branch of the project' + use :optional_params + use :create_params + end post "user/:user_id" do authenticated_as_admin! - user = User.find(params[:user_id]) - attrs = attributes_for_keys [:builds_enabled, - :default_branch, - :description, - :import_url, - :issues_enabled, - :lfs_enabled, - :merge_requests_enabled, - :name, - :only_allow_merge_if_build_succeeds, - :public, - :public_builds, - :request_access_enabled, - :shared_runners_enabled, - :snippets_enabled, - :visibility_level, - :wiki_enabled, - :only_allow_merge_if_all_discussions_are_resolved] - attrs = map_public_to_visibility_level(attrs) - @project = ::Projects::CreateService.new(user, attrs).execute - if @project.saved? - present @project, with: Entities::Project, - user_can_admin_project: can?(current_user, :admin_project, @project) + user = User.find_by(id: params.delete(:user_id)) + not_found!('User') unless user + + attrs = map_public_to_visibility_level(declared_params(include_missing: false)) + project = ::Projects::CreateService.new(user, attrs).execute + + if project.saved? + present project, with: Entities::Project, + user_can_admin_project: can?(current_user, :admin_project, project) else - render_validation_error!(@project) + render_validation_error!(project) end end + end + + params do + requires :id, type: String, desc: 'The ID of a project' + end + resource :projects, requirements: { id: /[^\/]+/ } do + desc 'Get a single project' do + success Entities::ProjectWithAccess + end + get ":id" do + entity = current_user ? Entities::ProjectWithAccess : Entities::BasicProjectDetails + present user_project, with: entity, user: current_user, + user_can_admin_project: can?(current_user, :admin_project, user_project) + end - # Fork new project for the current user or provided namespace. - # - # Parameters: - # id (required) - The ID of a project - # namespace (optional) - The ID or name of the namespace that the project will be forked into. - # Example Request - # POST /projects/fork/:id + desc 'Get events for a single project' do + success Entities::Event + end + params do + use :pagination + end + get ":id/events" do + present paginate(user_project.events.recent), with: Entities::Event + end + + desc 'Fork new project for the current user or provided namespace.' do + success Entities::Project + end + params do + optional :namespace, type: String, desc: 'The ID or name of the namespace that the project will be forked into' + end post 'fork/:id' do - attrs = {} - namespace_id = params[:namespace] + fork_params = declared_params(include_missing: false) + namespace_id = fork_params[:namespace] if namespace_id.present? - namespace = Namespace.find_by(id: namespace_id) || Namespace.find_by_path_or_name(namespace_id) + fork_params[:namespace] = if namespace_id =~ /^\d+$/ + Namespace.find_by(id: namespace_id) + else + Namespace.find_by_path_or_name(namespace_id) + end - unless namespace && can?(current_user, :create_projects, namespace) + unless fork_params[:namespace] && can?(current_user, :create_projects, fork_params[:namespace]) not_found!('Target Namespace') end - - attrs[:namespace] = namespace end - @forked_project = - ::Projects::ForkService.new(user_project, - current_user, - attrs).execute + forked_project = ::Projects::ForkService.new(user_project, current_user, fork_params).execute - if @forked_project.errors.any? - conflict!(@forked_project.errors.messages) + if forked_project.errors.any? + conflict!(forked_project.errors.messages) else - present @forked_project, with: Entities::Project, - user_can_admin_project: can?(current_user, :admin_project, @forked_project) + present forked_project, with: Entities::Project, + user_can_admin_project: can?(current_user, :admin_project, forked_project) end end - # Update an existing project - # - # Parameters: - # id (required) - the id of a project - # name (optional) - name of a project - # path (optional) - path of a project - # description (optional) - short project description - # issues_enabled (optional) - # merge_requests_enabled (optional) - # builds_enabled (optional) - # wiki_enabled (optional) - # snippets_enabled (optional) - # container_registry_enabled (optional) - # shared_runners_enabled (optional) - # public (optional) - if true same as setting visibility_level = 20 - # visibility_level (optional) - visibility level of a project - # public_builds (optional) - # lfs_enabled (optional) - # Example Request - # PUT /projects/:id + desc 'Update an existing project' do + success Entities::Project + end + params do + optional :name, type: String, desc: 'The name of the project' + optional :default_branch, type: String, desc: 'The default branch of the project' + optional :path, type: String, desc: 'The path of the repository' + use :optional_params + at_least_one_of :name, :description, :issues_enabled, :merge_requests_enabled, + :wiki_enabled, :builds_enabled, :snippets_enabled, + :shared_runners_enabled, :container_registry_enabled, + :lfs_enabled, :public, :visibility_level, :public_builds, + :request_access_enabled, :only_allow_merge_if_build_succeeds, + :only_allow_merge_if_all_discussions_are_resolved, :path, + :default_branch + end put ':id' do - attrs = attributes_for_keys [:builds_enabled, - :container_registry_enabled, - :default_branch, - :description, - :issues_enabled, - :lfs_enabled, - :merge_requests_enabled, - :name, - :only_allow_merge_if_build_succeeds, - :path, - :public, - :public_builds, - :request_access_enabled, - :shared_runners_enabled, - :snippets_enabled, - :visibility_level, - :wiki_enabled, - :only_allow_merge_if_all_discussions_are_resolved] - attrs = map_public_to_visibility_level(attrs) authorize_admin_project + attrs = map_public_to_visibility_level(declared_params(include_missing: false)) authorize! :rename_project, user_project if attrs[:name].present? - if attrs[:visibility_level].present? - authorize! :change_visibility_level, user_project - end + authorize! :change_visibility_level, user_project if attrs[:visibility_level].present? - ::Projects::UpdateService.new(user_project, - current_user, attrs).execute + ::Projects::UpdateService.new(user_project, current_user, attrs).execute if user_project.errors.any? render_validation_error!(user_project) @@ -297,12 +299,9 @@ module API end end - # Archive project - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # PUT /projects/:id/archive + desc 'Archive a project' do + success Entities::Project + end post ':id/archive' do authorize!(:archive_project, user_project) @@ -311,12 +310,9 @@ module API present user_project, with: Entities::Project end - # Unarchive project - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # PUT /projects/:id/unarchive + desc 'Unarchive a project' do + success Entities::Project + end post ':id/unarchive' do authorize!(:archive_project, user_project) @@ -325,12 +321,9 @@ module API present user_project, with: Entities::Project end - # Star project - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # POST /projects/:id/star + desc 'Star a project' do + success Entities::Project + end post ':id/star' do if current_user.starred?(user_project) not_modified! @@ -342,12 +335,9 @@ module API end end - # Unstar project - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # DELETE /projects/:id/star + desc 'Unstar a project' do + success Entities::Project + end delete ':id/star' do if current_user.starred?(user_project) current_user.toggle_star(user_project) @@ -359,67 +349,51 @@ module API end end - # Remove project - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # DELETE /projects/:id + desc 'Remove a project' delete ":id" do authorize! :remove_project, user_project ::Projects::DestroyService.new(user_project, current_user, {}).async_execute end - # Mark this project as forked from another - # - # Parameters: - # id: (required) - The ID of the project being marked as a fork - # forked_from_id: (required) - The ID of the project it was forked from - # Example Request: - # POST /projects/:id/fork/:forked_from_id + desc 'Mark this project as forked from another' + params do + requires :forked_from_id, type: String, desc: 'The ID of the project it was forked from' + end post ":id/fork/:forked_from_id" do authenticated_as_admin! + forked_from_project = find_project!(params[:forked_from_id]) - unless forked_from_project.nil? - if user_project.forked_from_project.nil? - user_project.create_forked_project_link(forked_to_project_id: user_project.id, forked_from_project_id: forked_from_project.id) - else - render_api_error!("Project already forked", 409) - end + not_found!("Source Project") unless forked_from_project + + if user_project.forked_from_project.nil? + user_project.create_forked_project_link(forked_to_project_id: user_project.id, forked_from_project_id: forked_from_project.id) else - not_found!("Source Project") + render_api_error!("Project already forked", 409) end end - # Remove a forked_from relationship - # - # Parameters: - # id: (required) - The ID of the project being marked as a fork - # Example Request: - # DELETE /projects/:id/fork + desc 'Remove a forked_from relationship' delete ":id/fork" do authorize! :remove_fork_project, user_project + if user_project.forked? user_project.forked_project_link.destroy + else + not_modified! end end - # Share project with group - # - # Parameters: - # id (required) - The ID of a project - # group_id (required) - The ID of a group - # group_access (required) - Level of permissions for sharing - # expires_at (optional) - Share expiration date - # - # Example Request: - # POST /projects/:id/share + desc 'Share the project with a group' do + success Entities::ProjectGroupLink + end + params do + requires :group_id, type: Integer, desc: 'The ID of a group' + requires :group_access, type: Integer, values: Gitlab::Access.values, desc: 'The group access level' + optional :expires_at, type: Date, desc: 'Share expiration date' + end post ":id/share" do authorize! :admin_project, user_project - required_attributes! [:group_id, :group_access] - attrs = attributes_for_keys [:group_id, :group_access, :expires_at] - - group = Group.find_by_id(attrs[:group_id]) + group = Group.find_by_id(params[:group_id]) unless group && can?(current_user, :read_group, group) not_found!('Group') @@ -429,7 +403,7 @@ module API return render_api_error!("The project sharing with group is disabled", 400) end - link = user_project.project_group_links.new(attrs) + link = user_project.project_group_links.new(declared_params(include_missing: false)) if link.save present link, with: Entities::ProjectGroupLink @@ -451,40 +425,26 @@ module API no_content! end - # Upload a file - # - # Parameters: - # id: (required) - The ID of the project - # file: (required) - The file to be uploaded + desc 'Upload a file' + params do + requires :file, type: File, desc: 'The file to be uploaded' + end post ":id/uploads" do ::Projects::UploadService.new(user_project, params[:file]).execute end - # search for projects current_user has access to - # - # Parameters: - # query (required) - A string contained in the project name - # per_page (optional) - number of projects to return per page - # page (optional) - the page to retrieve - # Example Request: - # GET /projects/search/:query - get "/search/:query" do - search_service = Search::GlobalService.new(current_user, search: params[:query]).execute - projects = search_service.objects('projects', params[:page]) - projects = projects.reorder(project_order_by => project_sort) - - present paginate(projects), with: Entities::Project + desc 'Get the users list of a project' do + success Entities::UserBasic + end + params do + optional :search, type: String, desc: 'Return list of users matching the search criteria' + use :pagination end - - # Get a users list - # - # Example Request: - # GET /users get ':id/users' do - @users = User.where(id: user_project.team.users.map(&:id)) - @users = @users.search(params[:search]) if params[:search].present? - @users = paginate @users - present @users, with: Entities::UserBasic + users = user_project.team.users + users = users.search(params[:search]) if params[:search].present? + + present paginate(users), with: Entities::UserBasic end end end diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb index 0dfffaf0bc6..96c20100541 100644 --- a/lib/backup/manager.rb +++ b/lib/backup/manager.rb @@ -14,7 +14,7 @@ module Backup s[:gitlab_version] = Gitlab::VERSION s[:tar_version] = tar_version s[:skipped] = ENV["SKIP"] - tar_file = "#{s[:backup_created_at].to_i}_gitlab_backup.tar" + tar_file = s[:backup_created_at].strftime('%s_%Y_%m_%d') + '_gitlab_backup.tar' Dir.chdir(Gitlab.config.backup.path) do File.open("#{Gitlab.config.backup.path}/backup_information.yml", @@ -83,10 +83,14 @@ module Backup Dir.chdir(Gitlab.config.backup.path) do file_list = Dir.glob('*_gitlab_backup.tar') - file_list.map! { |f| $1.to_i if f =~ /(\d+)_gitlab_backup.tar/ } - file_list.sort.each do |timestamp| - if Time.at(timestamp) < (Time.now - keep_time) - if Kernel.system(*%W(rm #{timestamp}_gitlab_backup.tar)) + file_list.map! do |path_string| + if path_string =~ /(\d+)(?:_\d{4}_\d{2}_\d{2})?_gitlab_backup\.tar/ + { timestamp: $1.to_i, path: path_string } + end + end + file_list.sort.each do |file| + if Time.at(file[:timestamp]) < (Time.now - keep_time) + if Kernel.system(*%W(rm #{file[:path]})) removed += 1 end end @@ -103,7 +107,7 @@ module Backup Dir.chdir(Gitlab.config.backup.path) # check for existing backups in the backup dir - file_list = Dir.glob("*_gitlab_backup.tar").each.map { |f| f.split(/_/).first.to_i } + file_list = Dir.glob("*_gitlab_backup.tar") puts "no backups found" if file_list.count == 0 if file_list.count > 1 && ENV["BACKUP"].nil? @@ -112,7 +116,7 @@ module Backup exit 1 end - tar_file = ENV["BACKUP"].nil? ? File.join("#{file_list.first}_gitlab_backup.tar") : File.join(ENV["BACKUP"] + "_gitlab_backup.tar") + tar_file = ENV["BACKUP"].nil? ? file_list.first : file_list.grep(ENV['BACKUP']).first unless File.exist?(tar_file) puts "The specified backup doesn't exist!" diff --git a/lib/banzai/filter/table_of_contents_filter.rb b/lib/banzai/filter/table_of_contents_filter.rb index a4eda6fdf76..8e7084f2543 100644 --- a/lib/banzai/filter/table_of_contents_filter.rb +++ b/lib/banzai/filter/table_of_contents_filter.rb @@ -35,9 +35,11 @@ module Banzai headers[id] += 1 if header_content = node.children.first + # namespace detection will be automatically handled via javascript (see issue #22781) + namespace = "user-content-" href = "#{id}#{uniq}" push_toc(href, text) - header_content.add_previous_sibling(anchor_tag(href)) + header_content.add_previous_sibling(anchor_tag("#{namespace}#{href}", href)) end end @@ -48,8 +50,8 @@ module Banzai private - def anchor_tag(href) - %Q{<a id="#{href}" class="anchor" href="##{href}" aria-hidden="true"></a>} + def anchor_tag(id, href) + %Q{<a id="#{id}" class="anchor" href="##{href}" aria-hidden="true"></a>} end def push_toc(href, text) diff --git a/lib/gitlab/chat_commands/issue_create.rb b/lib/gitlab/chat_commands/issue_create.rb index 99c1382af44..1dba85c1b51 100644 --- a/lib/gitlab/chat_commands/issue_create.rb +++ b/lib/gitlab/chat_commands/issue_create.rb @@ -4,11 +4,11 @@ module Gitlab def self.match(text) # we can not match \n with the dot by passing the m modifier as than # the title and description are not seperated - /\Aissue\s+create\s+(?<title>[^\n]*)\n*(?<description>(.|\n)*)/.match(text) + /\Aissue\s+(new|create)\s+(?<title>[^\n]*)\n*(?<description>(.|\n)*)/.match(text) end def self.help_message - 'issue create <title>\n<description>' + 'issue new <title>\n<description>' end def self.allowed?(project, user) diff --git a/lib/gitlab/diff/file_collection/merge_request_diff.rb b/lib/gitlab/diff/file_collection/merge_request_diff.rb index 26bb0bc16f5..56530448f36 100644 --- a/lib/gitlab/diff/file_collection/merge_request_diff.rb +++ b/lib/gitlab/diff/file_collection/merge_request_diff.rb @@ -61,7 +61,7 @@ module Gitlab end def cacheable?(diff_file) - @merge_request_diff.present? && diff_file.blob.text? + @merge_request_diff.present? && diff_file.blob && diff_file.blob.text? end def cache_key diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index bcbf6455998..db07b7c5fcc 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -46,7 +46,7 @@ module Gitlab def download_access_check if user user_download_access_check - elsif deploy_key.nil? && !Guest.can?(:download_code, project) + elsif deploy_key.nil? && !guest_can_downlod_code? raise UnauthorizedError, ERROR_MESSAGES[:download] end end @@ -59,6 +59,10 @@ module Gitlab end end + def guest_can_downlod_code? + Guest.can?(:download_code, project) + end + def user_download_access_check unless user_can_download_code? || build_can_download_code? raise UnauthorizedError, ERROR_MESSAGES[:download] diff --git a/lib/gitlab/git_access_wiki.rb b/lib/gitlab/git_access_wiki.rb index f71d3575909..2c06c4ff1ef 100644 --- a/lib/gitlab/git_access_wiki.rb +++ b/lib/gitlab/git_access_wiki.rb @@ -1,5 +1,13 @@ module Gitlab class GitAccessWiki < GitAccess + def guest_can_downlod_code? + Guest.can?(:download_wiki_code, project) + end + + def user_can_download_code? + authentication_abilities.include?(:download_code) && user_access.can_do_action?(:download_wiki_code) + end + def change_access_check(change) if user_access.can_do_action?(:create_wiki) build_status_object(true) diff --git a/lib/gitlab/github_import/branch_formatter.rb b/lib/gitlab/github_import/branch_formatter.rb index 4750675ae9d..0a8d05b5fe1 100644 --- a/lib/gitlab/github_import/branch_formatter.rb +++ b/lib/gitlab/github_import/branch_formatter.rb @@ -8,7 +8,7 @@ module Gitlab end def valid? - repo.present? + sha.present? && ref.present? end private diff --git a/lib/gitlab/o_auth/user.rb b/lib/gitlab/o_auth/user.rb index a8b4dc2a83f..96ed20af918 100644 --- a/lib/gitlab/o_auth/user.rb +++ b/lib/gitlab/o_auth/user.rb @@ -39,7 +39,7 @@ module Gitlab log.info "(#{provider}) saving user #{auth_hash.email} from login with extern_uid => #{auth_hash.uid}" gl_user rescue ActiveRecord::RecordInvalid => e - log.info "(#{provider}) Error saving user: #{gl_user.errors.full_messages}" + log.info "(#{provider}) Error saving user #{auth_hash.uid} (#{auth_hash.email}): #{gl_user.errors.full_messages}" return self, e.record.errors end diff --git a/lib/tasks/gitlab/helpers.rake b/lib/tasks/gitlab/helpers.rake new file mode 100644 index 00000000000..dd2d5861481 --- /dev/null +++ b/lib/tasks/gitlab/helpers.rake @@ -0,0 +1,8 @@ +require 'tasks/gitlab/task_helpers' + +# Prevent StateMachine warnings from outputting during a cron task +StateMachines::Machine.ignore_method_conflicts = true if ENV['CRON'] + +namespace :gitlab do + include Gitlab::TaskHelpers +end diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake index 58761a129d4..5a09cd7ce41 100644 --- a/lib/tasks/gitlab/shell.rake +++ b/lib/tasks/gitlab/shell.rake @@ -5,42 +5,23 @@ namespace :gitlab do warn_user_is_not_gitlab default_version = Gitlab::Shell.version_required - default_version_tag = 'v' + default_version - args.with_defaults(tag: default_version_tag, repo: "https://gitlab.com/gitlab-org/gitlab-shell.git") + default_version_tag = "v#{default_version}" + args.with_defaults(tag: default_version_tag, repo: 'https://gitlab.com/gitlab-org/gitlab-shell.git') - user = Gitlab.config.gitlab.user - home_dir = Rails.env.test? ? Rails.root.join('tmp/tests') : Gitlab.config.gitlab.user_home gitlab_url = Gitlab.config.gitlab.url # gitlab-shell requires a / at the end of the url gitlab_url += '/' unless gitlab_url.end_with?('/') target_dir = Gitlab.config.gitlab_shell.path - # Clone if needed - if File.directory?(target_dir) - Dir.chdir(target_dir) do - system(*%W(Gitlab.config.git.bin_path} fetch --tags --quiet)) - system(*%W(Gitlab.config.git.bin_path} checkout --quiet #{default_version_tag})) - end - else - system(*%W(#{Gitlab.config.git.bin_path} clone -- #{args.repo} #{target_dir})) - end + checkout_or_clone_tag(tag: default_version_tag, repo: args.repo, target_dir: target_dir) # Make sure we're on the right tag Dir.chdir(target_dir) do - # First try to checkout without fetching - # to avoid stalling tests if the Internet is down. - reseted = reset_to_commit(args) - - unless reseted - system(*%W(#{Gitlab.config.git.bin_path} fetch origin)) - reset_to_commit(args) - end - config = { - user: user, + user: Gitlab.config.gitlab.user, gitlab_url: gitlab_url, http_settings: {self_signed_cert: false}.stringify_keys, - auth_file: File.join(home_dir, ".ssh", "authorized_keys"), + auth_file: File.join(user_home, ".ssh", "authorized_keys"), redis: { bin: %x{which redis-cli}.chomp, namespace: "resque:gitlab" @@ -74,7 +55,7 @@ namespace :gitlab do # be an issue since it is more than likely that there are no "normal" # user accounts on a gitlab server). The alternative is for the admin to # install a ruby (1.9.3+) in the global path. - File.open(File.join(home_dir, ".ssh", "environment"), "w+") do |f| + File.open(File.join(user_home, ".ssh", "environment"), "w+") do |f| f.puts "PATH=#{ENV['PATH']}" end @@ -142,15 +123,4 @@ namespace :gitlab do puts "Quitting...".color(:red) exit 1 end - - def reset_to_commit(args) - tag, status = Gitlab::Popen.popen(%W(#{Gitlab.config.git.bin_path} describe -- #{args.tag})) - - unless status.zero? - tag, status = Gitlab::Popen.popen(%W(#{Gitlab.config.git.bin_path} describe -- origin/#{args.tag})) - end - - tag = tag.strip - system(*%W(#{Gitlab.config.git.bin_path} reset --hard #{tag})) - end end diff --git a/lib/tasks/gitlab/task_helpers.rake b/lib/tasks/gitlab/task_helpers.rake deleted file mode 100644 index 74be413423a..00000000000 --- a/lib/tasks/gitlab/task_helpers.rake +++ /dev/null @@ -1,140 +0,0 @@ -module Gitlab - class TaskAbortedByUserError < StandardError; end -end - -require 'rainbow/ext/string' - -# Prevent StateMachine warnings from outputting during a cron task -StateMachines::Machine.ignore_method_conflicts = true if ENV['CRON'] - -namespace :gitlab do - - # Ask if the user wants to continue - # - # Returns "yes" the user chose to continue - # Raises Gitlab::TaskAbortedByUserError if the user chose *not* to continue - def ask_to_continue - answer = prompt("Do you want to continue (yes/no)? ".color(:blue), %w{yes no}) - raise Gitlab::TaskAbortedByUserError unless answer == "yes" - end - - # Check which OS is running - # - # It will primarily use lsb_relase to determine the OS. - # It has fallbacks to Debian, SuSE, OS X and systems running systemd. - def os_name - os_name = run_command(%W(lsb_release -irs)) - os_name ||= if File.readable?('/etc/system-release') - File.read('/etc/system-release') - end - os_name ||= if File.readable?('/etc/debian_version') - debian_version = File.read('/etc/debian_version') - "Debian #{debian_version}" - end - os_name ||= if File.readable?('/etc/SuSE-release') - File.read('/etc/SuSE-release') - end - os_name ||= if os_x_version = run_command(%W(sw_vers -productVersion)) - "Mac OS X #{os_x_version}" - end - os_name ||= if File.readable?('/etc/os-release') - File.read('/etc/os-release').match(/PRETTY_NAME=\"(.+)\"/)[1] - end - os_name.try(:squish!) - end - - # Prompt the user to input something - # - # message - the message to display before input - # choices - array of strings of acceptable answers or nil for any answer - # - # Returns the user's answer - def prompt(message, choices = nil) - begin - print(message) - answer = STDIN.gets.chomp - end while choices.present? && !choices.include?(answer) - answer - end - - # Runs the given command and matches the output against the given pattern - # - # Returns nil if nothing matched - # Returns the MatchData if the pattern matched - # - # see also #run_command - # see also String#match - def run_and_match(command, regexp) - run_command(command).try(:match, regexp) - end - - # Runs the given command - # - # Returns nil if the command was not found - # Returns the output of the command otherwise - # - # see also #run_and_match - def run_command(command) - output, _ = Gitlab::Popen.popen(command) - output - rescue Errno::ENOENT - '' # if the command does not exist, return an empty string - end - - def uid_for(user_name) - run_command(%W(id -u #{user_name})).chomp.to_i - end - - def gid_for(group_name) - begin - Etc.getgrnam(group_name).gid - rescue ArgumentError # no group - "group #{group_name} doesn't exist" - end - end - - def warn_user_is_not_gitlab - unless @warned_user_not_gitlab - gitlab_user = Gitlab.config.gitlab.user - current_user = run_command(%W(whoami)).chomp - unless current_user == gitlab_user - puts " Warning ".color(:black).background(:yellow) - puts " You are running as user #{current_user.color(:magenta)}, we hope you know what you are doing." - puts " Things may work\/fail for the wrong reasons." - puts " For correct results you should run this as user #{gitlab_user.color(:magenta)}." - puts "" - end - @warned_user_not_gitlab = true - end - end - - # Tries to configure git itself - # - # Returns true if all subcommands were successfull (according to their exit code) - # Returns false if any or all subcommands failed. - def auto_fix_git_config(options) - if !@warned_user_not_gitlab - command_success = options.map do |name, value| - system(*%W(#{Gitlab.config.git.bin_path} config --global #{name} #{value})) - end - - command_success.all? - else - false - end - end - - def all_repos - Gitlab.config.repositories.storages.each do |name, path| - IO.popen(%W(find #{path} -mindepth 2 -maxdepth 2 -type d -name *.git)) do |find| - find.each_line do |path| - yield path.chomp - end - end - end - end - - def repository_storage_paths_args - Gitlab.config.repositories.storages.values - end -end diff --git a/lib/tasks/gitlab/task_helpers.rb b/lib/tasks/gitlab/task_helpers.rb new file mode 100644 index 00000000000..e128738b5f8 --- /dev/null +++ b/lib/tasks/gitlab/task_helpers.rb @@ -0,0 +1,190 @@ +require 'rainbow/ext/string' + +module Gitlab + TaskFailedError = Class.new(StandardError) + TaskAbortedByUserError = Class.new(StandardError) + + module TaskHelpers + # Ask if the user wants to continue + # + # Returns "yes" the user chose to continue + # Raises Gitlab::TaskAbortedByUserError if the user chose *not* to continue + def ask_to_continue + answer = prompt("Do you want to continue (yes/no)? ".color(:blue), %w{yes no}) + raise Gitlab::TaskAbortedByUserError unless answer == "yes" + end + + # Check which OS is running + # + # It will primarily use lsb_relase to determine the OS. + # It has fallbacks to Debian, SuSE, OS X and systems running systemd. + def os_name + os_name = run_command(%W(lsb_release -irs)) + os_name ||= if File.readable?('/etc/system-release') + File.read('/etc/system-release') + end + os_name ||= if File.readable?('/etc/debian_version') + debian_version = File.read('/etc/debian_version') + "Debian #{debian_version}" + end + os_name ||= if File.readable?('/etc/SuSE-release') + File.read('/etc/SuSE-release') + end + os_name ||= if os_x_version = run_command(%W(sw_vers -productVersion)) + "Mac OS X #{os_x_version}" + end + os_name ||= if File.readable?('/etc/os-release') + File.read('/etc/os-release').match(/PRETTY_NAME=\"(.+)\"/)[1] + end + os_name.try(:squish!) + end + + # Prompt the user to input something + # + # message - the message to display before input + # choices - array of strings of acceptable answers or nil for any answer + # + # Returns the user's answer + def prompt(message, choices = nil) + begin + print(message) + answer = STDIN.gets.chomp + end while choices.present? && !choices.include?(answer) + answer + end + + # Runs the given command and matches the output against the given pattern + # + # Returns nil if nothing matched + # Returns the MatchData if the pattern matched + # + # see also #run_command + # see also String#match + def run_and_match(command, regexp) + run_command(command).try(:match, regexp) + end + + # Runs the given command + # + # Returns '' if the command was not found + # Returns the output of the command otherwise + # + # see also #run_and_match + def run_command(command) + output, _ = Gitlab::Popen.popen(command) + output + rescue Errno::ENOENT + '' # if the command does not exist, return an empty string + end + + # Runs the given command and raises a Gitlab::TaskFailedError exception if + # the command does not exit with 0 + # + # Returns the output of the command otherwise + def run_command!(command) + output, status = Gitlab::Popen.popen(command) + + raise Gitlab::TaskFailedError unless status.zero? + + output + end + + def uid_for(user_name) + run_command(%W(id -u #{user_name})).chomp.to_i + end + + def gid_for(group_name) + begin + Etc.getgrnam(group_name).gid + rescue ArgumentError # no group + "group #{group_name} doesn't exist" + end + end + + def warn_user_is_not_gitlab + unless @warned_user_not_gitlab + gitlab_user = Gitlab.config.gitlab.user + current_user = run_command(%W(whoami)).chomp + unless current_user == gitlab_user + puts " Warning ".color(:black).background(:yellow) + puts " You are running as user #{current_user.color(:magenta)}, we hope you know what you are doing." + puts " Things may work\/fail for the wrong reasons." + puts " For correct results you should run this as user #{gitlab_user.color(:magenta)}." + puts "" + end + @warned_user_not_gitlab = true + end + end + + # Tries to configure git itself + # + # Returns true if all subcommands were successfull (according to their exit code) + # Returns false if any or all subcommands failed. + def auto_fix_git_config(options) + if !@warned_user_not_gitlab + command_success = options.map do |name, value| + system(*%W(#{Gitlab.config.git.bin_path} config --global #{name} #{value})) + end + + command_success.all? + else + false + end + end + + def all_repos + Gitlab.config.repositories.storages.each do |name, path| + IO.popen(%W(find #{path} -mindepth 2 -maxdepth 2 -type d -name *.git)) do |find| + find.each_line do |path| + yield path.chomp + end + end + end + end + + def repository_storage_paths_args + Gitlab.config.repositories.storages.values + end + + def user_home + Rails.env.test? ? Rails.root.join('tmp/tests') : Gitlab.config.gitlab.user_home + end + + def checkout_or_clone_tag(tag:, repo:, target_dir:) + if Dir.exist?(target_dir) + checkout_tag(tag, target_dir) + else + clone_repo(repo, target_dir) + end + + reset_to_tag(tag, target_dir) + end + + def clone_repo(repo, target_dir) + run_command!(%W[#{Gitlab.config.git.bin_path} clone -- #{repo} #{target_dir}]) + end + + def checkout_tag(tag, target_dir) + run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} fetch --tags --quiet]) + run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} checkout --quiet #{tag}]) + end + + def reset_to_tag(tag_wanted, target_dir) + tag = + begin + # First try to checkout without fetching + # to avoid stalling tests if the Internet is down. + run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} describe -- #{tag_wanted}]) + rescue Gitlab::TaskFailedError + run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} fetch origin]) + run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} describe -- origin/#{tag_wanted}]) + end + + if tag + run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} reset --hard #{tag.strip}]) + else + raise Gitlab::TaskFailedError + end + end + end +end diff --git a/lib/tasks/gitlab/workhorse.rake b/lib/tasks/gitlab/workhorse.rake new file mode 100644 index 00000000000..baea94bf8ca --- /dev/null +++ b/lib/tasks/gitlab/workhorse.rake @@ -0,0 +1,23 @@ +namespace :gitlab do + namespace :workhorse do + desc "GitLab | Install or upgrade gitlab-workhorse" + task :install, [:dir] => :environment do |t, args| + warn_user_is_not_gitlab + unless args.dir.present? + abort %(Please specify the directory where you want to install gitlab-workhorse:\n rake "gitlab:workhorse:install[/home/git/gitlab-workhorse]") + end + + tag = "v#{Gitlab::Workhorse.version}" + repo = 'https://gitlab.com/gitlab-org/gitlab-workhorse.git' + + checkout_or_clone_tag(tag: tag, repo: repo, target_dir: args.dir) + + _, status = Gitlab::Popen.popen(%w[which gmake]) + command = status.zero? ? 'gmake' : 'make' + + Dir.chdir(args.dir) do + run_command!([command]) + end + end + end +end diff --git a/public/404.html b/public/404.html index 92b7f4da0b9..11b29d09a82 100644 --- a/public/404.html +++ b/public/404.html @@ -42,6 +42,8 @@ img { max-width: 40vw; + display: block; + margin: 40px auto; } .container { @@ -51,8 +53,9 @@ </head> <body> + <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" + alt="GitLab Logo" /> <h1> - <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" alt="GitLab Logo" /><br /> 404 </h1> <div class="container"> diff --git a/public/422.html b/public/422.html index f625f8a33b7..9bd7cb4b7c8 100644 --- a/public/422.html +++ b/public/422.html @@ -42,6 +42,8 @@ img { max-width: 40vw; + display: block; + margin: 40px auto; } .container { @@ -51,8 +53,9 @@ </head> <body> + <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" + alt="GitLab Logo" /> <h1> - <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" alt="GitLab Logo" /><br /> 422 </h1> <div class="container"> diff --git a/public/500.html b/public/500.html index d76c66ba92a..f92e8839f8d 100644 --- a/public/500.html +++ b/public/500.html @@ -42,6 +42,8 @@ img { max-width: 40vw; + display: block; + margin: 40px auto; } .container { @@ -51,8 +53,9 @@ </head> <body> + <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" + alt="GitLab Logo" /> <h1> - <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" alt="GitLab Logo" /><br /> 500 </h1> <div class="container"> diff --git a/public/502.html b/public/502.html index 1a3c7efc769..c2be4f130a9 100644 --- a/public/502.html +++ b/public/502.html @@ -42,6 +42,8 @@ img { max-width: 40vw; + display: block; + margin: 40px auto; } .container { @@ -51,8 +53,9 @@ </head> <body> + <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" + alt="GitLab Logo" /> <h1> - <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" alt="GitLab Logo" /><br /> 502 </h1> <div class="container"> diff --git a/public/503.html b/public/503.html index c1c4e3ffdb8..8850ffce362 100644 --- a/public/503.html +++ b/public/503.html @@ -42,6 +42,8 @@ img { max-width: 40vw; + display: block; + margin: 40px auto; } .container { @@ -51,8 +53,9 @@ </head> <body> + <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" + alt="GitLab Logo" /> <h1> - <img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjIxMCIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTVsMzguNjQtMTE4LjkyMWgtNzcuMjhsMzguNjQgMTE4LjkyMXoiIGZpbGw9IiNlMjQzMjkiLz4KICA8cGF0aCBkPSJNMTA1LjA2MTQgMjAzLjY1NDhsLTM4LjY0LTExOC45MjFoLTU0LjE1M2w5Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTIuMjY4NSA4NC43MzQxbC0xMS43NDIgMzYuMTM5Yy0xLjA3MSAzLjI5Ni4xMDIgNi45MDcgMi45MDYgOC45NDRsMTAxLjYyOSA3My44MzgtOTIuNzkzLTExOC45MjF6IiBmaWxsPSIjZmNhMzI2Ii8+CiAgPHBhdGggZD0iTTEyLjI2ODUgODQuNzM0Mmg1NC4xNTNsLTIzLjI3My03MS42MjVjLTEuMTk3LTMuNjg2LTYuNDExLTMuNjg1LTcuNjA4IDBsLTIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+CiAgPHBhdGggZD0iTTEwNS4wNjE0IDIwMy42NTQ4bDM4LjY0LTExOC45MjFoNTQuMTUzbC05Mi43OTMgMTE4LjkyMXoiIGZpbGw9IiNmYzZkMjYiLz4KICA8cGF0aCBkPSJNMTk3Ljg1NDQgODQuNzM0MWwxMS43NDIgMzYuMTM5YzEuMDcxIDMuMjk2LS4xMDIgNi45MDctMi45MDYgOC45NDRsLTEwMS42MjkgNzMuODM4IDkyLjc5My0xMTguOTIxeiIgZmlsbD0iI2ZjYTMyNiIvPgogIDxwYXRoIGQ9Ik0xOTcuODU0NCA4NC43MzQyaC01NC4xNTNsMjMuMjczLTcxLjYyNWMxLjE5Ny0zLjY4NiA2LjQxMS0zLjY4NSA3LjYwOCAwbDIzLjI3MiA3MS42MjV6IiBmaWxsPSIjZTI0MzI5Ii8+Cjwvc3ZnPgo=" alt="GitLab Logo" /><br /> 503 </h1> <div class="container"> diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb index 0d1545040f1..ea2fd90a9b0 100644 --- a/spec/controllers/autocomplete_controller_spec.rb +++ b/spec/controllers/autocomplete_controller_spec.rb @@ -144,6 +144,15 @@ describe AutocompleteController do it { expect(body).to be_kind_of(Array) } it { expect(body.size).to eq 0 } end + + describe 'GET #users with todo filter' do + it 'gives an array of users' do + get :users, todo_filter: true + + expect(response.status).to eq 200 + expect(body).to be_kind_of(Array) + end + end end context 'author of issuable included' do diff --git a/spec/controllers/help_controller_spec.rb b/spec/controllers/help_controller_spec.rb index cffed987f6b..d3489324a9c 100644 --- a/spec/controllers/help_controller_spec.rb +++ b/spec/controllers/help_controller_spec.rb @@ -8,26 +8,32 @@ describe HelpController do end describe 'GET #index' do - context 'when url prefixed without /help/' do - it 'has correct url prefix' do - stub_readme("[API](api/README.md)") + context 'with absolute url' do + it 'keeps the URL absolute' do + stub_readme("[API](/api/README.md)") + get :index - expect(assigns[:help_index]).to eq '[API](/help/api/README.md)' + + expect(assigns[:help_index]).to eq '[API](/api/README.md)' end end - context 'when url prefixed with help' do - it 'will be an absolute path' do - stub_readme("[API](helpful_hints/README.md)") + context 'with relative url' do + it 'prefixes it with /help/' do + stub_readme("[API](api/README.md)") + get :index - expect(assigns[:help_index]).to eq '[API](/help/helpful_hints/README.md)' + + expect(assigns[:help_index]).to eq '[API](/help/api/README.md)' end end context 'when url is an external link' do - it 'will not be changed' do + it 'does not change it' do stub_readme("[external](https://some.external.link)") + get :index + expect(assigns[:help_index]).to eq '[external](https://some.external.link)' end end diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb index f160052a844..c16aafa1470 100644 --- a/spec/features/boards/sidebar_spec.rb +++ b/spec/features/boards/sidebar_spec.rb @@ -304,8 +304,8 @@ describe 'Issue Boards', feature: true, js: true do page.within('.subscription') do click_button 'Subscribe' - - expect(page).to have_content("You're receiving notifications because you're subscribed to this thread.") + wait_for_ajax + expect(page).to have_content("Unsubscribe") end end end diff --git a/spec/features/help_pages_spec.rb b/spec/features/help_pages_spec.rb index 73d03837144..4319d6db0d2 100644 --- a/spec/features/help_pages_spec.rb +++ b/spec/features/help_pages_spec.rb @@ -12,9 +12,9 @@ describe 'Help Pages', feature: true do end describe 'Get the main help page' do - shared_examples_for 'help page' do + shared_examples_for 'help page' do |prefix: ''| it 'prefixes links correctly' do - expect(page).to have_selector('div.documentation-index > ul a[href="/help/api/README.md"]') + expect(page).to have_selector(%(div.documentation-index > ul a[href="#{prefix}/help/api/README.md"])) end end @@ -33,5 +33,14 @@ describe 'Help Pages', feature: true do it_behaves_like 'help page' end + + context 'with a relative installation' do + before do + stub_config_setting(relative_url_root: '/gitlab') + visit help_path + end + + it_behaves_like 'help page', prefix: '/gitlab' + end end end diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb new file mode 100644 index 00000000000..3350a3aeefc --- /dev/null +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -0,0 +1,154 @@ +require 'spec_helper' + +describe "Pipelines", feature: true, js: true do + include GitlabRoutingHelper + + let(:project) { create(:empty_project) } + let(:user) { create(:user) } + + before do + login_as(user) + project.team << [user, :developer] + end + + describe 'GET /:project/pipelines/:id' do + let(:project) { create(:project) } + let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id) } + + before do + @success = create(:ci_build, :success, pipeline: pipeline, stage: 'build', name: 'build') + @failed = create(:ci_build, :failed, pipeline: pipeline, stage: 'test', name: 'test', commands: 'test') + @running = create(:ci_build, :running, pipeline: pipeline, stage: 'deploy', name: 'deploy') + @manual = create(:ci_build, :manual, pipeline: pipeline, stage: 'deploy', name: 'manual build') + @external = create(:generic_commit_status, status: 'success', pipeline: pipeline, name: 'jenkins', stage: 'external') + end + + before { visit namespace_project_pipeline_path(project.namespace, project, pipeline) } + + it 'shows the pipeline graph' do + expect(page).to have_selector('.pipeline-visualization') + expect(page).to have_content('Build') + expect(page).to have_content('Test') + expect(page).to have_content('Deploy') + expect(page).to have_content('Retry failed') + expect(page).to have_content('Cancel running') + end + + it 'shows Pipeline tab pane as active' do + expect(page).to have_css('#js-tab-pipeline.active') + end + + context 'page tabs' do + it 'shows Pipeline and Builds tabs with link' do + expect(page).to have_link('Pipeline') + expect(page).to have_link('Builds') + end + + it 'shows counter in Builds tab' do + expect(page.find('.js-builds-counter').text).to eq(pipeline.statuses.count.to_s) + end + + it 'shows Pipeline tab as active' do + expect(page).to have_css('.js-pipeline-tab-link.active') + end + end + + context 'retrying builds' do + it { expect(page).not_to have_content('retried') } + + context 'when retrying' do + before { click_on 'Retry failed' } + + it { expect(page).not_to have_content('Retry failed') } + end + end + + context 'canceling builds' do + it { expect(page).not_to have_selector('.ci-canceled') } + + context 'when canceling' do + before { click_on 'Cancel running' } + + it { expect(page).not_to have_content('Cancel running') } + end + end + end + + describe 'GET /:project/pipelines/:id/builds' do + let(:project) { create(:project) } + let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id) } + + before do + @success = create(:ci_build, :success, pipeline: pipeline, stage: 'build', name: 'build') + @failed = create(:ci_build, :failed, pipeline: pipeline, stage: 'test', name: 'test', commands: 'test') + @running = create(:ci_build, :running, pipeline: pipeline, stage: 'deploy', name: 'deploy') + @manual = create(:ci_build, :manual, pipeline: pipeline, stage: 'deploy', name: 'manual build') + @external = create(:generic_commit_status, status: 'success', pipeline: pipeline, name: 'jenkins', stage: 'external') + end + + before { visit builds_namespace_project_pipeline_path(project.namespace, project, pipeline)} + + it 'shows a list of builds' do + expect(page).to have_content('Test') + expect(page).to have_content(@success.id) + expect(page).to have_content('Deploy') + expect(page).to have_content(@failed.id) + expect(page).to have_content(@running.id) + expect(page).to have_content(@external.id) + expect(page).to have_content('Retry failed') + expect(page).to have_content('Cancel running') + expect(page).to have_link('Play') + end + + it 'shows Builds tab pane as active' do + expect(page).to have_css('#js-tab-builds.active') + end + + context 'page tabs' do + it 'shows Pipeline and Builds tabs with link' do + expect(page).to have_link('Pipeline') + expect(page).to have_link('Builds') + end + + it 'shows counter in Builds tab' do + expect(page.find('.js-builds-counter').text).to eq(pipeline.statuses.count.to_s) + end + + it 'shows Builds tab as active' do + expect(page).to have_css('li.js-builds-tab-link.active') + end + end + + context 'retrying builds' do + it { expect(page).not_to have_content('retried') } + + context 'when retrying' do + before { click_on 'Retry failed' } + + it { expect(page).not_to have_content('Retry failed') } + it { expect(page).to have_selector('.retried') } + end + end + + context 'canceling builds' do + it { expect(page).not_to have_selector('.ci-canceled') } + + context 'when canceling' do + before { click_on 'Cancel running' } + + it { expect(page).not_to have_content('Cancel running') } + it { expect(page).to have_selector('.ci-canceled') } + end + end + + context 'playing manual build' do + before do + within '.pipeline-holder' do + click_link('Play') + end + end + + it { expect(@manual.reload).to be_pending } + end + end +end diff --git a/spec/features/projects/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb index 10e5466fc85..f3731698a18 100644 --- a/spec/features/projects/pipelines_spec.rb +++ b/spec/features/projects/pipelines/pipelines_spec.rb @@ -152,65 +152,6 @@ describe "Pipelines" do end end - describe 'GET /:project/pipelines/:id' do - let(:project) { create(:project) } - let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id) } - - before do - @success = create(:ci_build, :success, pipeline: pipeline, stage: 'build', name: 'build') - @failed = create(:ci_build, :failed, pipeline: pipeline, stage: 'test', name: 'test', commands: 'test') - @running = create(:ci_build, :running, pipeline: pipeline, stage: 'deploy', name: 'deploy') - @manual = create(:ci_build, :manual, pipeline: pipeline, stage: 'deploy', name: 'manual build') - @external = create(:generic_commit_status, status: 'success', pipeline: pipeline, name: 'jenkins', stage: 'external') - end - - before { visit namespace_project_pipeline_path(project.namespace, project, pipeline) } - - it 'shows a list of builds' do - expect(page).to have_content('Test') - expect(page).to have_content(@success.id) - expect(page).to have_content('Deploy') - expect(page).to have_content(@failed.id) - expect(page).to have_content(@running.id) - expect(page).to have_content(@external.id) - expect(page).to have_content('Retry failed') - expect(page).to have_content('Cancel running') - expect(page).to have_link('Play') - end - - context 'retrying builds' do - it { expect(page).not_to have_content('retried') } - - context 'when retrying' do - before { click_on 'Retry failed' } - - it { expect(page).not_to have_content('Retry failed') } - it { expect(page).to have_selector('.retried') } - end - end - - context 'canceling builds' do - it { expect(page).not_to have_selector('.ci-canceled') } - - context 'when canceling' do - before { click_on 'Cancel running' } - - it { expect(page).not_to have_content('Cancel running') } - it { expect(page).to have_selector('.ci-canceled') } - end - end - - context 'playing manual build' do - before do - within '.pipeline-holder' do - click_link('Play') - end - end - - it { expect(@manual.reload).to be_pending } - end - end - describe 'POST /:project/pipelines' do let(:project) { create(:project) } diff --git a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb index 7afd83b7250..fff8b9f3447 100644 --- a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb +++ b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb @@ -20,7 +20,7 @@ feature 'Projects > Wiki > User creates wiki page', feature: true do click_button 'Create page' expect(page).to have_content('Home') - expect(page).to have_content("last edited by #{user.name}") + expect(page).to have_content("Last edited by #{user.name}") expect(page).to have_content('My awesome wiki!') end end @@ -41,7 +41,7 @@ feature 'Projects > Wiki > User creates wiki page', feature: true do click_button 'Create page' expect(page).to have_content('Foo') - expect(page).to have_content("last edited by #{user.name}") + expect(page).to have_content("Last edited by #{user.name}") expect(page).to have_content('My awesome wiki!') end @@ -55,7 +55,7 @@ feature 'Projects > Wiki > User creates wiki page', feature: true do click_button 'Create page' expect(page).to have_content('Spaces in the name') - expect(page).to have_content("last edited by #{user.name}") + expect(page).to have_content("Last edited by #{user.name}") expect(page).to have_content('My awesome wiki!') end @@ -69,7 +69,7 @@ feature 'Projects > Wiki > User creates wiki page', feature: true do click_button 'Create page' expect(page).to have_content('Hyphens in the name') - expect(page).to have_content("last edited by #{user.name}") + expect(page).to have_content("Last edited by #{user.name}") expect(page).to have_content('My awesome wiki!') end end @@ -85,7 +85,7 @@ feature 'Projects > Wiki > User creates wiki page', feature: true do click_button 'Create page' expect(page).to have_content('Home') - expect(page).to have_content("last edited by #{user.name}") + expect(page).to have_content("Last edited by #{user.name}") expect(page).to have_content('My awesome wiki!') end end @@ -105,7 +105,7 @@ feature 'Projects > Wiki > User creates wiki page', feature: true do click_button 'Create page' expect(page).to have_content('Foo') - expect(page).to have_content("last edited by #{user.name}") + expect(page).to have_content("Last edited by #{user.name}") expect(page).to have_content('My awesome wiki!') end end diff --git a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb index ef82d2375dd..f842d14fa96 100644 --- a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb +++ b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb @@ -22,7 +22,7 @@ feature 'Projects > Wiki > User updates wiki page', feature: true do click_button 'Save changes' expect(page).to have_content('Home') - expect(page).to have_content("last edited by #{user.name}") + expect(page).to have_content("Last edited by #{user.name}") expect(page).to have_content('My awesome wiki!') end end @@ -37,7 +37,7 @@ feature 'Projects > Wiki > User updates wiki page', feature: true do click_button 'Save changes' expect(page).to have_content('Home') - expect(page).to have_content("last edited by #{user.name}") + expect(page).to have_content("Last edited by #{user.name}") expect(page).to have_content('My awesome wiki!') end end diff --git a/spec/features/security/group/internal_access_spec.rb b/spec/features/security/group/internal_access_spec.rb index 35fcef7a712..87cce32d6c6 100644 --- a/spec/features/security/group/internal_access_spec.rb +++ b/spec/features/security/group/internal_access_spec.rb @@ -3,25 +3,12 @@ require 'rails_helper' describe 'Internal Group access', feature: true do include AccessMatchers - let(:group) { create(:group, :internal) } + let(:group) { create(:group, :internal) } let(:project) { create(:project, :internal, group: group) } - - let(:owner) { create(:user) } - let(:master) { create(:user) } - let(:developer) { create(:user) } - let(:reporter) { create(:user) } - let(:guest) { create(:user) } - - let(:project_guest) { create(:user) } - - before do - group.add_owner(owner) - group.add_master(master) - group.add_developer(developer) - group.add_reporter(reporter) - group.add_guest(guest) - - project.team << [project_guest, :guest] + let(:project_guest) do + create(:user) do |user| + project.add_guest(user) + end end describe "Group should be internal" do @@ -34,75 +21,75 @@ describe 'Internal Group access', feature: true do describe 'GET /groups/:path' do subject { group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe 'GET /groups/:path/issues' do subject { issues_group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe 'GET /groups/:path/merge_requests' do subject { merge_requests_group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe 'GET /groups/:path/group_members' do subject { group_group_members_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe 'GET /groups/:path/edit' do subject { edit_group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_denied_for master } - it { is_expected.to be_denied_for developer } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for project_guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :visitor } - it { is_expected.to be_denied_for :external } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_denied_for(:master).of(group) } + it { is_expected.to be_denied_for(:developer).of(group) } + it { is_expected.to be_denied_for(:reporter).of(group) } + it { is_expected.to be_denied_for(:guest).of(group) } + it { is_expected.to be_denied_for(project_guest) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:visitor) } + it { is_expected.to be_denied_for(:external) } end end diff --git a/spec/features/security/group/private_access_spec.rb b/spec/features/security/group/private_access_spec.rb index 75a93342628..1d6b3e77c22 100644 --- a/spec/features/security/group/private_access_spec.rb +++ b/spec/features/security/group/private_access_spec.rb @@ -3,25 +3,12 @@ require 'rails_helper' describe 'Private Group access', feature: true do include AccessMatchers - let(:group) { create(:group, :private) } + let(:group) { create(:group, :private) } let(:project) { create(:project, :private, group: group) } - - let(:owner) { create(:user) } - let(:master) { create(:user) } - let(:developer) { create(:user) } - let(:reporter) { create(:user) } - let(:guest) { create(:user) } - - let(:project_guest) { create(:user) } - - before do - group.add_owner(owner) - group.add_master(master) - group.add_developer(developer) - group.add_reporter(reporter) - group.add_guest(guest) - - project.team << [project_guest, :guest] + let(:project_guest) do + create(:user) do |user| + project.add_guest(user) + end end describe "Group should be private" do @@ -34,75 +21,75 @@ describe 'Private Group access', feature: true do describe 'GET /groups/:path' do subject { group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe 'GET /groups/:path/issues' do subject { issues_group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe 'GET /groups/:path/merge_requests' do subject { merge_requests_group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe 'GET /groups/:path/group_members' do subject { group_group_members_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe 'GET /groups/:path/edit' do subject { edit_group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_denied_for master } - it { is_expected.to be_denied_for developer } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for project_guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :visitor } - it { is_expected.to be_denied_for :external } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_denied_for(:master).of(group) } + it { is_expected.to be_denied_for(:developer).of(group) } + it { is_expected.to be_denied_for(:reporter).of(group) } + it { is_expected.to be_denied_for(:guest).of(group) } + it { is_expected.to be_denied_for(project_guest) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:visitor) } + it { is_expected.to be_denied_for(:external) } end end diff --git a/spec/features/security/group/public_access_spec.rb b/spec/features/security/group/public_access_spec.rb index 6c5ee93970b..d7d76177269 100644 --- a/spec/features/security/group/public_access_spec.rb +++ b/spec/features/security/group/public_access_spec.rb @@ -3,25 +3,12 @@ require 'rails_helper' describe 'Public Group access', feature: true do include AccessMatchers - let(:group) { create(:group, :public) } + let(:group) { create(:group, :public) } let(:project) { create(:project, :public, group: group) } - - let(:owner) { create(:user) } - let(:master) { create(:user) } - let(:developer) { create(:user) } - let(:reporter) { create(:user) } - let(:guest) { create(:user) } - - let(:project_guest) { create(:user) } - - before do - group.add_owner(owner) - group.add_master(master) - group.add_developer(developer) - group.add_reporter(reporter) - group.add_guest(guest) - - project.team << [project_guest, :guest] + let(:project_guest) do + create(:user) do |user| + project.add_guest(user) + end end describe "Group should be public" do @@ -34,75 +21,75 @@ describe 'Public Group access', feature: true do describe 'GET /groups/:path' do subject { group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end describe 'GET /groups/:path/issues' do subject { issues_group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end describe 'GET /groups/:path/merge_requests' do subject { merge_requests_group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end describe 'GET /groups/:path/group_members' do subject { group_group_members_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for project_guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:master).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end describe 'GET /groups/:path/edit' do subject { edit_group_path(group) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_denied_for master } - it { is_expected.to be_denied_for developer } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for project_guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :visitor } - it { is_expected.to be_denied_for :external } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_denied_for(:master).of(group) } + it { is_expected.to be_denied_for(:developer).of(group) } + it { is_expected.to be_denied_for(:reporter).of(group) } + it { is_expected.to be_denied_for(:guest).of(group) } + it { is_expected.to be_denied_for(project_guest) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:visitor) } + it { is_expected.to be_denied_for(:external) } end end diff --git a/spec/features/security/project/snippet/internal_access_spec.rb b/spec/features/security/project/snippet/internal_access_spec.rb index 49deacc5c74..2659b3ee3ec 100644 --- a/spec/features/security/project/snippet/internal_access_spec.rb +++ b/spec/features/security/project/snippet/internal_access_spec.rb @@ -5,76 +5,64 @@ describe "Internal Project Snippets Access", feature: true do let(:project) { create(:empty_project, :internal) } - let(:owner) { project.owner } - let(:master) { create(:user) } - let(:developer) { create(:user) } - let(:reporter) { create(:user) } - let(:guest) { create(:user) } - let(:internal_snippet) { create(:project_snippet, :internal, project: project, author: owner) } - let(:private_snippet) { create(:project_snippet, :private, project: project, author: owner) } - - before do - project.team << [master, :master] - project.team << [developer, :developer] - project.team << [reporter, :reporter] - project.team << [guest, :guest] - end + let(:internal_snippet) { create(:project_snippet, :internal, project: project, author: project.owner) } + let(:private_snippet) { create(:project_snippet, :private, project: project, author: project.owner) } describe "GET /:project_path/snippets" do subject { namespace_project_snippets_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/snippets/new" do subject { new_namespace_project_snippet_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/snippets/:id" do context "for an internal snippet" do subject { namespace_project_snippet_path(project.namespace, project, internal_snippet) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end context "for a private snippet" do subject { namespace_project_snippet_path(project.namespace, project, private_snippet) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end end @@ -82,29 +70,29 @@ describe "Internal Project Snippets Access", feature: true do context "for an internal snippet" do subject { raw_namespace_project_snippet_path(project.namespace, project, internal_snippet) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end context "for a private snippet" do subject { raw_namespace_project_snippet_path(project.namespace, project, private_snippet) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end end end diff --git a/spec/features/security/project/snippet/private_access_spec.rb b/spec/features/security/project/snippet/private_access_spec.rb index a1bfc076d99..6eb9f163bd5 100644 --- a/spec/features/security/project/snippet/private_access_spec.rb +++ b/spec/features/security/project/snippet/private_access_spec.rb @@ -5,73 +5,61 @@ describe "Private Project Snippets Access", feature: true do let(:project) { create(:empty_project, :private) } - let(:owner) { project.owner } - let(:master) { create(:user) } - let(:developer) { create(:user) } - let(:reporter) { create(:user) } - let(:guest) { create(:user) } - let(:private_snippet) { create(:project_snippet, :private, project: project, author: owner) } - - before do - project.team << [master, :master] - project.team << [developer, :developer] - project.team << [reporter, :reporter] - project.team << [guest, :guest] - end + let(:private_snippet) { create(:project_snippet, :private, project: project, author: project.owner) } describe "GET /:project_path/snippets" do subject { namespace_project_snippets_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/snippets/new" do subject { new_namespace_project_snippet_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/snippets/:id for a private snippet" do subject { namespace_project_snippet_path(project.namespace, project, private_snippet) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/snippets/:id/raw for a private snippet" do subject { raw_namespace_project_snippet_path(project.namespace, project, private_snippet) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end end diff --git a/spec/features/security/project/snippet/public_access_spec.rb b/spec/features/security/project/snippet/public_access_spec.rb index 30bcd87ef04..f3329d0bc96 100644 --- a/spec/features/security/project/snippet/public_access_spec.rb +++ b/spec/features/security/project/snippet/public_access_spec.rb @@ -5,91 +5,79 @@ describe "Public Project Snippets Access", feature: true do let(:project) { create(:empty_project, :public) } - let(:owner) { project.owner } - let(:master) { create(:user) } - let(:developer) { create(:user) } - let(:reporter) { create(:user) } - let(:guest) { create(:user) } - let(:public_snippet) { create(:project_snippet, :public, project: project, author: owner) } - let(:internal_snippet) { create(:project_snippet, :internal, project: project, author: owner) } - let(:private_snippet) { create(:project_snippet, :private, project: project, author: owner) } - - before do - project.team << [master, :master] - project.team << [developer, :developer] - project.team << [reporter, :reporter] - project.team << [guest, :guest] - end + let(:public_snippet) { create(:project_snippet, :public, project: project, author: project.owner) } + let(:internal_snippet) { create(:project_snippet, :internal, project: project, author: project.owner) } + let(:private_snippet) { create(:project_snippet, :private, project: project, author: project.owner) } describe "GET /:project_path/snippets" do subject { namespace_project_snippets_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end describe "GET /:project_path/snippets/new" do subject { new_namespace_project_snippet_path(project.namespace, project) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_denied_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end describe "GET /:project_path/snippets/:id" do context "for a public snippet" do subject { namespace_project_snippet_path(project.namespace, project, public_snippet) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end context "for an internal snippet" do subject { namespace_project_snippet_path(project.namespace, project, internal_snippet) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end context "for a private snippet" do subject { namespace_project_snippet_path(project.namespace, project, private_snippet) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end end @@ -97,43 +85,43 @@ describe "Public Project Snippets Access", feature: true do context "for a public snippet" do subject { raw_namespace_project_snippet_path(project.namespace, project, public_snippet) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_allowed_for :external } - it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_allowed_for(:external) } + it { is_expected.to be_allowed_for(:visitor) } end context "for an internal snippet" do subject { raw_namespace_project_snippet_path(project.namespace, project, internal_snippet) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_allowed_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_allowed_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end context "for a private snippet" do subject { raw_namespace_project_snippet_path(project.namespace, project, private_snippet) } - it { is_expected.to be_allowed_for :admin } - it { is_expected.to be_allowed_for owner } - it { is_expected.to be_allowed_for master } - it { is_expected.to be_allowed_for developer } - it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(project) } + it { is_expected.to be_allowed_for(:master).of(project) } + it { is_expected.to be_allowed_for(:developer).of(project) } + it { is_expected.to be_allowed_for(:reporter).of(project) } + it { is_expected.to be_allowed_for(:guest).of(project) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } end end end diff --git a/spec/finders/labels_finder_spec.rb b/spec/finders/labels_finder_spec.rb index 9085cc8debf..1724cdba830 100644 --- a/spec/finders/labels_finder_spec.rb +++ b/spec/finders/labels_finder_spec.rb @@ -17,7 +17,7 @@ describe LabelsFinder do let!(:project_label_4) { create(:label, project: project_4, title: 'Label 4') } let!(:project_label_5) { create(:label, project: project_5, title: 'Label 5') } - let!(:group_label_1) { create(:group_label, group: group_1, title: 'Label 1') } + let!(:group_label_1) { create(:group_label, group: group_1, title: 'Label 1 (group)') } let!(:group_label_2) { create(:group_label, group: group_1, title: 'Group Label 2') } let!(:group_label_3) { create(:group_label, group: group_2, title: 'Group Label 3') } diff --git a/spec/javascripts/application_spec.js b/spec/javascripts/application_spec.js deleted file mode 100644 index 7e38abc608e..00000000000 --- a/spec/javascripts/application_spec.js +++ /dev/null @@ -1,37 +0,0 @@ -/* eslint-disable space-before-function-paren, one-var, no-var, one-var-declaration-per-line, no-return-assign, padded-blocks, max-len */ - -/*= require lib/utils/common_utils */ - -(function() { - describe('Application', function() { - return describe('disable buttons', function() { - fixture.preload('application.html'); - beforeEach(function() { - return fixture.load('application.html'); - }); - it('should prevent default action for disabled buttons', function() { - var $button, isClicked; - gl.utils.preventDisabledButtons(); - isClicked = false; - $button = $('#test-button'); - expect($button).toExist(); - $button.click(function() { - return isClicked = true; - }); - $button.trigger('click'); - return expect(isClicked).toBe(false); - }); - - it('should be on the same page if a disabled link clicked', function() { - var locationBeforeLinkClick, $link; - locationBeforeLinkClick = window.location.href; - gl.utils.preventDisabledButtons(); - $link = $('#test-link'); - expect($link).toExist(); - $link.click(); - return expect(window.location.href).toBe(locationBeforeLinkClick); - }); - }); - }); - -}).call(this); diff --git a/spec/javascripts/bootstrap_linked_tabs_spec.js.es6 b/spec/javascripts/bootstrap_linked_tabs_spec.js.es6 new file mode 100644 index 00000000000..9aa3c50611d --- /dev/null +++ b/spec/javascripts/bootstrap_linked_tabs_spec.js.es6 @@ -0,0 +1,55 @@ +//= require lib/utils/bootstrap_linked_tabs + +(() => { + describe('Linked Tabs', () => { + fixture.preload('linked_tabs'); + + beforeEach(() => { + fixture.load('linked_tabs'); + }); + + describe('when is initialized', () => { + it('should activate the tab correspondent to the given action', () => { + const linkedTabs = new window.gl.LinkedTabs({ // eslint-disable-line + action: 'tab1', + defaultAction: 'tab1', + parentEl: '.linked-tabs', + }); + + expect(document.querySelector('#tab1').classList).toContain('active'); + }); + + it('should active the default tab action when the action is show', () => { + const linkedTabs = new window.gl.LinkedTabs({ // eslint-disable-line + action: 'show', + defaultAction: 'tab1', + parentEl: '.linked-tabs', + }); + + expect(document.querySelector('#tab1').classList).toContain('active'); + }); + }); + + describe('on click', () => { + it('should change the url according to the clicked tab', () => { + const historySpy = spyOn(history, 'replaceState').and.callFake(() => {}); + + const linkedTabs = new window.gl.LinkedTabs({ // eslint-disable-line + action: 'show', + defaultAction: 'tab1', + parentEl: '.linked-tabs', + }); + + const secondTab = document.querySelector('.linked-tabs li:nth-child(2) a'); + const newState = secondTab.getAttribute('href') + linkedTabs.currentLocation.search + linkedTabs.currentLocation.hash; + + secondTab.click(); + + expect(historySpy).toHaveBeenCalledWith({ + turbolinks: true, + url: newState, + }, document.title, newState); + }); + }); + }); +})(); diff --git a/spec/javascripts/environments/environment_item_spec.js.es6 b/spec/javascripts/environments/environment_item_spec.js.es6 index 14e90a9dd1b..5d7c6b2411d 100644 --- a/spec/javascripts/environments/environment_item_spec.js.es6 +++ b/spec/javascripts/environments/environment_item_spec.js.es6 @@ -1,4 +1,5 @@ //= require vue +//= require timeago //= require environments/components/environment_item describe('Environment item', () => { @@ -109,6 +110,8 @@ describe('Environment item', () => { name: 'deploy', build_path: '/root/ci-folders/builds/1279', retry_path: '/root/ci-folders/builds/1279/retry', + created_at: '2016-11-29T18:11:58.430Z', + updated_at: '2016-11-29T18:11:58.430Z', }, manual_actions: [ { @@ -149,6 +152,17 @@ describe('Environment item', () => { ).toContain('#'); }); + it('should render last deployment date', () => { + const timeagoInstance = new timeago(); // eslint-disable-line + const formatedDate = timeagoInstance.format( + environment.last_deployment.deployable.created_at, + ); + + expect( + component.$el.querySelector('.environment-created-date-timeago').textContent, + ).toContain(formatedDate); + }); + describe('With user information', () => { it('should render user avatar with link to profile', () => { expect( diff --git a/spec/javascripts/fixtures/application.html.haml b/spec/javascripts/fixtures/application.html.haml deleted file mode 100644 index 3fc6114407d..00000000000 --- a/spec/javascripts/fixtures/application.html.haml +++ /dev/null @@ -1,2 +0,0 @@ -%a#test-link.btn.disabled{:href => "/foo"} Test link -%button#test-button.btn.disabled Test Button diff --git a/spec/javascripts/fixtures/comments.html.haml b/spec/javascripts/fixtures/comments.html.haml deleted file mode 100644 index cc1f8f15c21..00000000000 --- a/spec/javascripts/fixtures/comments.html.haml +++ /dev/null @@ -1,21 +0,0 @@ -.flash-container.timeline-content -.timeline-icon.hidden-xs.hidden-sm - %a.author_link - %img -.timeline-content.timeline-content-form - %form.new-note.js-quick-submit.common-note-form.gfm-form.js-main-target-form - .md-area - .md-header - .md-write-holder - .zen-backdrop.div-dropzone-wrapper - .div-dropzone-wrapper - .div-dropzone.dz-clickable - %textarea.note-textarea.js-note-text.js-gfm-input.js-autosize.markdown-area - .note-form-actions.clearfix - %input.btn.btn-nr.btn-create.append-right-10.comment-btn.js-comment-button{ type: 'submit' } - %a.btn.btn-nr.btn-reopen.btn-comment.js-note-target-reopen - Reopen issue - %a.btn.btn-nr.btn-close.btn-comment.js-note-target-close - Close issue - %a.btn.btn-cancel.js-note-discard - Discard draft
\ No newline at end of file diff --git a/spec/javascripts/fixtures/issuable.html.haml b/spec/javascripts/fixtures/issuable.html.haml deleted file mode 100644 index 42ab4aa68b1..00000000000 --- a/spec/javascripts/fixtures/issuable.html.haml +++ /dev/null @@ -1,2 +0,0 @@ -%form.js-main-target-form - %textarea#note_note diff --git a/spec/javascripts/fixtures/issue_note.html.haml b/spec/javascripts/fixtures/issue_note.html.haml deleted file mode 100644 index 0aecc7334fd..00000000000 --- a/spec/javascripts/fixtures/issue_note.html.haml +++ /dev/null @@ -1,12 +0,0 @@ -%ul - %li.note - .js-task-list-container - .note-text - %ul.task-list - %li.task-list-item - %input.task-list-item-checkbox{type: 'checkbox'} - Task List Item - .note-edit-form - %form - %textarea.js-task-list-field - \- [ ] Task List Item diff --git a/spec/javascripts/fixtures/issues.rb b/spec/javascripts/fixtures/issues.rb index c10784fe5ae..06f708f9e15 100644 --- a/spec/javascripts/fixtures/issues.rb +++ b/spec/javascripts/fixtures/issues.rb @@ -26,8 +26,13 @@ describe Projects::IssuesController, '(JavaScript fixtures)', type: :controller end it 'issues/issue-with-task-list.html.raw' do |example| + issue = create(:issue, project: project, description: '- [ ] Task List Item') + render_issue(example.description, issue) + end + + it 'issues/issue_with_comment.html.raw' do |example| issue = create(:issue, project: project) - issue.update(description: '- [ ] Task List Item') + create(:note, project: project, noteable: issue, note: '- [ ] Task List Item').save render_issue(example.description, issue) end diff --git a/spec/javascripts/fixtures/linked_tabs.html.haml b/spec/javascripts/fixtures/linked_tabs.html.haml new file mode 100644 index 00000000000..93c0cf97ff0 --- /dev/null +++ b/spec/javascripts/fixtures/linked_tabs.html.haml @@ -0,0 +1,13 @@ +%ul.nav.nav-tabs.linked-tabs + %li + %a{ href: 'foo/bar/1', data: { target: 'div#tab1', action: 'tab1', toggle: 'tab' } } + Tab 1 + %li + %a{ href: 'foo/bar/1/context', data: { target: 'div#tab2', action: 'tab2', toggle: 'tab' } } + Tab 2 + +.tab-content + #tab1.tab-pane + Tab 1 Content + #tab2.tab-pane + Tab 2 Content diff --git a/spec/javascripts/fixtures/right_sidebar.html.haml b/spec/javascripts/fixtures/right_sidebar.html.haml deleted file mode 100644 index d259b58f235..00000000000 --- a/spec/javascripts/fixtures/right_sidebar.html.haml +++ /dev/null @@ -1,17 +0,0 @@ -%div - %div.page-gutter.page-with-sidebar - - %aside.right-sidebar - %div.block.issuable-sidebar-header - %a.gutter-toggle.pull-right.js-sidebar-toggle - %i.fa.fa-angle-double-left - %button.btn.btn-default.issuable-header-btn.pull-right.js-issuable-todo{ type: "button", data: { todo_text: "Add todo", mark_text: "Mark done", issuable_id: "1", issuable_type: "issue", url: "/todos" }} - %span.js-issuable-todo-text - Add todo - %i.fa.fa-spin.fa-spinner.js-issuable-todo-loading.hidden - - %form.issuable-context-form - %div.block.labels - %div.sidebar-collapsed-icon - %i.fa.fa-tags - %span 1 diff --git a/spec/javascripts/merge_request_tabs_spec.js b/spec/javascripts/merge_request_tabs_spec.js index 971222c44e1..65e4177ecfe 100644 --- a/spec/javascripts/merge_request_tabs_spec.js +++ b/spec/javascripts/merge_request_tabs_spec.js @@ -1,108 +1,111 @@ -/* eslint-disable space-before-function-paren, no-var, comma-dangle, dot-notation, quotes, no-undef, no-return-assign, no-underscore-dangle, camelcase, padded-blocks, max-len */ +/* eslint-disable no-var, comma-dangle, object-shorthand */ /*= require merge_request_tabs */ //= require breakpoints -(function() { - describe('MergeRequestTabs', function() { - var stubLocation; - stubLocation = function(stubs) { - var defaults; - defaults = { +(function () { + describe('MergeRequestTabs', function () { + var stubLocation = {}; + var setLocation = function (stubs) { + var defaults = { pathname: '', search: '', hash: '' }; - return $.extend(defaults, stubs); + $.extend(stubLocation, defaults, stubs || {}); }; fixture.preload('merge_request_tabs.html'); - beforeEach(function() { - this["class"] = new MergeRequestTabs(); - return this.spies = { - ajax: spyOn($, 'ajax').and.callFake(function() {}), - history: spyOn(history, 'replaceState').and.callFake(function() {}) + + beforeEach(function () { + this.class = new gl.MergeRequestTabs({ stubLocation: stubLocation }); + setLocation(); + + this.spies = { + ajax: spyOn($, 'ajax').and.callFake(function () {}), + history: spyOn(window.history, 'replaceState').and.callFake(function () {}) }; }); - describe('#activateTab', function() { - beforeEach(function() { + + describe('#activateTab', function () { + beforeEach(function () { fixture.load('merge_request_tabs.html'); - return this.subject = this["class"].activateTab; + this.subject = this.class.activateTab; }); - it('shows the first tab when action is show', function() { + it('shows the first tab when action is show', function () { this.subject('show'); - return expect($('#notes')).toHaveClass('active'); + expect($('#notes')).toHaveClass('active'); }); - it('shows the notes tab when action is notes', function() { + it('shows the notes tab when action is notes', function () { this.subject('notes'); - return expect($('#notes')).toHaveClass('active'); + expect($('#notes')).toHaveClass('active'); }); - it('shows the commits tab when action is commits', function() { + it('shows the commits tab when action is commits', function () { this.subject('commits'); - return expect($('#commits')).toHaveClass('active'); + expect($('#commits')).toHaveClass('active'); }); - return it('shows the diffs tab when action is diffs', function() { + it('shows the diffs tab when action is diffs', function () { this.subject('diffs'); - return expect($('#diffs')).toHaveClass('active'); + expect($('#diffs')).toHaveClass('active'); }); }); - return describe('#setCurrentAction', function() { - beforeEach(function() { - return this.subject = this["class"].setCurrentAction; + + describe('#setCurrentAction', function () { + beforeEach(function () { + this.subject = this.class.setCurrentAction; }); - it('changes from commits', function() { - this["class"]._location = stubLocation({ + it('changes from commits', function () { + setLocation({ pathname: '/foo/bar/merge_requests/1/commits' }); expect(this.subject('notes')).toBe('/foo/bar/merge_requests/1'); - return expect(this.subject('diffs')).toBe('/foo/bar/merge_requests/1/diffs'); + expect(this.subject('diffs')).toBe('/foo/bar/merge_requests/1/diffs'); }); - it('changes from diffs', function() { - this["class"]._location = stubLocation({ + it('changes from diffs', function () { + setLocation({ pathname: '/foo/bar/merge_requests/1/diffs' }); expect(this.subject('notes')).toBe('/foo/bar/merge_requests/1'); - return expect(this.subject('commits')).toBe('/foo/bar/merge_requests/1/commits'); + expect(this.subject('commits')).toBe('/foo/bar/merge_requests/1/commits'); }); - it('changes from diffs.html', function() { - this["class"]._location = stubLocation({ + it('changes from diffs.html', function () { + setLocation({ pathname: '/foo/bar/merge_requests/1/diffs.html' }); expect(this.subject('notes')).toBe('/foo/bar/merge_requests/1'); - return expect(this.subject('commits')).toBe('/foo/bar/merge_requests/1/commits'); + expect(this.subject('commits')).toBe('/foo/bar/merge_requests/1/commits'); }); - it('changes from notes', function() { - this["class"]._location = stubLocation({ + it('changes from notes', function () { + setLocation({ pathname: '/foo/bar/merge_requests/1' }); expect(this.subject('diffs')).toBe('/foo/bar/merge_requests/1/diffs'); - return expect(this.subject('commits')).toBe('/foo/bar/merge_requests/1/commits'); + expect(this.subject('commits')).toBe('/foo/bar/merge_requests/1/commits'); }); - it('includes search parameters and hash string', function() { - this["class"]._location = stubLocation({ + it('includes search parameters and hash string', function () { + setLocation({ pathname: '/foo/bar/merge_requests/1/diffs', search: '?view=parallel', hash: '#L15-35' }); - return expect(this.subject('show')).toBe('/foo/bar/merge_requests/1?view=parallel#L15-35'); + expect(this.subject('show')).toBe('/foo/bar/merge_requests/1?view=parallel#L15-35'); }); - it('replaces the current history state', function() { - var new_state; - this["class"]._location = stubLocation({ + it('replaces the current history state', function () { + var newState; + setLocation({ pathname: '/foo/bar/merge_requests/1' }); - new_state = this.subject('commits'); - return expect(this.spies.history).toHaveBeenCalledWith({ + newState = this.subject('commits'); + expect(this.spies.history).toHaveBeenCalledWith({ turbolinks: true, - url: new_state - }, document.title, new_state); + url: newState + }, document.title, newState); }); - return it('treats "show" like "notes"', function() { - this["class"]._location = stubLocation({ + it('treats "show" like "notes"', function () { + setLocation({ pathname: '/foo/bar/merge_requests/1/commits' }); - return expect(this.subject('show')).toBe('/foo/bar/merge_requests/1'); + expect(this.subject('show')).toBe('/foo/bar/merge_requests/1'); }); }); }); - }).call(this); diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js index 51f2ae8bcbd..2db182d702b 100644 --- a/spec/javascripts/notes_spec.js +++ b/spec/javascripts/notes_spec.js @@ -6,17 +6,21 @@ (function() { window.gon || (window.gon = {}); - - window.disableButtonIfEmptyField = function() { - return null; - }; + window.gl = window.gl || {}; + gl.utils = gl.utils || {}; describe('Notes', function() { - describe('task lists', function() { - fixture.preload('issue_note.html'); + var commentsTemplate = 'issues/issue_with_comment.raw'; + fixture.preload(commentsTemplate); + beforeEach(function () { + fixture.load(commentsTemplate); + gl.utils.disableButtonIfEmptyField = _.noop; + window.project_uploads_path = 'http://test.host/uploads'; + }); + + describe('task lists', function() { beforeEach(function() { - fixture.load('issue_note.html'); $('form').on('submit', function(e) { e.preventDefault(); }); @@ -41,12 +45,9 @@ }); describe('comments', function() { - var commentsTemplate = 'comments.html'; var textarea = '.js-note-text'; - fixture.preload(commentsTemplate); beforeEach(function() { - fixture.load(commentsTemplate); this.notes = new Notes(); this.autoSizeSpy = spyOnEvent($(textarea), 'autosize:update'); diff --git a/spec/javascripts/right_sidebar_spec.js b/spec/javascripts/right_sidebar_spec.js index 83ebbd63f3a..0a9bc546144 100644 --- a/spec/javascripts/right_sidebar_spec.js +++ b/spec/javascripts/right_sidebar_spec.js @@ -34,9 +34,10 @@ }; describe('RightSidebar', function() { - fixture.preload('right_sidebar.html'); + var fixtureName = 'issues/open-issue.html.raw'; + fixture.preload(fixtureName); beforeEach(function() { - fixture.load('right_sidebar.html'); + fixture.load(fixtureName); this.sidebar = new Sidebar; $aside = $('.right-sidebar'); $page = $('.page-with-sidebar'); @@ -44,15 +45,12 @@ $toggle = $aside.find('.js-sidebar-toggle'); return $labelsIcon = $aside.find('.sidebar-collapsed-icon'); }); - it('should expand the sidebar when arrow is clicked', function() { + it('should expand/collapse the sidebar when arrow is clicked', function() { + assertSidebarState('expanded'); $toggle.click(); - return assertSidebarState('expanded'); - }); - it('should collapse the sidebar when arrow is clicked', function() { + assertSidebarState('collapsed'); $toggle.click(); assertSidebarState('expanded'); - $toggle.click(); - return assertSidebarState('collapsed'); }); it('should float over the page and when sidebar icons clicked', function() { $labelsIcon.click(); diff --git a/spec/javascripts/shortcuts_issuable_spec.js b/spec/javascripts/shortcuts_issuable_spec.js index 7d36d79b687..e37816b0a8c 100644 --- a/spec/javascripts/shortcuts_issuable_spec.js +++ b/spec/javascripts/shortcuts_issuable_spec.js @@ -4,9 +4,11 @@ (function() { describe('ShortcutsIssuable', function() { - fixture.preload('issuable.html'); + var fixtureName = 'issues/open-issue.html.raw'; + fixture.preload(fixtureName); beforeEach(function() { - fixture.load('issuable.html'); + fixture.load(fixtureName); + document.querySelector('.js-new-note-form').classList.add('js-main-target-form'); return this.shortcut = new ShortcutsIssuable(); }); return describe('#replyWithSelectedText', function() { diff --git a/spec/javascripts/vue_common_components/commit_spec.js.es6 b/spec/javascripts/vue_common_components/commit_spec.js.es6 index b1dbc8bd5fa..d170517dd9b 100644 --- a/spec/javascripts/vue_common_components/commit_spec.js.es6 +++ b/spec/javascripts/vue_common_components/commit_spec.js.es6 @@ -10,7 +10,7 @@ describe('Commit component', () => { el: document.querySelector('.test-commit-container'), propsData: { tag: false, - ref: { + commit_ref: { name: 'master', ref_url: 'http://localhost/namespace2/gitlabhq/tree/master', }, @@ -34,7 +34,7 @@ describe('Commit component', () => { props = { tag: true, - ref: { + commit_ref: { name: 'master', ref_url: 'http://localhost/namespace2/gitlabhq/tree/master', }, @@ -59,11 +59,11 @@ describe('Commit component', () => { }); it('should render a link to the ref url', () => { - expect(component.$el.querySelector('.branch-name').getAttribute('href')).toEqual(props.ref.ref_url); + expect(component.$el.querySelector('.branch-name').getAttribute('href')).toEqual(props.commit_ref.ref_url); }); it('should render the ref name', () => { - expect(component.$el.querySelector('.branch-name').textContent).toContain(props.ref.name); + expect(component.$el.querySelector('.branch-name').textContent).toContain(props.commit_ref.name); }); it('should render the commit short sha with a link to the commit url', () => { @@ -103,7 +103,7 @@ describe('Commit component', () => { fixture.set('<div class="test-commit-container"></div>'); props = { tag: false, - ref: { + commit_ref: { name: 'master', ref_url: 'http://localhost/namespace2/gitlabhq/tree/master', }, diff --git a/spec/lib/banzai/filter/table_of_contents_filter_spec.rb b/spec/lib/banzai/filter/table_of_contents_filter_spec.rb index 356dd01a03a..70b31f3a880 100644 --- a/spec/lib/banzai/filter/table_of_contents_filter_spec.rb +++ b/spec/lib/banzai/filter/table_of_contents_filter_spec.rb @@ -22,7 +22,7 @@ describe Banzai::Filter::TableOfContentsFilter, lib: true do html = header(i, "Header #{i}") doc = filter(html) - expect(doc.css("h#{i} a").first.attr('id')).to eq "header-#{i}" + expect(doc.css("h#{i} a").first.attr('id')).to eq "user-content-header-#{i}" end end @@ -32,7 +32,12 @@ describe Banzai::Filter::TableOfContentsFilter, lib: true do expect(doc.css('h1 a').first.attr('class')).to eq 'anchor' end - it 'links to the id' do + it 'has a namespaced id' do + doc = filter(header(1, 'Header')) + expect(doc.css('h1 a').first.attr('id')).to eq 'user-content-header' + end + + it 'links to the non-namespaced id' do doc = filter(header(1, 'Header')) expect(doc.css('h1 a').first.attr('href')).to eq '#header' end @@ -40,29 +45,29 @@ describe Banzai::Filter::TableOfContentsFilter, lib: true do describe 'generated IDs' do it 'translates spaces to dashes' do doc = filter(header(1, 'This header has spaces in it')) - expect(doc.css('h1 a').first.attr('id')).to eq 'this-header-has-spaces-in-it' + expect(doc.css('h1 a').first.attr('href')).to eq '#this-header-has-spaces-in-it' end it 'squeezes multiple spaces and dashes' do doc = filter(header(1, 'This---header is poorly-formatted')) - expect(doc.css('h1 a').first.attr('id')).to eq 'this-header-is-poorly-formatted' + expect(doc.css('h1 a').first.attr('href')).to eq '#this-header-is-poorly-formatted' end it 'removes punctuation' do doc = filter(header(1, "This, header! is, filled. with @ punctuation?")) - expect(doc.css('h1 a').first.attr('id')).to eq 'this-header-is-filled-with-punctuation' + expect(doc.css('h1 a').first.attr('href')).to eq '#this-header-is-filled-with-punctuation' end it 'appends a unique number to duplicates' do doc = filter(header(1, 'One') + header(2, 'One')) - expect(doc.css('h1 a').first.attr('id')).to eq 'one' - expect(doc.css('h2 a').first.attr('id')).to eq 'one-1' + expect(doc.css('h1 a').first.attr('href')).to eq '#one' + expect(doc.css('h2 a').first.attr('href')).to eq '#one-1' end it 'supports Unicode' do doc = filter(header(1, '한글')) - expect(doc.css('h1 a').first.attr('id')).to eq '한글' + expect(doc.css('h1 a').first.attr('id')).to eq 'user-content-한글' expect(doc.css('h1 a').first.attr('href')).to eq '#한글' end end diff --git a/spec/lib/gitlab/chat_commands/issue_create_spec.rb b/spec/lib/gitlab/chat_commands/issue_create_spec.rb index dd07cff9243..6c71e79ff6d 100644 --- a/spec/lib/gitlab/chat_commands/issue_create_spec.rb +++ b/spec/lib/gitlab/chat_commands/issue_create_spec.rb @@ -57,5 +57,12 @@ describe Gitlab::ChatCommands::IssueCreate, service: true do expect(match[:title]).to eq('my title') expect(match[:description]).to eq('description') end + + it 'matches the alias new' do + match = described_class.match("issue new my title") + + expect(match).not_to be_nil + expect(match[:title]).to eq('my title') + end end end diff --git a/spec/lib/gitlab/cycle_analytics/events_spec.rb b/spec/lib/gitlab/cycle_analytics/events_spec.rb index 9aeaa6b3ee8..6062e7af4f5 100644 --- a/spec/lib/gitlab/cycle_analytics/events_spec.rb +++ b/spec/lib/gitlab/cycle_analytics/events_spec.rb @@ -321,6 +321,6 @@ describe Gitlab::CycleAnalytics::Events do context.update(milestone: milestone) mr = create_merge_request_closing_issue(context) - ProcessCommitWorker.new.perform(project.id, user.id, mr.commits.last.sha) + ProcessCommitWorker.new.perform(project.id, user.id, mr.commits.last.to_hash) end end diff --git a/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb b/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb index c863a5f04cc..2a680f03476 100644 --- a/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb +++ b/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb @@ -10,4 +10,12 @@ describe Gitlab::Diff::FileCollection::MergeRequestDiff do described_class.new(merge_request.merge_request_diff, diff_options: nil).diff_files end + + it 'does not hightlight file if blob is not accessable' do + allow_any_instance_of(Gitlab::Diff::File).to receive(:blob).and_return(nil) + + expect_any_instance_of(Gitlab::Diff::File).not_to receive(:highlighted_diff_lines) + + described_class.new(merge_request.merge_request_diff, diff_options: nil).diff_files + end end diff --git a/spec/lib/gitlab/git_access_wiki_spec.rb b/spec/lib/gitlab/git_access_wiki_spec.rb index 576aa5c366f..578db51631e 100644 --- a/spec/lib/gitlab/git_access_wiki_spec.rb +++ b/spec/lib/gitlab/git_access_wiki_spec.rb @@ -26,4 +26,29 @@ describe Gitlab::GitAccessWiki, lib: true do def changes ['6f6d7e7ed 570e7b2ab refs/heads/master'] end + + describe '#download_access_check' do + subject { access.check('git-upload-pack', '_any') } + + before do + project.team << [user, :developer] + end + + context 'when wiki feature is enabled' do + it 'give access to download wiki code' do + project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::ENABLED) + + expect(subject.allowed?).to be_truthy + end + end + + context 'when wiki feature is disabled' do + it 'does not give access to download wiki code' do + project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::DISABLED) + + expect(subject.allowed?).to be_falsey + expect(subject.message).to match(/You are not allowed to download code/) + end + end + end end diff --git a/spec/lib/gitlab/github_import/branch_formatter_spec.rb b/spec/lib/gitlab/github_import/branch_formatter_spec.rb index e5300dbba1e..462caa5b5fe 100644 --- a/spec/lib/gitlab/github_import/branch_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/branch_formatter_spec.rb @@ -49,14 +49,20 @@ describe Gitlab::GithubImport::BranchFormatter, lib: true do end describe '#valid?' do - it 'returns true when raw repo is present' do + it 'returns true when raw sha and ref are present' do branch = described_class.new(project, double(raw)) expect(branch.valid?).to eq true end - it 'returns false when raw repo is blank' do - branch = described_class.new(project, double(raw.merge(repo: nil))) + it 'returns false when raw sha is blank' do + branch = described_class.new(project, double(raw.merge(sha: nil))) + + expect(branch.valid?).to eq false + end + + it 'returns false when raw ref is blank' do + branch = described_class.new(project, double(raw.merge(ref: nil))) expect(branch.valid?).to eq false end diff --git a/spec/migrations/migrate_process_commit_worker_jobs_spec.rb b/spec/migrations/migrate_process_commit_worker_jobs_spec.rb new file mode 100644 index 00000000000..52428547a9f --- /dev/null +++ b/spec/migrations/migrate_process_commit_worker_jobs_spec.rb @@ -0,0 +1,194 @@ +require 'spec_helper' +require Rails.root.join('db', 'migrate', '20161124141322_migrate_process_commit_worker_jobs.rb') + +describe MigrateProcessCommitWorkerJobs do + let(:project) { create(:project) } + let(:user) { create(:user) } + let(:commit) { project.commit.raw.raw_commit } + + describe 'Project' do + describe 'find_including_path' do + it 'returns Project instances' do + expect(described_class::Project.find_including_path(project.id)). + to be_an_instance_of(described_class::Project) + end + + it 'selects the full path for every Project' do + migration_project = described_class::Project. + find_including_path(project.id) + + expect(migration_project[:path_with_namespace]). + to eq(project.path_with_namespace) + end + end + + describe '#repository_storage_path' do + it 'returns the storage path for the repository' do + migration_project = described_class::Project. + find_including_path(project.id) + + expect(File.directory?(migration_project.repository_storage_path)). + to eq(true) + end + end + + describe '#repository_path' do + it 'returns the path to the repository' do + migration_project = described_class::Project. + find_including_path(project.id) + + expect(File.directory?(migration_project.repository_path)).to eq(true) + end + end + + describe '#repository' do + it 'returns a Rugged::Repository' do + migration_project = described_class::Project. + find_including_path(project.id) + + expect(migration_project.repository). + to be_an_instance_of(Rugged::Repository) + end + end + end + + describe '#up', :redis do + let(:migration) { described_class.new } + + def job_count + Sidekiq.redis { |r| r.llen('queue:process_commit') } + end + + before do + Sidekiq.redis do |redis| + job = JSON.dump(args: [project.id, user.id, commit.oid]) + redis.lpush('queue:process_commit', job) + end + end + + it 'skips jobs using a project that no longer exists' do + allow(described_class::Project).to receive(:find_including_path). + with(project.id). + and_return(nil) + + migration.up + + expect(job_count).to eq(0) + end + + it 'skips jobs using commits that no longer exist' do + allow_any_instance_of(Rugged::Repository).to receive(:lookup). + with(commit.oid). + and_raise(Rugged::OdbError) + + migration.up + + expect(job_count).to eq(0) + end + + it 'inserts migrated jobs back into the queue' do + migration.up + + expect(job_count).to eq(1) + end + + context 'a migrated job' do + let(:job) do + migration.up + + JSON.load(Sidekiq.redis { |r| r.lpop('queue:process_commit') }) + end + + let(:commit_hash) do + job['args'][2] + end + + it 'includes the project ID' do + expect(job['args'][0]).to eq(project.id) + end + + it 'includes the user ID' do + expect(job['args'][1]).to eq(user.id) + end + + it 'includes the commit ID' do + expect(commit_hash['id']).to eq(commit.oid) + end + + it 'includes the commit message' do + expect(commit_hash['message']).to eq(commit.message) + end + + it 'includes the parent IDs' do + expect(commit_hash['parent_ids']).to eq(commit.parent_ids) + end + + it 'includes the author date' do + expect(commit_hash['authored_date']).to eq(commit.author[:time].to_s) + end + + it 'includes the author name' do + expect(commit_hash['author_name']).to eq(commit.author[:name]) + end + + it 'includes the author Email' do + expect(commit_hash['author_email']).to eq(commit.author[:email]) + end + + it 'includes the commit date' do + expect(commit_hash['committed_date']).to eq(commit.committer[:time].to_s) + end + + it 'includes the committer name' do + expect(commit_hash['committer_name']).to eq(commit.committer[:name]) + end + + it 'includes the committer Email' do + expect(commit_hash['committer_email']).to eq(commit.committer[:email]) + end + end + end + + describe '#down', :redis do + let(:migration) { described_class.new } + + def job_count + Sidekiq.redis { |r| r.llen('queue:process_commit') } + end + + before do + Sidekiq.redis do |redis| + job = JSON.dump(args: [project.id, user.id, commit.oid]) + redis.lpush('queue:process_commit', job) + + migration.up + end + end + + it 'pushes migrated jobs back into the queue' do + migration.down + + expect(job_count).to eq(1) + end + + context 'a migrated job' do + let(:job) do + migration.down + + JSON.load(Sidekiq.redis { |r| r.lpop('queue:process_commit') }) + end + + it 'includes the project ID' do + expect(job['args'][0]).to eq(project.id) + end + + it 'includes the user ID' do + expect(job['args'][1]).to eq(user.id) + end + + it 'includes the commit SHA' do + expect(job['args'][2]).to eq(commit.oid) + end + end + end +end diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index ef07f2275b1..d4970e38f7c 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -730,8 +730,8 @@ describe Ci::Build, models: true do pipeline2 = create(:ci_pipeline, project: project) @build2 = create(:ci_build, pipeline: pipeline2) - commits = [double(id: pipeline.sha), double(id: pipeline2.sha)] - allow(@merge_request).to receive(:commits).and_return(commits) + allow(@merge_request).to receive(:commits_sha). + and_return([pipeline.sha, pipeline2.sha]) allow(MergeRequest).to receive_message_chain(:includes, :where, :reorder).and_return([@merge_request]) end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 3b12f16b4db..0d2b4920835 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe Ci::Pipeline, models: true do + include EmailHelpers + let(:project) { FactoryGirl.create :empty_project } let(:pipeline) { FactoryGirl.create :ci_empty_pipeline, status: 'created', project: project } diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index e3bb3482d67..7194c20d3bf 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -302,4 +302,21 @@ eos expect(commit.uri_type('this/path/doesnt/exist')).to be_nil end end + + describe '.from_hash' do + let(:new_commit) { described_class.from_hash(commit.to_hash, project) } + + it 'returns a Commit' do + expect(new_commit).to be_an_instance_of(described_class) + end + + it 'wraps a Gitlab::Git::Commit' do + expect(new_commit.raw).to be_an_instance_of(Gitlab::Git::Commit) + end + + it 'stores the correct commit fields' do + expect(new_commit.id).to eq(commit.id) + expect(new_commit.message).to eq(commit.message) + end + end end diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb index e5007424041..eb876d105da 100644 --- a/spec/models/merge_request_diff_spec.rb +++ b/spec/models/merge_request_diff_spec.rb @@ -77,24 +77,13 @@ describe MergeRequestDiff, models: true do end describe '#commits_sha' do - shared_examples 'returning all commits SHA' do - it 'returns all commits SHA' do - commits_sha = subject.commits_sha + it 'returns all commits SHA using serialized commits' do + subject.st_commits = [ + { id: 'sha1' }, + { id: 'sha2' } + ] - expect(commits_sha).to eq(subject.commits.map(&:sha)) - end - end - - context 'when commits were loaded' do - before do - subject.commits - end - - it_behaves_like 'returning all commits SHA' - end - - context 'when commits were not loaded' do - it_behaves_like 'returning all commits SHA' + expect(subject.commits_sha).to eq(['sha1', 'sha2']) end end @@ -113,4 +102,15 @@ describe MergeRequestDiff, models: true do expect(diffs.size).to eq(3) end end + + describe '#commits_count' do + it 'returns number of commits using serialized commits' do + subject.st_commits = [ + { id: 'sha1' }, + { id: 'sha2' } + ] + + expect(subject.commits_count).to eq 2 + end + end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 30cc91bf265..2cc818af6c7 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -557,16 +557,13 @@ describe MergeRequest, models: true do end describe '#commits_sha' do - let(:commit0) { double('commit0', sha: 'sha1') } - let(:commit1) { double('commit1', sha: 'sha2') } - let(:commit2) { double('commit2', sha: 'sha3') } - before do - allow(subject.merge_request_diff).to receive(:commits).and_return([commit0, commit1, commit2]) + allow(subject.merge_request_diff).to receive(:commits_sha). + and_return(['sha1']) end - it 'returns sha of commits' do - expect(subject.commits_sha).to contain_exactly('sha1', 'sha2', 'sha3') + it 'delegates to merge request diff' do + expect(subject.commits_sha).to eq ['sha1'] end end @@ -1440,4 +1437,26 @@ describe MergeRequest, models: true do end end end + + describe '#has_commits?' do + before do + allow(subject.merge_request_diff).to receive(:commits_count). + and_return(2) + end + + it 'returns true when merge request diff has commits' do + expect(subject.has_commits?).to be_truthy + end + end + + describe '#has_no_commits?' do + before do + allow(subject.merge_request_diff).to receive(:commits_count). + and_return(0) + end + + it 'returns true when merge request diff has 0 commits' do + expect(subject.has_no_commits?).to be_truthy + end + end end diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb index f5da967cd14..862e3a72a73 100644 --- a/spec/models/project_services/jira_service_spec.rb +++ b/spec/models/project_services/jira_service_spec.rb @@ -68,7 +68,7 @@ describe JiraService, models: true do end end - describe "Execute" do + describe '#close_issue' do let(:custom_base_url) { 'http://custom_url' } let(:user) { create(:user) } let(:project) { create(:project) } @@ -101,12 +101,10 @@ describe JiraService, models: true do @jira_service.save project_issues_url = 'http://gitlab_jira_username:gitlab_jira_password@jira.example.com/rest/api/2/issue/JIRA-123' - @project_url = 'http://gitlab_jira_username:gitlab_jira_password@jira.example.com/rest/api/2/project/GitLabProject' @transitions_url = 'http://gitlab_jira_username:gitlab_jira_password@jira.example.com/rest/api/2/issue/JIRA-123/transitions' @comment_url = 'http://gitlab_jira_username:gitlab_jira_password@jira.example.com/rest/api/2/issue/JIRA-123/comment' @remote_link_url = 'http://gitlab_jira_username:gitlab_jira_password@jira.example.com/rest/api/2/issue/JIRA-123/remotelink' - WebMock.stub_request(:get, @project_url) WebMock.stub_request(:get, project_issues_url) WebMock.stub_request(:post, @transitions_url) WebMock.stub_request(:post, @comment_url) @@ -114,7 +112,7 @@ describe JiraService, models: true do end it "calls JIRA API" do - @jira_service.execute(merge_request, ExternalIssue.new("JIRA-123", project)) + @jira_service.close_issue(merge_request, ExternalIssue.new("JIRA-123", project)) expect(WebMock).to have_requested(:post, @comment_url).with( body: /Issue solved with/ @@ -124,7 +122,7 @@ describe JiraService, models: true do # Check https://developer.atlassian.com/jiradev/jira-platform/guides/other/guide-jira-remote-issue-links/fields-in-remote-issue-links # for more information it "creates Remote Link reference in JIRA for comment" do - @jira_service.execute(merge_request, ExternalIssue.new("JIRA-123", project)) + @jira_service.close_issue(merge_request, ExternalIssue.new("JIRA-123", project)) # Creates comment expect(WebMock).to have_requested(:post, @comment_url) @@ -146,7 +144,7 @@ describe JiraService, models: true do it "does not send comment or remote links to issues already closed" do allow_any_instance_of(JIRA::Resource::Issue).to receive(:resolution).and_return(true) - @jira_service.execute(merge_request, ExternalIssue.new("JIRA-123", project)) + @jira_service.close_issue(merge_request, ExternalIssue.new("JIRA-123", project)) expect(WebMock).not_to have_requested(:post, @comment_url) expect(WebMock).not_to have_requested(:post, @remote_link_url) @@ -155,7 +153,7 @@ describe JiraService, models: true do it "references the GitLab commit/merge request" do stub_config_setting(base_url: custom_base_url) - @jira_service.execute(merge_request, ExternalIssue.new("JIRA-123", project)) + @jira_service.close_issue(merge_request, ExternalIssue.new("JIRA-123", project)) expect(WebMock).to have_requested(:post, @comment_url).with( body: /#{custom_base_url}\/#{project.path_with_namespace}\/commit\/#{merge_request.diff_head_sha}/ @@ -170,7 +168,7 @@ describe JiraService, models: true do { script_name: '/gitlab' } end - @jira_service.execute(merge_request, ExternalIssue.new("JIRA-123", project)) + @jira_service.close_issue(merge_request, ExternalIssue.new("JIRA-123", project)) expect(WebMock).to have_requested(:post, @comment_url).with( body: /#{Gitlab.config.gitlab.url}\/#{project.path_with_namespace}\/commit\/#{merge_request.diff_head_sha}/ @@ -178,19 +176,33 @@ describe JiraService, models: true do end it "calls the api with jira_issue_transition_id" do - @jira_service.execute(merge_request, ExternalIssue.new("JIRA-123", project)) + @jira_service.close_issue(merge_request, ExternalIssue.new("JIRA-123", project)) expect(WebMock).to have_requested(:post, @transitions_url).with( body: /custom-id/ ).once end + end - context "when testing" do - it "tries to get jira project" do - @jira_service.execute(nil) + describe '#test_settings' do + let(:jira_service) do + described_class.new( + url: 'http://jira.example.com', + username: 'gitlab_jira_username', + password: 'gitlab_jira_password', + project_key: 'GitLabProject' + ) + end + let(:project_url) { 'http://gitlab_jira_username:gitlab_jira_password@jira.example.com/rest/api/2/project/GitLabProject' } - expect(WebMock).to have_requested(:get, @project_url) - end + before do + WebMock.stub_request(:get, project_url) + end + + it 'tries to get JIRA project' do + jira_service.test_settings + + expect(WebMock).to have_requested(:get, project_url) end end diff --git a/spec/models/project_services/pipeline_email_service_spec.rb b/spec/models/project_services/pipeline_email_service_spec.rb index 4f56bceda44..7c8824485f5 100644 --- a/spec/models/project_services/pipeline_email_service_spec.rb +++ b/spec/models/project_services/pipeline_email_service_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe PipelinesEmailService do + include EmailHelpers + let(:pipeline) do create(:ci_pipeline, project: project, sha: project.commit('master').sha) end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 14c891994d0..2244803f90c 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -575,6 +575,23 @@ describe User, models: true do end end end + + describe '#require_ssh_key?' do + protocol_and_expectation = { + 'http' => false, + 'ssh' => true, + '' => true, + } + + protocol_and_expectation.each do |protocol, expected| + it "has correct require_ssh_key?" do + stub_application_setting(enabled_git_access_protocol: protocol) + user = build(:user) + + expect(user.require_ssh_key?).to eq(expected) + end + end + end end describe '.find_by_any_email' do diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index 96249a7d8c3..b49e4f3a8bc 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -23,7 +23,7 @@ describe ProjectPolicy, models: true do :download_code, :fork_project, :create_project_snippet, :update_issue, :admin_issue, :admin_label, :admin_list, :read_commit_status, :read_build, :read_container_image, :read_pipeline, :read_environment, :read_deployment, - :read_merge_request + :read_merge_request, :download_wiki_code ] end @@ -56,7 +56,8 @@ describe ProjectPolicy, models: true do let(:public_permissions) do [ :download_code, :fork_project, :read_commit_status, :read_pipeline, - :read_container_image, :build_download_code, :build_read_container_image + :read_container_image, :build_download_code, :build_read_container_image, + :download_wiki_code ] end @@ -87,6 +88,15 @@ describe ProjectPolicy, models: true do expect(Ability.allowed?(user, :read_issue, project)).to be_falsy end + it 'does not include the wiki permissions when the feature is disabled' do + project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::DISABLED) + wiki_permissions = [:read_wiki, :create_wiki, :update_wiki, :admin_wiki, :download_wiki_code] + + permissions = described_class.abilities(owner, project).to_set + + expect(permissions).not_to include(*wiki_permissions) + end + context 'abilities for non-public projects' do let(:project) { create(:empty_project, namespace: owner.namespace) } diff --git a/spec/rake_helper.rb b/spec/rake_helper.rb index 9b5b4bf9fea..298a520f5ca 100644 --- a/spec/rake_helper.rb +++ b/spec/rake_helper.rb @@ -8,7 +8,7 @@ RSpec.configure do |config| config.before(:all) do $stdout = StringIO.new - Rake.application.rake_require 'tasks/gitlab/task_helpers' + Rake.application.rake_require 'tasks/gitlab/helpers' Rake::Task.define_task :environment end diff --git a/spec/requests/api/api_helpers_spec.rb b/spec/requests/api/api_helpers_spec.rb index 01bb9e955e0..36517ad0f8c 100644 --- a/spec/requests/api/api_helpers_spec.rb +++ b/spec/requests/api/api_helpers_spec.rb @@ -47,7 +47,7 @@ describe API::Helpers, api: true do end def error!(message, status) - raise Exception + raise Exception.new("#{status} - #{message}") end describe ".current_user" do @@ -290,4 +290,56 @@ describe API::Helpers, api: true do handle_api_exception(exception) end end + + describe '.authenticate_non_get!' do + %w[HEAD GET].each do |method_name| + context "method is #{method_name}" do + before do + expect_any_instance_of(self.class).to receive(:route).and_return(double(route_method: method_name)) + end + + it 'does not raise an error' do + expect_any_instance_of(self.class).not_to receive(:authenticate!) + + expect { authenticate_non_get! }.not_to raise_error + end + end + end + + %w[POST PUT PATCH DELETE].each do |method_name| + context "method is #{method_name}" do + before do + expect_any_instance_of(self.class).to receive(:route).and_return(double(route_method: method_name)) + end + + it 'calls authenticate!' do + expect_any_instance_of(self.class).to receive(:authenticate!) + + authenticate_non_get! + end + end + end + end + + describe '.authenticate!' do + context 'current_user is nil' do + before do + expect_any_instance_of(self.class).to receive(:current_user).and_return(nil) + end + + it 'returns a 401 response' do + expect { authenticate! }.to raise_error '401 - {"message"=>"401 Unauthorized"}' + end + end + + context 'current_user is present' do + before do + expect_any_instance_of(self.class).to receive(:current_user).and_return(true) + end + + it 'does not raise an error' do + expect { authenticate! }.not_to raise_error + end + end + end end diff --git a/spec/requests/api/award_emoji_spec.rb b/spec/requests/api/award_emoji_spec.rb index 5ad4fc4865a..c8e8f31cc1f 100644 --- a/spec/requests/api/award_emoji_spec.rb +++ b/spec/requests/api/award_emoji_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::AwardEmoji, api: true do include ApiHelpers let(:user) { create(:user) } let!(:project) { create(:empty_project) } diff --git a/spec/requests/api/boards_spec.rb b/spec/requests/api/boards_spec.rb index 4f5c09a3029..3019724f52e 100644 --- a/spec/requests/api/boards_spec.rb +++ b/spec/requests/api/boards_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Boards, api: true do include ApiHelpers let(:user) { create(:user) } diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb index fe6b875b997..55b8c8c0c69 100644 --- a/spec/requests/api/branches_spec.rb +++ b/spec/requests/api/branches_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' require 'mime/types' -describe API::API, api: true do +describe API::Branches, api: true do include ApiHelpers let(:user) { create(:user) } @@ -31,11 +31,22 @@ describe API::API, api: true do expect(json_response['name']).to eq(branch_name) expect(json_response['commit']['id']).to eq(branch_sha) + expect(json_response['merged']).to eq(false) expect(json_response['protected']).to eq(false) expect(json_response['developers_can_push']).to eq(false) expect(json_response['developers_can_merge']).to eq(false) end + context 'on a merged branch' do + it "returns the branch information for a single branch" do + get api("/projects/#{project.id}/repository/branches/merge-test", user) + + expect(response).to have_http_status(200) + expect(json_response['name']).to eq('merge-test') + expect(json_response['merged']).to eq(true) + end + end + it "returns a 403 error if guest" do get api("/projects/#{project.id}/repository/branches", user2) expect(response).to have_http_status(403) diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb index fc72a44d663..0ea991b18b8 100644 --- a/spec/requests/api/builds_spec.rb +++ b/spec/requests/api/builds_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Builds, api: true do include ApiHelpers let(:user) { create(:user) } diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index a6e8550fac3..e497bce6943 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' require 'mime/types' -describe API::API, api: true do +describe API::Commits, api: true do include ApiHelpers let(:user) { create(:user) } let(:user2) { create(:user) } @@ -18,11 +18,14 @@ describe API::API, api: true do before { project.team << [user2, :reporter] } it "returns project commits" do + commit = project.repository.commit get api("/projects/#{project.id}/repository/commits", user) - expect(response).to have_http_status(200) + expect(response).to have_http_status(200) expect(json_response).to be_an Array - expect(json_response.first['id']).to eq(project.repository.commit.id) + expect(json_response.first['id']).to eq(commit.id) + expect(json_response.first['committer_name']).to eq(commit.committer_name) + expect(json_response.first['committer_email']).to eq(commit.committer_email) end end @@ -134,6 +137,8 @@ describe API::API, api: true do expect(response).to have_http_status(201) expect(json_response['title']).to eq(message) + expect(json_response['committer_name']).to eq(user.name) + expect(json_response['committer_email']).to eq(user.email) end it 'returns a 400 bad request if file exists' do diff --git a/spec/requests/api/deploy_keys_spec.rb b/spec/requests/api/deploy_keys_spec.rb index 65897edba7f..5fa7299044e 100644 --- a/spec/requests/api/deploy_keys_spec.rb +++ b/spec/requests/api/deploy_keys_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::DeployKeys, api: true do include ApiHelpers let(:user) { create(:user) } diff --git a/spec/requests/api/deployments_spec.rb b/spec/requests/api/deployments_spec.rb index 8fa8c66db6c..31e3cfa1b2f 100644 --- a/spec/requests/api/deployments_spec.rb +++ b/spec/requests/api/deployments_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Deployments, api: true do include ApiHelpers let(:user) { create(:user) } diff --git a/spec/requests/api/environments_spec.rb b/spec/requests/api/environments_spec.rb index 1898b07835d..126496c43a5 100644 --- a/spec/requests/api/environments_spec.rb +++ b/spec/requests/api/environments_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Environments, api: true do include ApiHelpers let(:user) { create(:user) } diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb index 050d0dd082d..2081f80ccc1 100644 --- a/spec/requests/api/files_spec.rb +++ b/spec/requests/api/files_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Files, api: true do include ApiHelpers let(:user) { create(:user) } let!(:project) { create(:project, namespace: user.namespace ) } diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index d9fdafde05e..548ed8e1892 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Groups, api: true do include ApiHelpers let(:user1) { create(:user, can_create_group: false) } diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index e88a7e27d45..35644bd8cc9 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Internal, api: true do include ApiHelpers let(:user) { create(:user) } let(:key) { create(:key, user: user) } diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index ae7994af981..5700f800c2e 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -1,7 +1,8 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Issues, api: true do include ApiHelpers + include EmailHelpers let(:user) { create(:user) } let(:user2) { create(:user) } diff --git a/spec/requests/api/keys_spec.rb b/spec/requests/api/keys_spec.rb index 893ed5c2b10..4c80987d680 100644 --- a/spec/requests/api/keys_spec.rb +++ b/spec/requests/api/keys_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Keys, api: true do include ApiHelpers let(:user) { create(:user) } diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb index aaf41639277..b29ce1ea25e 100644 --- a/spec/requests/api/labels_spec.rb +++ b/spec/requests/api/labels_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Labels, api: true do include ApiHelpers let(:user) { create(:user) } diff --git a/spec/requests/api/merge_request_diffs_spec.rb b/spec/requests/api/merge_request_diffs_spec.rb index 131c2d406ea..e1887138aab 100644 --- a/spec/requests/api/merge_request_diffs_spec.rb +++ b/spec/requests/api/merge_request_diffs_spec.rb @@ -1,6 +1,6 @@ require "spec_helper" -describe API::API, 'MergeRequestDiffs', api: true do +describe API::MergeRequestDiffs, 'MergeRequestDiffs', api: true do include ApiHelpers let!(:user) { create(:user) } diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index dce754fa1cb..0b89ac7960e 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -1,6 +1,6 @@ require "spec_helper" -describe API::API, api: true do +describe API::MergeRequests, api: true do include ApiHelpers let(:base_time) { Time.now } let(:user) { create(:user) } diff --git a/spec/requests/api/milestones_spec.rb b/spec/requests/api/milestones_spec.rb index b0946a838a1..8beef821d6c 100644 --- a/spec/requests/api/milestones_spec.rb +++ b/spec/requests/api/milestones_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Milestones, api: true do include ApiHelpers let(:user) { create(:user) } let!(:project) { create(:empty_project, namespace: user.namespace ) } diff --git a/spec/requests/api/namespaces_spec.rb b/spec/requests/api/namespaces_spec.rb index 5347cf4f7bc..c1edf384d5c 100644 --- a/spec/requests/api/namespaces_spec.rb +++ b/spec/requests/api/namespaces_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Namespaces, api: true do include ApiHelpers let(:admin) { create(:admin) } let(:user) { create(:user) } diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb index b71a4c5a56e..028f93c8561 100644 --- a/spec/requests/api/notes_spec.rb +++ b/spec/requests/api/notes_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Notes, api: true do include ApiHelpers let(:user) { create(:user) } let!(:project) { create(:project, :public, namespace: user.namespace) } diff --git a/spec/requests/api/notification_settings_spec.rb b/spec/requests/api/notification_settings_spec.rb index e6d8a5ee954..8691a81420f 100644 --- a/spec/requests/api/notification_settings_spec.rb +++ b/spec/requests/api/notification_settings_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::NotificationSettings, api: true do include ApiHelpers let(:user) { create(:user) } diff --git a/spec/requests/api/pipelines_spec.rb b/spec/requests/api/pipelines_spec.rb index a7e511aaeda..9a01f7fa1c4 100644 --- a/spec/requests/api/pipelines_spec.rb +++ b/spec/requests/api/pipelines_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Pipelines, api: true do include ApiHelpers let(:user) { create(:user) } diff --git a/spec/requests/api/project_hooks_spec.rb b/spec/requests/api/project_hooks_spec.rb index 5f39329a1b8..a42cedae614 100644 --- a/spec/requests/api/project_hooks_spec.rb +++ b/spec/requests/api/project_hooks_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, 'ProjectHooks', api: true do +describe API::ProjectHooks, 'ProjectHooks', api: true do include ApiHelpers let(:user) { create(:user) } let(:user3) { create(:user) } diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb index 1c25fd04339..01032c0929b 100644 --- a/spec/requests/api/project_snippets_spec.rb +++ b/spec/requests/api/project_snippets_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -describe API::API, api: true do +describe API::ProjectSnippets, api: true do include ApiHelpers let(:project) { create(:empty_project, :public) } diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index e53ee2a4e76..c5d67a90abc 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- require 'spec_helper' -describe API::API, api: true do +describe API::Projects, api: true do include ApiHelpers include Gitlab::CurrentSettings let(:user) { create(:user) } @@ -200,32 +200,43 @@ describe API::API, api: true do end describe 'GET /projects/visible' do - let(:public_project) { create(:project, :public) } + shared_examples_for 'visible projects response' do + it 'returns the visible projects' do + get api('/projects/visible', current_user) + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.map { |p| p['id'] }).to contain_exactly(*projects.map(&:id)) + end + end + + let!(:public_project) { create(:project, :public) } before do - public_project project project2 project3 project4 end - it 'returns the projects viewable by the user' do - get api('/projects/visible', user) - - expect(response).to have_http_status(200) - expect(json_response).to be_an Array - expect(json_response.map { |project| project['id'] }). - to contain_exactly(public_project.id, project.id, project2.id, project3.id) + context 'when unauthenticated' do + it_behaves_like 'visible projects response' do + let(:current_user) { nil } + let(:projects) { [public_project] } + end end - it 'shows only public projects when the user only has access to those' do - get api('/projects/visible', user2) + context 'when authenticated' do + it_behaves_like 'visible projects response' do + let(:current_user) { user } + let(:projects) { [public_project, project, project2, project3] } + end + end - expect(response).to have_http_status(200) - expect(json_response).to be_an Array - expect(json_response.map { |project| project['id'] }). - to contain_exactly(public_project.id) + context 'when authenticated as a different user' do + it_behaves_like 'visible projects response' do + let(:current_user) { user2 } + let(:projects) { [public_project] } + end end end @@ -415,16 +426,7 @@ describe API::API, api: true do not_to change { Project.count } expect(response).to have_http_status(400) - expect(json_response['message']['name']).to eq([ - 'can\'t be blank', - 'is too short (minimum is 0 characters)', - Gitlab::Regex.project_name_regex_message - ]) - expect(json_response['message']['path']).to eq([ - 'can\'t be blank', - 'is too short (minimum is 0 characters)', - Gitlab::Regex.send(:project_path_regex_message) - ]) + expect(json_response['error']).to eq('name is missing') end it 'assigns attributes to project' do @@ -438,6 +440,7 @@ describe API::API, api: true do post api("/projects/user/#{user.id}", admin), project + expect(response).to have_http_status(201) project.each_pair do |k, v| next if %i[has_external_issue_tracker path].include?(k) expect(json_response[k.to_s]).to eq(v) @@ -447,6 +450,8 @@ describe API::API, api: true do it 'sets a project as public' do project = attributes_for(:project, :public) post api("/projects/user/#{user.id}", admin), project + + expect(response).to have_http_status(201) expect(json_response['public']).to be_truthy expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PUBLIC) end @@ -454,6 +459,8 @@ describe API::API, api: true do it 'sets a project as public using :public' do project = attributes_for(:project, { public: true }) post api("/projects/user/#{user.id}", admin), project + + expect(response).to have_http_status(201) expect(json_response['public']).to be_truthy expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PUBLIC) end @@ -461,6 +468,8 @@ describe API::API, api: true do it 'sets a project as internal' do project = attributes_for(:project, :internal) post api("/projects/user/#{user.id}", admin), project + + expect(response).to have_http_status(201) expect(json_response['public']).to be_falsey expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::INTERNAL) end @@ -468,6 +477,7 @@ describe API::API, api: true do it 'sets a project as internal overriding :public' do project = attributes_for(:project, :internal, { public: true }) post api("/projects/user/#{user.id}", admin), project + expect(response).to have_http_status(201) expect(json_response['public']).to be_falsey expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::INTERNAL) end @@ -529,135 +539,150 @@ describe API::API, api: true do end describe 'GET /projects/:id' do - before { project } - before { project_member } - - it 'returns a project by id' do - group = create(:group) - link = create(:project_group_link, project: project, group: group) + context 'when unauthenticated' do + it 'returns the public projects' do + public_project = create(:project, :public) - get api("/projects/#{project.id}", user) + get api("/projects/#{public_project.id}") - expect(response).to have_http_status(200) - expect(json_response['id']).to eq(project.id) - expect(json_response['description']).to eq(project.description) - expect(json_response['default_branch']).to eq(project.default_branch) - expect(json_response['tag_list']).to be_an Array - expect(json_response['public']).to be_falsey - expect(json_response['archived']).to be_falsey - expect(json_response['visibility_level']).to be_present - expect(json_response['ssh_url_to_repo']).to be_present - expect(json_response['http_url_to_repo']).to be_present - expect(json_response['web_url']).to be_present - expect(json_response['owner']).to be_a Hash - expect(json_response['owner']).to be_a Hash - expect(json_response['name']).to eq(project.name) - expect(json_response['path']).to be_present - expect(json_response['issues_enabled']).to be_present - expect(json_response['merge_requests_enabled']).to be_present - expect(json_response['wiki_enabled']).to be_present - expect(json_response['builds_enabled']).to be_present - expect(json_response['snippets_enabled']).to be_present - expect(json_response['container_registry_enabled']).to be_present - expect(json_response['created_at']).to be_present - expect(json_response['last_activity_at']).to be_present - expect(json_response['shared_runners_enabled']).to be_present - expect(json_response['creator_id']).to be_present - expect(json_response['namespace']).to be_present - expect(json_response['avatar_url']).to be_nil - expect(json_response['star_count']).to be_present - expect(json_response['forks_count']).to be_present - expect(json_response['public_builds']).to be_present - expect(json_response['shared_with_groups']).to be_an Array - expect(json_response['shared_with_groups'].length).to eq(1) - expect(json_response['shared_with_groups'][0]['group_id']).to eq(group.id) - expect(json_response['shared_with_groups'][0]['group_name']).to eq(group.name) - expect(json_response['shared_with_groups'][0]['group_access_level']).to eq(link.group_access) - expect(json_response['only_allow_merge_if_build_succeeds']).to eq(project.only_allow_merge_if_build_succeeds) - expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to eq(project.only_allow_merge_if_all_discussions_are_resolved) - end - - it 'returns a project by path name' do - get api("/projects/#{project.id}", user) - expect(response).to have_http_status(200) - expect(json_response['name']).to eq(project.name) + expect(response).to have_http_status(200) + expect(json_response['id']).to eq(public_project.id) + expect(json_response['description']).to eq(public_project.description) + expect(json_response.keys).not_to include('permissions') + end end - it 'returns a 404 error if not found' do - get api('/projects/42', user) - expect(response).to have_http_status(404) - expect(json_response['message']).to eq('404 Project Not Found') - end + context 'when authenticated' do + before do + project + project_member + end - it 'returns a 404 error if user is not a member' do - other_user = create(:user) - get api("/projects/#{project.id}", other_user) - expect(response).to have_http_status(404) - end + it 'returns a project by id' do + group = create(:group) + link = create(:project_group_link, project: project, group: group) - it 'handles users with dots' do - dot_user = create(:user, username: 'dot.user') - project = create(:project, creator_id: dot_user.id, namespace: dot_user.namespace) + get api("/projects/#{project.id}", user) - get api("/projects/#{dot_user.namespace.name}%2F#{project.path}", dot_user) - expect(response).to have_http_status(200) - expect(json_response['name']).to eq(project.name) - end + expect(response).to have_http_status(200) + expect(json_response['id']).to eq(project.id) + expect(json_response['description']).to eq(project.description) + expect(json_response['default_branch']).to eq(project.default_branch) + expect(json_response['tag_list']).to be_an Array + expect(json_response['public']).to be_falsey + expect(json_response['archived']).to be_falsey + expect(json_response['visibility_level']).to be_present + expect(json_response['ssh_url_to_repo']).to be_present + expect(json_response['http_url_to_repo']).to be_present + expect(json_response['web_url']).to be_present + expect(json_response['owner']).to be_a Hash + expect(json_response['owner']).to be_a Hash + expect(json_response['name']).to eq(project.name) + expect(json_response['path']).to be_present + expect(json_response['issues_enabled']).to be_present + expect(json_response['merge_requests_enabled']).to be_present + expect(json_response['wiki_enabled']).to be_present + expect(json_response['builds_enabled']).to be_present + expect(json_response['snippets_enabled']).to be_present + expect(json_response['container_registry_enabled']).to be_present + expect(json_response['created_at']).to be_present + expect(json_response['last_activity_at']).to be_present + expect(json_response['shared_runners_enabled']).to be_present + expect(json_response['creator_id']).to be_present + expect(json_response['namespace']).to be_present + expect(json_response['avatar_url']).to be_nil + expect(json_response['star_count']).to be_present + expect(json_response['forks_count']).to be_present + expect(json_response['public_builds']).to be_present + expect(json_response['shared_with_groups']).to be_an Array + expect(json_response['shared_with_groups'].length).to eq(1) + expect(json_response['shared_with_groups'][0]['group_id']).to eq(group.id) + expect(json_response['shared_with_groups'][0]['group_name']).to eq(group.name) + expect(json_response['shared_with_groups'][0]['group_access_level']).to eq(link.group_access) + expect(json_response['only_allow_merge_if_build_succeeds']).to eq(project.only_allow_merge_if_build_succeeds) + expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to eq(project.only_allow_merge_if_all_discussions_are_resolved) + end + + it 'returns a project by path name' do + get api("/projects/#{project.id}", user) + expect(response).to have_http_status(200) + expect(json_response['name']).to eq(project.name) + end - describe 'permissions' do - context 'all projects' do - before { project.team << [user, :master] } + it 'returns a 404 error if not found' do + get api('/projects/42', user) + expect(response).to have_http_status(404) + expect(json_response['message']).to eq('404 Project Not Found') + end - it 'contains permission information' do - get api("/projects", user) + it 'returns a 404 error if user is not a member' do + other_user = create(:user) + get api("/projects/#{project.id}", other_user) + expect(response).to have_http_status(404) + end - expect(response).to have_http_status(200) - expect(json_response.first['permissions']['project_access']['access_level']). - to eq(Gitlab::Access::MASTER) - expect(json_response.first['permissions']['group_access']).to be_nil - end + it 'handles users with dots' do + dot_user = create(:user, username: 'dot.user') + project = create(:project, creator_id: dot_user.id, namespace: dot_user.namespace) + + get api("/projects/#{dot_user.namespace.name}%2F#{project.path}", dot_user) + expect(response).to have_http_status(200) + expect(json_response['name']).to eq(project.name) end - context 'personal project' do - it 'sets project access and returns 200' do - project.team << [user, :master] - get api("/projects/#{project.id}", user) + describe 'permissions' do + context 'all projects' do + before { project.team << [user, :master] } - expect(response).to have_http_status(200) - expect(json_response['permissions']['project_access']['access_level']). + it 'contains permission information' do + get api("/projects", user) + + expect(response).to have_http_status(200) + expect(json_response.first['permissions']['project_access']['access_level']). to eq(Gitlab::Access::MASTER) - expect(json_response['permissions']['group_access']).to be_nil + expect(json_response.first['permissions']['group_access']).to be_nil + end end - end - context 'group project' do - let(:project2) { create(:project, group: create(:group)) } + context 'personal project' do + it 'sets project access and returns 200' do + project.team << [user, :master] + get api("/projects/#{project.id}", user) - before { project2.group.add_owner(user) } + expect(response).to have_http_status(200) + expect(json_response['permissions']['project_access']['access_level']). + to eq(Gitlab::Access::MASTER) + expect(json_response['permissions']['group_access']).to be_nil + end + end - it 'sets the owner and return 200' do - get api("/projects/#{project2.id}", user) + context 'group project' do + let(:project2) { create(:project, group: create(:group)) } - expect(response).to have_http_status(200) - expect(json_response['permissions']['project_access']).to be_nil - expect(json_response['permissions']['group_access']['access_level']). + before { project2.group.add_owner(user) } + + it 'sets the owner and return 200' do + get api("/projects/#{project2.id}", user) + + expect(response).to have_http_status(200) + expect(json_response['permissions']['project_access']).to be_nil + expect(json_response['permissions']['group_access']['access_level']). to eq(Gitlab::Access::OWNER) + end end end end end describe 'GET /projects/:id/events' do - before { project_member2 } - - context 'valid request' do - before do + shared_examples_for 'project events response' do + it 'returns the project events' do + member = create(:user) + create(:project_member, :developer, user: member, project: project) note = create(:note_on_issue, note: 'What an awesome day!', project: project) EventCreateService.new.leave_note(note, note.author) - end - it 'returns all events' do - get api("/projects/#{project.id}/events", user) + get api("/projects/#{project.id}/events", current_user) expect(response).to have_http_status(200) @@ -670,24 +695,90 @@ describe API::API, api: true do expect(last_event['action_name']).to eq('joined') expect(last_event['project_id'].to_i).to eq(project.id) - expect(last_event['author_username']).to eq(user3.username) - expect(last_event['author']['name']).to eq(user3.name) + expect(last_event['author_username']).to eq(member.username) + expect(last_event['author']['name']).to eq(member.name) end end - it 'returns a 404 error if not found' do - get api('/projects/42/events', user) + context 'when unauthenticated' do + it_behaves_like 'project events response' do + let(:project) { create(:project, :public) } + let(:current_user) { nil } + end + end - expect(response).to have_http_status(404) - expect(json_response['message']).to eq('404 Project Not Found') + context 'when authenticated' do + context 'valid request' do + it_behaves_like 'project events response' do + let(:current_user) { user } + end + end + + it 'returns a 404 error if not found' do + get api('/projects/42/events', user) + + expect(response).to have_http_status(404) + expect(json_response['message']).to eq('404 Project Not Found') + end + + it 'returns a 404 error if user is not a member' do + other_user = create(:user) + + get api("/projects/#{project.id}/events", other_user) + + expect(response).to have_http_status(404) + end end + end - it 'returns a 404 error if user is not a member' do - other_user = create(:user) + describe 'GET /projects/:id/users' do + shared_examples_for 'project users response' do + it 'returns the project users' do + member = create(:user) + create(:project_member, :developer, user: member, project: project) - get api("/projects/#{project.id}/events", other_user) + get api("/projects/#{project.id}/users", current_user) - expect(response).to have_http_status(404) + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.size).to eq(1) + + first_user = json_response.first + + expect(first_user['username']).to eq(member.username) + expect(first_user['name']).to eq(member.name) + expect(first_user.keys).to contain_exactly(*%w[name username id state avatar_url web_url]) + end + end + + context 'when unauthenticated' do + it_behaves_like 'project users response' do + let(:project) { create(:project, :public) } + let(:current_user) { nil } + end + end + + context 'when authenticated' do + context 'valid request' do + it_behaves_like 'project users response' do + let(:current_user) { user } + end + end + + it 'returns a 404 error if not found' do + get api('/projects/42/users', user) + + expect(response).to have_http_status(404) + expect(json_response['message']).to eq('404 Project Not Found') + end + + it 'returns a 404 error if user is not a member' do + other_user = create(:user) + + get api("/projects/#{project.id}/users", other_user) + + expect(response).to have_http_status(404) + end end end @@ -848,7 +939,7 @@ describe API::API, api: true do it 'is idempotent if not forked' do expect(project_fork_target.forked_from_project).to be_nil delete api("/projects/#{project_fork_target.id}/fork", admin) - expect(response).to have_http_status(200) + expect(response).to have_http_status(304) expect(project_fork_target.reload.forked_from_project).to be_nil end end @@ -865,7 +956,7 @@ describe API::API, api: true do post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: Gitlab::Access::DEVELOPER, expires_at: expires_at end.to change { ProjectGroupLink.count }.by(1) - expect(response.status).to eq 201 + expect(response).to have_http_status(201) expect(json_response['group_id']).to eq(group.id) expect(json_response['group_access']).to eq(Gitlab::Access::DEVELOPER) expect(json_response['expires_at']).to eq(expires_at.to_s) @@ -873,18 +964,18 @@ describe API::API, api: true do it "returns a 400 error when group id is not given" do post api("/projects/#{project.id}/share", user), group_access: Gitlab::Access::DEVELOPER - expect(response.status).to eq 400 + expect(response).to have_http_status(400) end it "returns a 400 error when access level is not given" do post api("/projects/#{project.id}/share", user), group_id: group.id - expect(response.status).to eq 400 + expect(response).to have_http_status(400) end it "returns a 400 error when sharing is disabled" do project.namespace.update(share_with_group_lock: true) post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: Gitlab::Access::DEVELOPER - expect(response.status).to eq 400 + expect(response).to have_http_status(400) end it 'returns a 404 error when user cannot read group' do @@ -892,19 +983,20 @@ describe API::API, api: true do post api("/projects/#{project.id}/share", user), group_id: private_group.id, group_access: Gitlab::Access::DEVELOPER - expect(response.status).to eq 404 + expect(response).to have_http_status(404) end it 'returns a 404 error when group does not exist' do post api("/projects/#{project.id}/share", user), group_id: 1234, group_access: Gitlab::Access::DEVELOPER - expect(response.status).to eq 404 + expect(response).to have_http_status(404) end - it "returns a 409 error when wrong params passed" do + it "returns a 400 error when wrong params passed" do post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: 1234 - expect(response.status).to eq 409 - expect(json_response['message']).to eq 'Group access is not included in the list' + + expect(response).to have_http_status(400) + expect(json_response['error']).to eq 'group_access does not have a valid value' end end @@ -950,35 +1042,37 @@ describe API::API, api: true do let!(:public) { create(:empty_project, :public, name: "public #{query}") } let!(:unfound_public) { create(:empty_project, :public, name: 'unfound public') } + shared_examples_for 'project search response' do |args = {}| + it 'returns project search responses' do + get api("/projects/search/#{query}", current_user) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.size).to eq(args[:results]) + json_response.each { |project| expect(project['name']).to match(args[:match_regex] || /.*query.*/) } + end + end + context 'when unauthenticated' do - it 'returns authentication error' do - get api("/projects/search/#{query}") - expect(response).to have_http_status(401) + it_behaves_like 'project search response', results: 1 do + let(:current_user) { nil } end end context 'when authenticated' do - it 'returns an array of projects' do - get api("/projects/search/#{query}", user) - expect(response).to have_http_status(200) - expect(json_response).to be_an Array - expect(json_response.size).to eq(6) - json_response.each {|project| expect(project['name']).to match(/.*query.*/)} + it_behaves_like 'project search response', results: 6 do + let(:current_user) { user } end end context 'when authenticated as a different user' do - it 'returns matching public projects' do - get api("/projects/search/#{query}", user2) - expect(response).to have_http_status(200) - expect(json_response).to be_an Array - expect(json_response.size).to eq(2) - json_response.each {|project| expect(project['name']).to match(/(internal|public) query/)} + it_behaves_like 'project search response', results: 2, match_regex: /(internal|public) query/ do + let(:current_user) { user2 } end end end - describe 'PUT /projects/:id̈́' do + describe 'PUT /projects/:id' do before { project } before { user } before { user3 } @@ -1017,7 +1111,6 @@ describe API::API, api: true do it 'updates visibility_level from public to private' do project3.update_attributes({ visibility_level: Gitlab::VisibilityLevel::PUBLIC }) - project_param = { public: false } put api("/projects/#{project3.id}", user), project_param expect(response).to have_http_status(200) diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb index 38c8ad34f9d..c90b69e8ebb 100644 --- a/spec/requests/api/repositories_spec.rb +++ b/spec/requests/api/repositories_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' require 'mime/types' -describe API::API, api: true do +describe API::Repositories, api: true do include ApiHelpers include RepoHelpers include WorkhorseHelpers @@ -44,7 +44,6 @@ describe API::API, api: true do end end - describe 'GET /projects/:id/repository/tree?recursive=1' do context 'authorized user' do before { project.team << [user2, :reporter] } @@ -67,7 +66,7 @@ describe API::API, api: true do expect(json_response).to be_an Object json_response['message'] == '404 Tree Not Found' - end + end end context "unauthorized user" do diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb index bb0344e5995..d30361f53d4 100644 --- a/spec/requests/api/services_spec.rb +++ b/spec/requests/api/services_spec.rb @@ -1,6 +1,6 @@ require "spec_helper" -describe API::API, api: true do +describe API::Services, api: true do include ApiHelpers let(:user) { create(:user) } let(:admin) { create(:admin) } diff --git a/spec/requests/api/session_spec.rb b/spec/requests/api/session_spec.rb index e3f22b4c578..794e2b5c04d 100644 --- a/spec/requests/api/session_spec.rb +++ b/spec/requests/api/session_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Session, api: true do include ApiHelpers let(:user) { create(:user) } diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb index 096a8ebab70..9a8d633d657 100644 --- a/spec/requests/api/settings_spec.rb +++ b/spec/requests/api/settings_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, 'Settings', api: true do +describe API::Settings, 'Settings', api: true do include ApiHelpers let(:user) { create(:user) } diff --git a/spec/requests/api/system_hooks_spec.rb b/spec/requests/api/system_hooks_spec.rb index 6c9df21f598..b3e5afdadb1 100644 --- a/spec/requests/api/system_hooks_spec.rb +++ b/spec/requests/api/system_hooks_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::SystemHooks, api: true do include ApiHelpers let(:user) { create(:user) } diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb index d563883cd47..06fa94fae87 100644 --- a/spec/requests/api/tags_spec.rb +++ b/spec/requests/api/tags_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' require 'mime/types' -describe API::API, api: true do +describe API::Tags, api: true do include ApiHelpers include RepoHelpers diff --git a/spec/requests/api/triggers_spec.rb b/spec/requests/api/triggers_spec.rb index c890a51ae42..67ec3168679 100644 --- a/spec/requests/api/triggers_spec.rb +++ b/spec/requests/api/triggers_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API do +describe API::Triggers do include ApiHelpers let(:user) { create(:user) } diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 1a6e7716b2f..f82f52e7399 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Users, api: true do include ApiHelpers let(:user) { create(:user) } diff --git a/spec/requests/api/variables_spec.rb b/spec/requests/api/variables_spec.rb index 05fbdb909dc..7435f320607 100644 --- a/spec/requests/api/variables_spec.rb +++ b/spec/requests/api/variables_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Variables, api: true do include ApiHelpers let(:user) { create(:user) } diff --git a/spec/requests/api/version_spec.rb b/spec/requests/api/version_spec.rb index 54b69a0cae7..da1b2fda70e 100644 --- a/spec/requests/api/version_spec.rb +++ b/spec/requests/api/version_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Version, api: true do include ApiHelpers describe 'GET /version' do diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index a09d8689ff2..80652129928 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Ci::API::API do +describe Ci::API::Builds do include ApiHelpers let(:runner) { FactoryGirl.create(:ci_runner, tag_list: ["mysql", "ruby"]) } diff --git a/spec/requests/ci/api/runners_spec.rb b/spec/requests/ci/api/runners_spec.rb index d6c26fd8a94..bd55934d0c8 100644 --- a/spec/requests/ci/api/runners_spec.rb +++ b/spec/requests/ci/api/runners_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Ci::API::API do +describe Ci::API::Runners do include ApiHelpers include StubGitlabCalls diff --git a/spec/requests/ci/api/triggers_spec.rb b/spec/requests/ci/api/triggers_spec.rb index 0a0f979f57d..2d434ab5dd8 100644 --- a/spec/requests/ci/api/triggers_spec.rb +++ b/spec/requests/ci/api/triggers_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Ci::API::API do +describe Ci::API::Triggers do include ApiHelpers describe 'POST /projects/:project_id/refs/:ref/trigger' do diff --git a/spec/requests/projects/cycle_analytics_events_spec.rb b/spec/requests/projects/cycle_analytics_events_spec.rb index 5c90fd9bad9..f5e0fdcda2d 100644 --- a/spec/requests/projects/cycle_analytics_events_spec.rb +++ b/spec/requests/projects/cycle_analytics_events_spec.rb @@ -135,6 +135,6 @@ describe 'cycle analytics events' do merge_merge_requests_closing_issue(issue) - ProcessCommitWorker.new.perform(project.id, user.id, mr.commits.last.sha) + ProcessCommitWorker.new.perform(project.id, user.id, mr.commits.last.to_hash) end end diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb index 9d7702f5c96..e7624e70725 100644 --- a/spec/services/git_push_service_spec.rb +++ b/spec/services/git_push_service_spec.rb @@ -263,7 +263,7 @@ describe GitPushService, services: true do author_email: commit_author.email ) - allow_any_instance_of(ProcessCommitWorker).to receive(:find_commit). + allow_any_instance_of(ProcessCommitWorker).to receive(:build_commit). and_return(commit) allow(project.repository).to receive(:commits_between).and_return([commit]) @@ -321,7 +321,7 @@ describe GitPushService, services: true do committed_date: commit_time ) - allow_any_instance_of(ProcessCommitWorker).to receive(:find_commit). + allow_any_instance_of(ProcessCommitWorker).to receive(:build_commit). and_return(commit) allow(project.repository).to receive(:commits_between).and_return([commit]) @@ -360,7 +360,7 @@ describe GitPushService, services: true do allow(project.repository).to receive(:commits_between). and_return([closing_commit]) - allow_any_instance_of(ProcessCommitWorker).to receive(:find_commit). + allow_any_instance_of(ProcessCommitWorker).to receive(:build_commit). and_return(closing_commit) project.team << [commit_author, :master] diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index 4c878d748c0..500d224ff98 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -2,6 +2,8 @@ require 'spec_helper' describe Issues::UpdateService, services: true do + include EmailHelpers + let(:user) { create(:user) } let(:user2) { create(:user) } let(:user3) { create(:user) } diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb index dff1781d2aa..5a89acc96a4 100644 --- a/spec/services/merge_requests/merge_service_spec.rb +++ b/spec/services/merge_requests/merge_service_spec.rb @@ -75,7 +75,7 @@ describe MergeRequests::MergeService, services: true do commit = double('commit', safe_message: "Fixes #{jira_issue.to_reference}") allow(merge_request).to receive(:commits).and_return([commit]) - expect_any_instance_of(JiraService).to receive(:close_issue).with(merge_request, an_instance_of(JIRA::Resource::Issue)).once + expect_any_instance_of(JiraService).to receive(:close_issue).with(merge_request, jira_issue).once service.execute(merge_request) end diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb index 0bd6db1810a..790ef765f3a 100644 --- a/spec/services/merge_requests/update_service_spec.rb +++ b/spec/services/merge_requests/update_service_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe MergeRequests::UpdateService, services: true do + include EmailHelpers + let(:project) { create(:project) } let(:user) { create(:user) } let(:user2) { create(:user) } diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index 08ae61708a5..f3e80ac22a0 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe NotificationService, services: true do + include EmailHelpers + let(:notification) { NotificationService.new } around(:each) do |example| diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb index 7dcd03496bb..90771825f5c 100644 --- a/spec/services/projects/destroy_service_spec.rb +++ b/spec/services/projects/destroy_service_spec.rb @@ -7,15 +7,21 @@ describe Projects::DestroyService, services: true do let!(:remove_path) { path.sub(/\.git\Z/, "+#{project.id}+deleted.git") } let!(:async) { false } # execute or async_execute + shared_examples 'deleting the project' do + it 'deletes the project' do + expect(Project.all).not_to include(project) + expect(Dir.exist?(path)).to be_falsey + expect(Dir.exist?(remove_path)).to be_falsey + end + end + context 'Sidekiq inline' do before do # Run sidekiq immediatly to check that renamed repository will be removed Sidekiq::Testing.inline! { destroy_project(project, user, {}) } end - it { expect(Project.all).not_to include(project) } - it { expect(Dir.exist?(path)).to be_falsey } - it { expect(Dir.exist?(remove_path)).to be_falsey } + it_behaves_like 'deleting the project' end context 'Sidekiq fake' do @@ -38,11 +44,21 @@ describe Projects::DestroyService, services: true do Sidekiq::Testing.inline! { destroy_project(project, user, {}) } end - it 'deletes the project' do - expect(Project.all).not_to include(project) - expect(Dir.exist?(path)).to be_falsey - expect(Dir.exist?(remove_path)).to be_falsey + it_behaves_like 'deleting the project' + end + + context 'delete with pipeline' do # which has optimistic locking + let!(:pipeline) { create(:ci_pipeline, project: project) } + + before do + expect(project).to receive(:destroy!).and_call_original + + perform_enqueued_jobs do + destroy_project(project, user, {}) + end end + + it_behaves_like 'deleting the project' end context 'container registry' do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index bead1a006d1..6ee3307512d 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -32,7 +32,7 @@ RSpec.configure do |config| config.include LoginHelpers, type: :feature config.include SearchHelpers, type: :feature config.include StubConfiguration - config.include EmailHelpers + config.include EmailHelpers, type: :mailer config.include TestEnv config.include ActiveJob::TestHelper config.include ActiveSupport::Testing::TimeHelpers @@ -55,8 +55,12 @@ RSpec.configure do |config| config.around(:each, :redis) do |example| Gitlab::Redis.with(&:flushall) + Sidekiq.redis(&:flushall) + example.run + Gitlab::Redis.with(&:flushall) + Sidekiq.redis(&:flushall) end end diff --git a/spec/support/matchers/access_matchers.rb b/spec/support/matchers/access_matchers.rb index 691d7e05f57..ceddb656596 100644 --- a/spec/support/matchers/access_matchers.rb +++ b/spec/support/matchers/access_matchers.rb @@ -7,7 +7,7 @@ module AccessMatchers extend RSpec::Matchers::DSL include Warden::Test::Helpers - def emulate_user(user, project = nil) + def emulate_user(user, membership = nil) case user when :user login_as(create(:user)) @@ -19,16 +19,17 @@ module AccessMatchers login_as(create(:user, external: true)) when User login_as(user) - when :owner - raise ArgumentError, "cannot emulate owner without project" unless project - - login_as(project.owner) - when *Gitlab::Access.sym_options.keys - raise ArgumentError, "cannot emulate user #{user} without project" unless project + when *Gitlab::Access.sym_options_with_owner.keys + raise ArgumentError, "cannot emulate #{user} without membership parent" unless membership role = user - user = create(:user) - project.public_send(:"add_#{role}", user) + + if role == :owner && membership.owner + user = membership.owner + else + user = create(:user) + membership.public_send(:"add_#{role}", user) + end login_as(user) else @@ -47,14 +48,14 @@ module AccessMatchers matcher :be_allowed_for do |user| match do |url| - emulate_user(user, @project) + emulate_user(user, @membership) visit(url) status_code != 404 && current_path != new_user_session_path end - chain :of do |project| - @project = project + chain :of do |membership| + @membership = membership end description { description_for(user, 'allowed') } @@ -62,14 +63,14 @@ module AccessMatchers matcher :be_denied_for do |user| match do |url| - emulate_user(user, @project) + emulate_user(user, @membership) visit(url) status_code == 404 || current_path == new_user_session_path end - chain :of do |project| - @project = project + chain :of do |membership| + @membership = membership end description { description_for(user, 'denied') } diff --git a/spec/support/matchers/have_issuable_counts.rb b/spec/support/matchers/have_issuable_counts.rb index 02605d6b70e..92cf3de5448 100644 --- a/spec/support/matchers/have_issuable_counts.rb +++ b/spec/support/matchers/have_issuable_counts.rb @@ -1,9 +1,9 @@ RSpec::Matchers.define :have_issuable_counts do |opts| - match do |actual| - expected_counts = opts.map do |state, count| - "#{state.to_s.humanize} #{count}" - end + expected_counts = opts.map do |state, count| + "#{state.to_s.humanize} #{count}" + end + match do |actual| actual.within '.issues-state-filters' do expected_counts.each do |expected_count| expect(actual).to have_content(expected_count) diff --git a/spec/support/matchers/markdown_matchers.rb b/spec/support/matchers/markdown_matchers.rb index 8c98b1f988c..97b8b342eb2 100644 --- a/spec/support/matchers/markdown_matchers.rb +++ b/spec/support/matchers/markdown_matchers.rb @@ -38,9 +38,9 @@ module MarkdownMatchers set_default_markdown_messages match do |actual| - expect(actual).to have_selector('h1 a#gitlab-markdown') - expect(actual).to have_selector('h2 a#markdown') - expect(actual).to have_selector('h3 a#autolinkfilter') + expect(actual).to have_selector('h1 a#user-content-gitlab-markdown') + expect(actual).to have_selector('h2 a#user-content-markdown') + expect(actual).to have_selector('h3 a#user-content-autolinkfilter') end end diff --git a/spec/support/rake_helpers.rb b/spec/support/rake_helpers.rb index 52d80c69835..4a8158ed79b 100644 --- a/spec/support/rake_helpers.rb +++ b/spec/support/rake_helpers.rb @@ -1,7 +1,7 @@ module RakeHelpers - def run_rake_task(task_name) + def run_rake_task(task_name, *args) Rake::Task[task_name].reenable - Rake.application.invoke_task task_name + Rake.application.invoke_task("#{task_name}[#{args.join(',')}]") end def stub_warn_user_is_not_gitlab diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb index 287d83344db..a9fea5f1e81 100644 --- a/spec/tasks/gitlab/backup_rake_spec.rb +++ b/spec/tasks/gitlab/backup_rake_spec.rb @@ -5,7 +5,7 @@ describe 'gitlab:app namespace rake task' do let(:enable_registry) { true } before :all do - Rake.application.rake_require 'tasks/gitlab/task_helpers' + Rake.application.rake_require 'tasks/gitlab/helpers' Rake.application.rake_require 'tasks/gitlab/backup' Rake.application.rake_require 'tasks/gitlab/shell' Rake.application.rake_require 'tasks/gitlab/db' @@ -333,4 +333,35 @@ describe 'gitlab:app namespace rake task' do expect { run_rake_task('gitlab:backup:restore') }.not_to raise_error end end + + describe "Human Readable Backup Name" do + def tars_glob + Dir.glob(File.join(Gitlab.config.backup.path, '*_gitlab_backup.tar')) + end + + before :all do + @origin_cd = Dir.pwd + + reenable_backup_sub_tasks + + FileUtils.rm tars_glob + + # Redirect STDOUT and run the rake task + orig_stdout = $stdout + $stdout = StringIO.new + run_rake_task('gitlab:backup:create') + $stdout = orig_stdout + + @backup_tar = tars_glob.first + end + + after :all do + FileUtils.rm(@backup_tar) + Dir.chdir @origin_cd + end + + it 'name has human readable time' do + expect(@backup_tar).to match(/\d+_\d{4}_\d{2}_\d{2}_gitlab_backup.tar$/) + end + end end # gitlab:app namespace diff --git a/spec/tasks/gitlab/mail_google_schema_whitelisting.rb b/spec/tasks/gitlab/mail_google_schema_whitelisting.rb index 37feb5e6faf..80fc8c48fed 100644 --- a/spec/tasks/gitlab/mail_google_schema_whitelisting.rb +++ b/spec/tasks/gitlab/mail_google_schema_whitelisting.rb @@ -3,7 +3,7 @@ require 'rake' describe 'gitlab:mail_google_schema_whitelisting rake task' do before :all do - Rake.application.rake_require "tasks/gitlab/task_helpers" + Rake.application.rake_require "tasks/gitlab/helpers" Rake.application.rake_require "tasks/gitlab/mail_google_schema_whitelisting" # empty task as env is already loaded Rake::Task.define_task :environment diff --git a/spec/tasks/gitlab/task_helpers_spec.rb b/spec/tasks/gitlab/task_helpers_spec.rb new file mode 100644 index 00000000000..86e42d845ce --- /dev/null +++ b/spec/tasks/gitlab/task_helpers_spec.rb @@ -0,0 +1,96 @@ +require 'spec_helper' +require 'tasks/gitlab/task_helpers' + +class TestHelpersTest + include Gitlab::TaskHelpers +end + +describe Gitlab::TaskHelpers do + subject { TestHelpersTest.new } + + let(:repo) { 'https://gitlab.com/gitlab-org/gitlab-test.git' } + let(:clone_path) { Rails.root.join('tmp/tests/task_helpers_tests').to_s } + let(:tag) { 'v1.1.0' } + + describe '#checkout_or_clone_tag' do + before do + allow(subject).to receive(:run_command!) + expect(subject).to receive(:reset_to_tag).with(tag, clone_path) + end + + context 'target_dir does not exist' do + it 'clones the repo, retrieve the tag from origin, and checkout the tag' do + expect(subject).to receive(:clone_repo).with(repo, clone_path) + + subject.checkout_or_clone_tag(tag: tag, repo: repo, target_dir: clone_path) + end + end + + context 'target_dir exists' do + before do + expect(Dir).to receive(:exist?).and_return(true) + end + + it 'fetch and checkout the tag' do + expect(subject).to receive(:checkout_tag).with(tag, clone_path) + + subject.checkout_or_clone_tag(tag: tag, repo: repo, target_dir: clone_path) + end + end + end + + describe '#clone_repo' do + it 'clones the repo in the target dir' do + expect(subject). + to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} clone -- #{repo} #{clone_path}]) + + subject.clone_repo(repo, clone_path) + end + end + + describe '#checkout_tag' do + it 'clones the repo in the target dir' do + expect(subject). + to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} fetch --tags --quiet]) + expect(subject). + to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} checkout --quiet #{tag}]) + + subject.checkout_tag(tag, clone_path) + end + end + + describe '#reset_to_tag' do + let(:tag) { 'v1.1.0' } + before do + expect(subject). + to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} reset --hard #{tag}]) + end + + context 'when the tag is not checked out locally' do + before do + expect(subject). + to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} describe -- #{tag}]).and_raise(Gitlab::TaskFailedError) + end + + it 'fetch origin, ensure the tag exists, and resets --hard to the given tag' do + expect(subject). + to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} fetch origin]) + expect(subject). + to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} describe -- origin/#{tag}]).and_return(tag) + + subject.reset_to_tag(tag, clone_path) + end + end + + context 'when the tag is checked out locally' do + before do + expect(subject). + to receive(:run_command!).with(%W[#{Gitlab.config.git.bin_path} -C #{clone_path} describe -- #{tag}]).and_return(tag) + end + + it 'resets --hard to the given tag' do + subject.reset_to_tag(tag, clone_path) + end + end + end +end diff --git a/spec/tasks/gitlab/users_rake_spec.rb b/spec/tasks/gitlab/users_rake_spec.rb index e6ebef82b78..972670e7f91 100644 --- a/spec/tasks/gitlab/users_rake_spec.rb +++ b/spec/tasks/gitlab/users_rake_spec.rb @@ -5,7 +5,7 @@ describe 'gitlab:users namespace rake task' do let(:enable_registry) { true } before :all do - Rake.application.rake_require 'tasks/gitlab/task_helpers' + Rake.application.rake_require 'tasks/gitlab/helpers' Rake.application.rake_require 'tasks/gitlab/users' # empty task as env is already loaded diff --git a/spec/tasks/gitlab/workhorse_rake_spec.rb b/spec/tasks/gitlab/workhorse_rake_spec.rb new file mode 100644 index 00000000000..6de66c3cf07 --- /dev/null +++ b/spec/tasks/gitlab/workhorse_rake_spec.rb @@ -0,0 +1,81 @@ +require 'rake_helper' + +describe 'gitlab:workhorse namespace rake task' do + before :all do + Rake.application.rake_require 'tasks/gitlab/workhorse' + end + + describe 'install' do + let(:repo) { 'https://gitlab.com/gitlab-org/gitlab-workhorse.git' } + let(:clone_path) { Rails.root.join('tmp/tests/gitlab-workhorse').to_s } + let(:tag) { "v#{File.read(Rails.root.join(Gitlab::Workhorse::VERSION_FILE)).chomp}" } + before do + allow(ENV).to receive(:[]) + end + + context 'no dir given' do + it 'aborts and display a help message' do + # avoid writing task output to spec progress + allow($stderr).to receive :write + expect { run_rake_task('gitlab:workhorse:install') }.to raise_error /Please specify the directory where you want to install gitlab-workhorse/ + end + end + + context 'when an underlying Git command fail' do + it 'aborts and display a help message' do + expect_any_instance_of(Object). + to receive(:checkout_or_clone_tag).and_raise 'Git error' + + expect { run_rake_task('gitlab:workhorse:install', clone_path) }.to raise_error 'Git error' + end + end + + describe 'checkout or clone' do + before do + expect(Dir).to receive(:chdir).with(clone_path) + end + + it 'calls checkout_or_clone_tag with the right arguments' do + expect_any_instance_of(Object). + to receive(:checkout_or_clone_tag).with(tag: tag, repo: repo, target_dir: clone_path) + + run_rake_task('gitlab:workhorse:install', clone_path) + end + end + + describe 'gmake/make' do + before do + FileUtils.mkdir_p(clone_path) + expect(Dir).to receive(:chdir).with(clone_path).and_call_original + end + + context 'gmake is available' do + before do + expect_any_instance_of(Object).to receive(:checkout_or_clone_tag) + allow_any_instance_of(Object).to receive(:run_command!).with(['gmake']).and_return(true) + end + + it 'calls gmake in the gitlab-workhorse directory' do + expect(Gitlab::Popen).to receive(:popen).with(%w[which gmake]).and_return(['/usr/bin/gmake', 0]) + expect_any_instance_of(Object).to receive(:run_command!).with(['gmake']).and_return(true) + + run_rake_task('gitlab:workhorse:install', clone_path) + end + end + + context 'gmake is not available' do + before do + expect_any_instance_of(Object).to receive(:checkout_or_clone_tag) + allow_any_instance_of(Object).to receive(:run_command!).with(['make']).and_return(true) + end + + it 'calls make in the gitlab-workhorse directory' do + expect(Gitlab::Popen).to receive(:popen).with(%w[which gmake]).and_return(['', 42]) + expect_any_instance_of(Object).to receive(:run_command!).with(['make']).and_return(true) + + run_rake_task('gitlab:workhorse:install', clone_path) + end + end + end + end +end diff --git a/spec/workers/build_email_worker_spec.rb b/spec/workers/build_email_worker_spec.rb index a1aa336361a..542e674c150 100644 --- a/spec/workers/build_email_worker_spec.rb +++ b/spec/workers/build_email_worker_spec.rb @@ -1,6 +1,7 @@ require 'spec_helper' describe BuildEmailWorker do + include EmailHelpers include RepoHelpers let(:build) { create(:ci_build) } diff --git a/spec/workers/emails_on_push_worker_spec.rb b/spec/workers/emails_on_push_worker_spec.rb index fc652f6f4c3..f27e413f7b8 100644 --- a/spec/workers/emails_on_push_worker_spec.rb +++ b/spec/workers/emails_on_push_worker_spec.rb @@ -2,6 +2,7 @@ require 'spec_helper' describe EmailsOnPushWorker do include RepoHelpers + include EmailHelpers include EmailSpec::Matchers let(:project) { create(:project) } diff --git a/spec/workers/pipeline_notification_worker_spec.rb b/spec/workers/pipeline_notification_worker_spec.rb index d487a719680..739f9b63967 100644 --- a/spec/workers/pipeline_notification_worker_spec.rb +++ b/spec/workers/pipeline_notification_worker_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe PipelineNotificationWorker do + include EmailHelpers + let(:pipeline) do create(:ci_pipeline, project: project, diff --git a/spec/workers/process_commit_worker_spec.rb b/spec/workers/process_commit_worker_spec.rb index 3e4fee42240..75c7fc1efd2 100644 --- a/spec/workers/process_commit_worker_spec.rb +++ b/spec/workers/process_commit_worker_spec.rb @@ -11,31 +11,25 @@ describe ProcessCommitWorker do it 'does not process the commit when the project does not exist' do expect(worker).not_to receive(:close_issues) - worker.perform(-1, user.id, commit.id) + worker.perform(-1, user.id, commit.to_hash) end it 'does not process the commit when the user does not exist' do expect(worker).not_to receive(:close_issues) - worker.perform(project.id, -1, commit.id) - end - - it 'does not process the commit when the commit no longer exists' do - expect(worker).not_to receive(:close_issues) - - worker.perform(project.id, user.id, 'this-should-does-not-exist') + worker.perform(project.id, -1, commit.to_hash) end it 'processes the commit message' do expect(worker).to receive(:process_commit_message).and_call_original - worker.perform(project.id, user.id, commit.id) + worker.perform(project.id, user.id, commit.to_hash) end it 'updates the issue metrics' do expect(worker).to receive(:update_issue_metrics).and_call_original - worker.perform(project.id, user.id, commit.id) + worker.perform(project.id, user.id, commit.to_hash) end end @@ -106,4 +100,19 @@ describe ProcessCommitWorker do expect(metric.first_mentioned_in_commit_at).to eq(commit.committed_date) end end + + describe '#build_commit' do + it 'returns a Commit' do + commit = worker.build_commit(project, id: '123') + + expect(commit).to be_an_instance_of(Commit) + end + + it 'parses date strings into Time instances' do + commit = worker. + build_commit(project, id: '123', authored_date: Time.now.to_s) + + expect(commit.authored_date).to be_an_instance_of(Time) + end + end end |